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.identity=identity
|
||||
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
|
||||
|
||||
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
|
||||
|
@ -66,7 +72,6 @@ S3Proxy does not support:
|
|||
|
||||
* single-part uploads larger than 2 GB ([upstream issue](https://github.com/jclouds/jclouds/pull/426))
|
||||
* multi-part uploads
|
||||
* authorization of clients
|
||||
* bucket ACLs
|
||||
* server-side copy
|
||||
* URL signing
|
||||
|
|
|
@ -40,10 +40,16 @@ import org.jclouds.blobstore.BlobStoreContext;
|
|||
*/
|
||||
public final class S3Proxy {
|
||||
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;
|
||||
|
||||
public S3Proxy(BlobStore blobStore, URI endpoint) {
|
||||
public S3Proxy(BlobStore blobStore, URI endpoint, String identity,
|
||||
String credential) {
|
||||
Preconditions.checkNotNull(blobStore);
|
||||
Preconditions.checkNotNull(endpoint);
|
||||
// TODO: allow service paths?
|
||||
|
@ -58,7 +64,7 @@ public final class S3Proxy {
|
|||
connector.setHost(endpoint.getHost());
|
||||
connector.setPort(endpoint.getPort());
|
||||
server.addConnector(connector);
|
||||
server.setHandler(new S3ProxyHandler(blobStore));
|
||||
server.setHandler(new S3ProxyHandler(blobStore, identity, credential));
|
||||
}
|
||||
|
||||
public void start() throws Exception {
|
||||
|
@ -86,13 +92,35 @@ public final class S3Proxy {
|
|||
String endpoint = properties.getProperty(Constants.PROPERTY_ENDPOINT);
|
||||
String s3ProxyEndpointString = properties.getProperty(
|
||||
PROPERTY_S3PROXY_ENDPOINT);
|
||||
String s3ProxyAuthorization = properties.getProperty(
|
||||
PROPERTY_S3PROXY_AUTHORIZATION);
|
||||
if (provider == null || identity == null || credential == null
|
||||
|| s3ProxyEndpointString == null) {
|
||||
|| s3ProxyEndpointString == null
|
||||
|| s3ProxyAuthorization == null) {
|
||||
System.err.println("Properties file must contain:\n" +
|
||||
Constants.PROPERTY_PROVIDER + "\n" +
|
||||
Constants.PROPERTY_IDENTITY + "\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);
|
||||
}
|
||||
|
||||
|
@ -105,7 +133,8 @@ public final class S3Proxy {
|
|||
}
|
||||
BlobStoreContext context = builder.build(BlobStoreContext.class);
|
||||
URI s3ProxyEndpoint = new URI(s3ProxyEndpointString);
|
||||
S3Proxy s3Proxy = new S3Proxy(context.getBlobStore(), s3ProxyEndpoint);
|
||||
S3Proxy s3Proxy = new S3Proxy(context.getBlobStore(), s3ProxyEndpoint,
|
||||
localIdentity, localCredential);
|
||||
s3Proxy.start();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,19 +20,28 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
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.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
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.io.BaseEncoding;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
@ -69,8 +78,13 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
private static final String FAKE_REQUEST_ID = "4442587FB7D0A2F9";
|
||||
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.identity = identity;
|
||||
this.credential = credential;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -83,6 +97,19 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
String[] path = uri.split("/", 3);
|
||||
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) {
|
||||
case "DELETE":
|
||||
if (uri.lastIndexOf("/") == 0) {
|
||||
|
@ -618,6 +645,7 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
|
||||
private static void sendSimpleErrorResponse(HttpServletResponse response,
|
||||
int status, String code, String message, Optional<String> extra) {
|
||||
logger.debug("{} {} {} {}", status, code, message, extra);
|
||||
try (Writer writer = response.getWriter()) {
|
||||
response.setStatus(status);
|
||||
writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" +
|
||||
|
@ -640,4 +668,68 @@ final class S3ProxyHandler extends AbstractHandler {
|
|||
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.identity=identity
|
||||
jclouds.credential=credential
|
||||
# endpoint is optional for some providers
|
||||
#jclouds.endpoint=http://127.0.0.1:8081
|
||||
jclouds.filesystem.basedir=/tmp/blobstore
|
||||
|
||||
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())
|
||||
.build(BlobStoreContext.class);
|
||||
s3BlobStore = s3Context.getBlobStore();
|
||||
s3Proxy = new S3Proxy(blobStore, s3Endpoint);
|
||||
s3Proxy = new S3Proxy(blobStore, s3Endpoint, "identity", "credential");
|
||||
s3Proxy.start();
|
||||
}
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue