kopia lustrzana https://github.com/gaul/s3proxy
rodzic
3f52992a25
commit
f7db3ee4fb
|
@ -52,9 +52,15 @@ Users can configure S3Proxy via a properties file. An example:
|
||||||
jclouds.provider=transient
|
jclouds.provider=transient
|
||||||
jclouds.identity=identity
|
jclouds.identity=identity
|
||||||
jclouds.credential=credential
|
jclouds.credential=credential
|
||||||
#jclouds.endpoint=http://127.0.0.1:8081 # optional for some providers
|
# endpoint is optional for some providers
|
||||||
|
#jclouds.endpoint=http://127.0.0.1:8081
|
||||||
jclouds.filesystem.basedir=/tmp/blobstore
|
jclouds.filesystem.basedir=/tmp/blobstore
|
||||||
|
|
||||||
s3proxy.endpoint=http://127.0.0.1:8080
|
s3proxy.endpoint=http://127.0.0.1:8080
|
||||||
|
# authorization must be aws-v2 or none
|
||||||
|
s3proxy.authorization=aws-v2
|
||||||
|
s3proxy.identity=identity
|
||||||
|
s3proxy.credential=credential
|
||||||
```
|
```
|
||||||
|
|
||||||
Users can also set a variety of Java and
|
Users can also set a variety of Java and
|
||||||
|
@ -66,7 +72,6 @@ S3Proxy does not support:
|
||||||
|
|
||||||
* single-part uploads larger than 2 GB ([upstream issue](https://github.com/jclouds/jclouds/pull/426))
|
* single-part uploads larger than 2 GB ([upstream issue](https://github.com/jclouds/jclouds/pull/426))
|
||||||
* multi-part uploads
|
* multi-part uploads
|
||||||
* authorization of clients
|
|
||||||
* bucket ACLs
|
* bucket ACLs
|
||||||
* server-side copy
|
* server-side copy
|
||||||
* URL signing
|
* URL signing
|
||||||
|
|
|
@ -40,10 +40,16 @@ import org.jclouds.blobstore.BlobStoreContext;
|
||||||
*/
|
*/
|
||||||
public final class S3Proxy {
|
public final class S3Proxy {
|
||||||
private static final String PROPERTY_S3PROXY_ENDPOINT = "s3proxy.endpoint";
|
private static final String PROPERTY_S3PROXY_ENDPOINT = "s3proxy.endpoint";
|
||||||
|
private static final String PROPERTY_S3PROXY_AUTHORIZATION =
|
||||||
|
"s3proxy.authorization";
|
||||||
|
private static final String PROPERTY_S3PROXY_IDENTITY = "s3proxy.identity";
|
||||||
|
private static final String PROPERTY_S3PROXY_CREDENTIAL =
|
||||||
|
"s3proxy.credential";
|
||||||
|
|
||||||
private final Server server;
|
private final Server server;
|
||||||
|
|
||||||
public S3Proxy(BlobStore blobStore, URI endpoint) {
|
public S3Proxy(BlobStore blobStore, URI endpoint, String identity,
|
||||||
|
String credential) {
|
||||||
Preconditions.checkNotNull(blobStore);
|
Preconditions.checkNotNull(blobStore);
|
||||||
Preconditions.checkNotNull(endpoint);
|
Preconditions.checkNotNull(endpoint);
|
||||||
// TODO: allow service paths?
|
// TODO: allow service paths?
|
||||||
|
@ -58,7 +64,7 @@ public final class S3Proxy {
|
||||||
connector.setHost(endpoint.getHost());
|
connector.setHost(endpoint.getHost());
|
||||||
connector.setPort(endpoint.getPort());
|
connector.setPort(endpoint.getPort());
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
server.setHandler(new S3ProxyHandler(blobStore));
|
server.setHandler(new S3ProxyHandler(blobStore, identity, credential));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void start() throws Exception {
|
public void start() throws Exception {
|
||||||
|
@ -86,13 +92,35 @@ public final class S3Proxy {
|
||||||
String endpoint = properties.getProperty(Constants.PROPERTY_ENDPOINT);
|
String endpoint = properties.getProperty(Constants.PROPERTY_ENDPOINT);
|
||||||
String s3ProxyEndpointString = properties.getProperty(
|
String s3ProxyEndpointString = properties.getProperty(
|
||||||
PROPERTY_S3PROXY_ENDPOINT);
|
PROPERTY_S3PROXY_ENDPOINT);
|
||||||
|
String s3ProxyAuthorization = properties.getProperty(
|
||||||
|
PROPERTY_S3PROXY_AUTHORIZATION);
|
||||||
if (provider == null || identity == null || credential == null
|
if (provider == null || identity == null || credential == null
|
||||||
|| s3ProxyEndpointString == null) {
|
|| s3ProxyEndpointString == null
|
||||||
|
|| s3ProxyAuthorization == null) {
|
||||||
System.err.println("Properties file must contain:\n" +
|
System.err.println("Properties file must contain:\n" +
|
||||||
Constants.PROPERTY_PROVIDER + "\n" +
|
Constants.PROPERTY_PROVIDER + "\n" +
|
||||||
Constants.PROPERTY_IDENTITY + "\n" +
|
Constants.PROPERTY_IDENTITY + "\n" +
|
||||||
Constants.PROPERTY_CREDENTIAL + "\n" +
|
Constants.PROPERTY_CREDENTIAL + "\n" +
|
||||||
PROPERTY_S3PROXY_ENDPOINT);
|
PROPERTY_S3PROXY_ENDPOINT + "\n" +
|
||||||
|
PROPERTY_S3PROXY_AUTHORIZATION);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String localIdentity = null;
|
||||||
|
String localCredential = null;
|
||||||
|
if (s3ProxyAuthorization.equalsIgnoreCase("aws-v2")) {
|
||||||
|
localIdentity = properties.getProperty(PROPERTY_S3PROXY_IDENTITY);
|
||||||
|
localCredential = properties.getProperty(
|
||||||
|
PROPERTY_S3PROXY_CREDENTIAL);
|
||||||
|
if (localIdentity == null || localCredential == null) {
|
||||||
|
System.err.println("Both " + PROPERTY_S3PROXY_IDENTITY +
|
||||||
|
" and " + PROPERTY_S3PROXY_CREDENTIAL +
|
||||||
|
" must be set");
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
} else if (!s3ProxyAuthorization.equalsIgnoreCase("none")) {
|
||||||
|
System.err.println(PROPERTY_S3PROXY_AUTHORIZATION +
|
||||||
|
" must be aws-v2 or none, was: " + s3ProxyAuthorization);
|
||||||
System.exit(1);
|
System.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +133,8 @@ public final class S3Proxy {
|
||||||
}
|
}
|
||||||
BlobStoreContext context = builder.build(BlobStoreContext.class);
|
BlobStoreContext context = builder.build(BlobStoreContext.class);
|
||||||
URI s3ProxyEndpoint = new URI(s3ProxyEndpointString);
|
URI s3ProxyEndpoint = new URI(s3ProxyEndpointString);
|
||||||
S3Proxy s3Proxy = new S3Proxy(context.getBlobStore(), s3ProxyEndpoint);
|
S3Proxy s3Proxy = new S3Proxy(context.getBlobStore(), s3ProxyEndpoint,
|
||||||
|
localIdentity, localCredential);
|
||||||
s3Proxy.start();
|
s3Proxy.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,19 +20,28 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.io.Writer;
|
import java.io.Writer;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.SortedSetMultimap;
|
||||||
|
import com.google.common.collect.TreeMultimap;
|
||||||
import com.google.common.hash.HashCode;
|
import com.google.common.hash.HashCode;
|
||||||
import com.google.common.io.BaseEncoding;
|
import com.google.common.io.BaseEncoding;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
|
@ -69,8 +78,13 @@ final class S3ProxyHandler extends AbstractHandler {
|
||||||
private static final String FAKE_REQUEST_ID = "4442587FB7D0A2F9";
|
private static final String FAKE_REQUEST_ID = "4442587FB7D0A2F9";
|
||||||
private final BlobStore blobStore;
|
private final BlobStore blobStore;
|
||||||
|
|
||||||
S3ProxyHandler(BlobStore blobStore) {
|
private final String identity;
|
||||||
|
private final String credential;
|
||||||
|
|
||||||
|
S3ProxyHandler(BlobStore blobStore, String identity, String credential) {
|
||||||
this.blobStore = Preconditions.checkNotNull(blobStore);
|
this.blobStore = Preconditions.checkNotNull(blobStore);
|
||||||
|
this.identity = identity;
|
||||||
|
this.credential = credential;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -83,6 +97,19 @@ final class S3ProxyHandler extends AbstractHandler {
|
||||||
String[] path = uri.split("/", 3);
|
String[] path = uri.split("/", 3);
|
||||||
logger.debug("request: {}", request);
|
logger.debug("request: {}", request);
|
||||||
|
|
||||||
|
if (identity != null) {
|
||||||
|
String expectedAuthorization = createAuthorizationHeader(request,
|
||||||
|
identity, credential);
|
||||||
|
if (!expectedAuthorization.equals(request.getHeader(
|
||||||
|
HttpHeaders.AUTHORIZATION))) {
|
||||||
|
sendSimpleErrorResponse(response,
|
||||||
|
HttpServletResponse.SC_FORBIDDEN,
|
||||||
|
"SignatureDoesNotMatch", "Forbidden");
|
||||||
|
baseRequest.setHandled(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case "DELETE":
|
case "DELETE":
|
||||||
if (uri.lastIndexOf("/") == 0) {
|
if (uri.lastIndexOf("/") == 0) {
|
||||||
|
@ -618,6 +645,7 @@ final class S3ProxyHandler extends AbstractHandler {
|
||||||
|
|
||||||
private static void sendSimpleErrorResponse(HttpServletResponse response,
|
private static void sendSimpleErrorResponse(HttpServletResponse response,
|
||||||
int status, String code, String message, Optional<String> extra) {
|
int status, String code, String message, Optional<String> extra) {
|
||||||
|
logger.debug("{} {} {} {}", status, code, message, extra);
|
||||||
try (Writer writer = response.getWriter()) {
|
try (Writer writer = response.getWriter()) {
|
||||||
response.setStatus(status);
|
response.setStatus(status);
|
||||||
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
|
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
|
||||||
|
@ -640,4 +668,68 @@ final class S3ProxyHandler extends AbstractHandler {
|
||||||
ioe.getMessage());
|
ioe.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create Amazon V2 authorization header. Reference:
|
||||||
|
* http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html
|
||||||
|
*/
|
||||||
|
private static String createAuthorizationHeader(HttpServletRequest request,
|
||||||
|
String identity, String credential) {
|
||||||
|
// sort Amazon headers
|
||||||
|
SortedSetMultimap<String, String> canonicalizedHeaders =
|
||||||
|
TreeMultimap.create();
|
||||||
|
for (String headerName : Collections.list(request.getHeaderNames())) {
|
||||||
|
headerName = headerName.toLowerCase();
|
||||||
|
if (!headerName.startsWith("x-amz-")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (String headerValue : Collections.list(request.getHeaders(
|
||||||
|
headerName))) {
|
||||||
|
canonicalizedHeaders.put(headerName, Strings.nullToEmpty(
|
||||||
|
headerValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build string to sign
|
||||||
|
StringBuilder builder = new StringBuilder()
|
||||||
|
.append(request.getMethod()).append('\n');
|
||||||
|
String contentMD5 = request.getHeader(HttpHeaders.CONTENT_MD5);
|
||||||
|
if (contentMD5 != null) {
|
||||||
|
builder.append(contentMD5);
|
||||||
|
}
|
||||||
|
builder.append('\n');
|
||||||
|
String contentType = request.getHeader(HttpHeaders.CONTENT_TYPE);
|
||||||
|
if (contentType != null) {
|
||||||
|
builder.append(contentType);
|
||||||
|
}
|
||||||
|
builder.append('\n');
|
||||||
|
if (!canonicalizedHeaders.containsKey("x-amz-date")) {
|
||||||
|
builder.append(request.getHeader(HttpHeaders.DATE));
|
||||||
|
}
|
||||||
|
builder.append('\n');
|
||||||
|
for (Map.Entry<String, String> entry : canonicalizedHeaders.entries()) {
|
||||||
|
builder.append(entry.getKey()).append(':')
|
||||||
|
.append(entry.getValue()).append('\n');
|
||||||
|
}
|
||||||
|
builder.append(request.getRequestURI());
|
||||||
|
if ("".equals(request.getParameter("acl"))) {
|
||||||
|
builder.append("?acl");
|
||||||
|
}
|
||||||
|
String stringToSign = builder.toString();
|
||||||
|
logger.debug("stringToSign: {}", stringToSign);
|
||||||
|
|
||||||
|
// sign string
|
||||||
|
Mac mac;
|
||||||
|
try {
|
||||||
|
mac = Mac.getInstance("HmacSHA1");
|
||||||
|
mac.init(new SecretKeySpec(credential.getBytes(
|
||||||
|
StandardCharsets.UTF_8), "HmacSHA1"));
|
||||||
|
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
|
||||||
|
throw Throwables.propagate(e);
|
||||||
|
}
|
||||||
|
String signature = BaseEncoding.base64().encode(mac.doFinal(
|
||||||
|
stringToSign.getBytes(StandardCharsets.UTF_8)));
|
||||||
|
|
||||||
|
return "AWS" + " " + identity + ":" + signature;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
jclouds.provider=transient
|
jclouds.provider=transient
|
||||||
jclouds.identity=identity
|
jclouds.identity=identity
|
||||||
jclouds.credential=credential
|
jclouds.credential=credential
|
||||||
|
# endpoint is optional for some providers
|
||||||
#jclouds.endpoint=http://127.0.0.1:8081
|
#jclouds.endpoint=http://127.0.0.1:8081
|
||||||
jclouds.filesystem.basedir=/tmp/blobstore
|
jclouds.filesystem.basedir=/tmp/blobstore
|
||||||
|
|
||||||
s3proxy.endpoint=http://127.0.0.1:8080
|
s3proxy.endpoint=http://127.0.0.1:8080
|
||||||
#s3proxy.loglevel=INFO # TODO: not yet supported
|
# authorization must be aws-v2 or none
|
||||||
|
s3proxy.authorization=aws-v2
|
||||||
|
s3proxy.identity=identity
|
||||||
|
s3proxy.credential=credential
|
||||||
|
|
|
@ -65,7 +65,7 @@ public final class S3ProxyTest {
|
||||||
.endpoint(s3Endpoint.toString())
|
.endpoint(s3Endpoint.toString())
|
||||||
.build(BlobStoreContext.class);
|
.build(BlobStoreContext.class);
|
||||||
s3BlobStore = s3Context.getBlobStore();
|
s3BlobStore = s3Context.getBlobStore();
|
||||||
s3Proxy = new S3Proxy(blobStore, s3Endpoint);
|
s3Proxy = new S3Proxy(blobStore, s3Endpoint, "identity", "credential");
|
||||||
s3Proxy.start();
|
s3Proxy.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue