Allow endpoint of s3proxy to have service path

Sometimes the service is not the only one under a domain.
This commit adds s3proxy.service-path to allow s3proxy to be deployed
with a context path. Fixes #48.
pull/161/merge
shenghu 2017-03-12 19:55:49 -04:00 zatwierdzone przez Andrew Gaul
rodzic 8224f6fe6e
commit 8b2d056d40
13 zmienionych plików z 95 dodań i 16 usunięć

Wyświetl plik

@ -97,6 +97,9 @@ public final class Main {
System.exit(1);
}
String s3ProxyServicePath = properties.getProperty(
S3ProxyConstants.PROPERTY_SERVICE_PATH);
AuthenticationType s3ProxyAuthorization =
AuthenticationType.fromString(s3ProxyAuthorizationString);
String localIdentity = null;
@ -215,6 +218,9 @@ public final class Main {
s3ProxyBuilder.corsAllowAll(Boolean.parseBoolean(
corsAllowAll));
}
if (s3ProxyServicePath != null) {
s3ProxyBuilder.servicePath(s3ProxyServicePath);
}
s3Proxy = s3ProxyBuilder.build();
} catch (IllegalArgumentException | IllegalStateException e) {
System.err.println(e.getMessage());

Wyświetl plik

@ -28,6 +28,7 @@ import com.google.common.base.Strings;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.jclouds.blobstore.BlobStore;
@ -74,6 +75,12 @@ public final class S3Proxy {
"Must provide both identity and credential");
server = new Server();
if (builder.servicePath != null && !builder.servicePath.isEmpty()) {
ContextHandler context = new ContextHandler();
context.setContextPath(builder.servicePath);
}
((QueuedThreadPool) server.getThreadPool()).setName("S3Proxy");
HttpConnectionFactory httpConnectionFactory =
new HttpConnectionFactory();
@ -106,7 +113,7 @@ public final class S3Proxy {
builder.credential, Optional.fromNullable(builder.virtualHost),
builder.v4MaxNonChunkedRequestSize,
builder.ignoreUnknownHeaders, builder.ignoreUnknownParameters,
builder.corsAllowAll);
builder.corsAllowAll, builder.servicePath);
server.setHandler(handler);
}
@ -121,6 +128,7 @@ public final class S3Proxy {
private String keyStorePath;
private String keyStorePassword;
private String virtualHost;
private String servicePath;
private long v4MaxNonChunkedRequestSize = 32 * 1024 * 1024;
private boolean ignoreUnknownHeaders;
private boolean ignoreUnknownParameters;
@ -193,6 +201,18 @@ public final class S3Proxy {
this.corsAllowAll = corsAllowAll;
return this;
}
public void servicePath(String s3ProxyServicePath) {
String path = Strings.nullToEmpty(s3ProxyServicePath);
if (!path.isEmpty()) {
if (!path.startsWith("/")) {
path = "/" + path;
}
}
this.servicePath = path;
}
}
public static Builder builder() {

Wyświetl plik

@ -25,6 +25,8 @@ public final class S3ProxyConstants {
"s3proxy.authorization";
public static final String PROPERTY_IDENTITY =
"s3proxy.identity";
public static final String PROPERTY_SERVICE_PATH =
"s3proxy.service-path";
/** When true, include "Access-Control-Allow-Origin: *" in all responses. */
public static final String PROPERTY_CORS_ALLOW_ALL =
"s3proxy.cors-allow-all";

Wyświetl plik

@ -219,6 +219,7 @@ public class S3ProxyHandler {
private final boolean ignoreUnknownHeaders;
private final boolean ignoreUnknownParameters;
private final boolean corsAllowAll;
private final String servicePath;
private final XMLOutputFactory xmlOutputFactory =
XMLOutputFactory.newInstance();
private BlobStoreLocator blobStoreLocator;
@ -240,7 +241,8 @@ public class S3ProxyHandler {
AuthenticationType authenticationType, final String identity,
final String credential, Optional<String> virtualHost,
long v4MaxNonChunkedRequestSize, boolean ignoreUnknownHeaders,
boolean ignoreUnknownParameters, boolean corsAllowAll) {
boolean ignoreUnknownParameters, boolean corsAllowAll,
final String servicePath) {
if (authenticationType != AuthenticationType.NONE) {
anonymousIdentity = false;
blobStoreLocator = new BlobStoreLocator() {
@ -274,6 +276,7 @@ public class S3ProxyHandler {
this.defaultBlobStore = blobStore;
xmlOutputFactory.setProperty("javax.xml.stream.isRepairingNamespaces",
Boolean.FALSE);
this.servicePath = Strings.nullToEmpty(servicePath);
}
private static String getBlobStoreType(BlobStore blobStore) {
@ -285,6 +288,13 @@ public class S3ProxyHandler {
InputStream is) throws IOException, S3Exception {
String method = request.getMethod();
String uri = request.getRequestURI();
if (!this.servicePath.isEmpty()) {
if (uri.length() > this.servicePath.length()) {
uri = uri.substring(this.servicePath.length());
}
}
logger.debug("request: {}", request);
String hostHeader = request.getHeader(HttpHeaders.HOST);
if (hostHeader != null && virtualHost.isPresent()) {
@ -359,6 +369,7 @@ public class S3ProxyHandler {
String headerAuthorization = request.getHeader(
HttpHeaders.AUTHORIZATION);
S3AuthorizationHeader authHeader = null;
boolean presignedUrl = false;
if (!anonymousIdentity) {
if (headerAuthorization == null) {
@ -368,6 +379,7 @@ public class S3ProxyHandler {
throw new S3Exception(S3ErrorCode.ACCESS_DENIED);
}
headerAuthorization = "AWS " + identity + ":" + signature;
presignedUrl = true;
}
try {
@ -441,9 +453,12 @@ public class S3ProxyHandler {
}
String expectedSignature = null;
// When presigned url is generated, it doesn't consider service path
String uriForSigning = presignedUrl ? uri : this.servicePath + uri;
if (authHeader.hmacAlgorithm == null) {
expectedSignature = createAuthorizationSignature(request,
uri, credential);
uriForSigning, credential);
} else {
String contentSha256 = request.getHeader(
"x-amz-content-sha256");
@ -466,13 +481,14 @@ public class S3ProxyHandler {
is = new ByteArrayInputStream(payload);
}
expectedSignature = createAuthorizationSignatureV4(
baseRequest, payload, uri, credential);
baseRequest, payload, uriForSigning, credential);
} catch (InvalidKeyException | NoSuchAlgorithmException e) {
throw new S3Exception(S3ErrorCode.INVALID_ARGUMENT);
}
}
if (!expectedSignature.equals(authHeader.signature)) {
logger.debug("fail to validate signature");
throw new S3Exception(S3ErrorCode.SIGNATURE_DOES_NOT_MATCH);
}
}

Wyświetl plik

@ -44,10 +44,12 @@ final class S3ProxyHandlerJetty extends AbstractHandler {
AuthenticationType authenticationType, final String identity,
final String credential, Optional<String> virtualHost,
long v4MaxNonChunkedRequestSize, boolean ignoreUnknownHeaders,
boolean ignoreUnknownParameters, boolean corsAllowAll) {
boolean ignoreUnknownParameters, boolean corsAllowAll,
String servicePath) {
handler = new S3ProxyHandler(blobStore, authenticationType, identity,
credential, virtualHost, v4MaxNonChunkedRequestSize,
ignoreUnknownHeaders, ignoreUnknownParameters, corsAllowAll);
ignoreUnknownHeaders, ignoreUnknownParameters, corsAllowAll,
servicePath);
}
@Override

Wyświetl plik

@ -114,6 +114,7 @@ public final class AwsSdkTest {
private String containerName;
private BasicAWSCredentials awsCreds;
private AmazonS3 client;
private String servicePath;
@Before
public void setUp() throws Exception {
@ -123,8 +124,9 @@ public final class AwsSdkTest {
context = info.getBlobStore().getContext();
s3Proxy = info.getS3Proxy();
s3Endpoint = info.getSecureEndpoint();
servicePath = info.getServicePath();
s3EndpointConfig = new EndpointConfiguration(
s3Endpoint.toString(), "us-east-1");
s3Endpoint.toString() + servicePath, "us-east-1");
client = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(awsCreds))
.withEndpointConfiguration(s3EndpointConfig)
@ -702,7 +704,7 @@ public final class AwsSdkTest {
HttpClient httpClient = context.utils().http();
URI uri = new URI(s3Endpoint.getScheme(), s3Endpoint.getUserInfo(),
s3Endpoint.getHost(), s3Proxy.getSecurePort(),
"/" + containerName + "/" + blobName,
servicePath + "/" + containerName + "/" + blobName,
/*query=*/ null, /*fragment=*/ null);
try (InputStream actual = httpClient.get(uri);
InputStream expected = BYTE_SOURCE.openStream()) {

Wyświetl plik

@ -30,6 +30,7 @@ import org.assertj.core.api.Fail;
import org.jclouds.Constants;
import org.jclouds.aws.AWSResponseException;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.s3.reference.S3Constants;
import org.jclouds.s3.services.BucketsLiveTest;
import org.testng.SkipException;
import org.testng.annotations.AfterClass;
@ -74,9 +75,11 @@ public final class JcloudsBucketsLiveTest extends BucketsLiveTest {
props.setProperty(Constants.PROPERTY_CREDENTIAL,
info.getS3Credential());
props.setProperty(Constants.PROPERTY_ENDPOINT,
info.getEndpoint().toString());
info.getEndpoint().toString() + info.getServicePath());
props.setProperty(Constants.PROPERTY_STRIP_EXPECT_HEADER, "true");
endpoint = info.getEndpoint().toString();
props.setProperty(S3Constants.PROPERTY_S3_SERVICE_PATH,
info.getServicePath());
endpoint = info.getEndpoint().toString() + info.getServicePath();
return props;
}

Wyświetl plik

@ -25,6 +25,7 @@ import org.jclouds.Constants;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.s3.blobstore.integration.S3BlobIntegrationLiveTest;
import org.jclouds.s3.reference.S3Constants;
import org.testng.SkipException;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
@ -69,7 +70,9 @@ public final class JcloudsS3BlobIntegrationLiveTest
props.setProperty(Constants.PROPERTY_CREDENTIAL,
info.getS3Credential());
props.setProperty(Constants.PROPERTY_ENDPOINT,
info.getEndpoint().toString());
info.getEndpoint().toString() + info.getServicePath());
props.setProperty(S3Constants.PROPERTY_S3_SERVICE_PATH,
info.getServicePath());
props.setProperty(Constants.PROPERTY_STRIP_EXPECT_HEADER, "true");
return props;
}

Wyświetl plik

@ -27,6 +27,7 @@ import com.google.common.util.concurrent.Uninterruptibles;
import org.jclouds.Constants;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.s3.blobstore.integration.S3BlobSignerLiveTest;
import org.jclouds.s3.reference.S3Constants;
import org.testng.SkipException;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
@ -68,7 +69,9 @@ public final class JcloudsS3BlobSignerLiveTest extends S3BlobSignerLiveTest {
props.setProperty(Constants.PROPERTY_CREDENTIAL,
info.getS3Credential());
props.setProperty(Constants.PROPERTY_ENDPOINT,
info.getEndpoint().toString());
info.getEndpoint().toString() + info.getServicePath());
props.setProperty(S3Constants.PROPERTY_S3_SERVICE_PATH,
info.getServicePath());
props.setProperty(Constants.PROPERTY_STRIP_EXPECT_HEADER, "true");
endpoint = info.getEndpoint().toString();
return props;

Wyświetl plik

@ -34,6 +34,7 @@ import org.jclouds.aws.AWSResponseException;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.s3.S3ClientLiveTest;
import org.jclouds.s3.domain.S3Object;
import org.jclouds.s3.reference.S3Constants;
import org.testng.SkipException;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
@ -77,9 +78,11 @@ public final class JcloudsS3ClientLiveTest extends S3ClientLiveTest {
props.setProperty(Constants.PROPERTY_CREDENTIAL,
info.getS3Credential());
props.setProperty(Constants.PROPERTY_ENDPOINT,
info.getEndpoint().toString());
info.getEndpoint().toString() + info.getServicePath());
props.setProperty(Constants.PROPERTY_STRIP_EXPECT_HEADER, "true");
endpoint = info.getEndpoint().toString();
props.setProperty(S3Constants.PROPERTY_S3_SERVICE_PATH,
info.getServicePath());
endpoint = info.getEndpoint().toString() + info.getServicePath();
return props;
}

Wyświetl plik

@ -24,6 +24,7 @@ import com.google.common.util.concurrent.Uninterruptibles;
import org.jclouds.Constants;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.s3.blobstore.integration.S3ContainerIntegrationLiveTest;
import org.jclouds.s3.reference.S3Constants;
import org.testng.SkipException;
import org.testng.annotations.AfterClass;
import org.testng.annotations.Test;
@ -68,7 +69,9 @@ public final class JcloudsS3ContainerIntegrationLiveTest
props.setProperty(Constants.PROPERTY_CREDENTIAL,
info.getS3Credential());
props.setProperty(Constants.PROPERTY_ENDPOINT,
info.getEndpoint().toString());
info.getEndpoint().toString() + info.getServicePath());
props.setProperty(S3Constants.PROPERTY_S3_SERVICE_PATH,
info.getServicePath());
props.setProperty(Constants.PROPERTY_STRIP_EXPECT_HEADER, "true");
return props;
}

Wyświetl plik

@ -112,6 +112,7 @@ final class TestUtils {
private BlobStore blobStore;
private URI endpoint;
private URI secureEndpoint;
private String servicePath;
S3Proxy getS3Proxy() {
return s3Proxy;
@ -129,6 +130,10 @@ final class TestUtils {
return s3Credential;
}
String getServicePath() {
return servicePath;
}
BlobStore getBlobStore() {
return blobStore;
}
@ -184,7 +189,14 @@ final class TestUtils {
S3ProxyConstants.PROPERTY_KEYSTORE_PASSWORD);
String virtualHost = info.getProperties().getProperty(
S3ProxyConstants.PROPERTY_VIRTUAL_HOST);
String servicePath = Strings.nullToEmpty(info.getProperties()
.getProperty(S3ProxyConstants.PROPERTY_SERVICE_PATH));
if (!servicePath.isEmpty()) {
if (!servicePath.startsWith("/")) {
servicePath = "/" + servicePath;
}
}
info.servicePath = servicePath;
info.getProperties().setProperty(Constants.PROPERTY_USER_AGENT,
String.format("s3proxy/%s jclouds/%s java/%s",
TestUtils.class.getPackage().getImplementationVersion(),
@ -221,6 +233,9 @@ final class TestUtils {
if (virtualHost != null) {
s3ProxyBuilder.virtualHost(virtualHost);
}
if (servicePath != null) {
s3ProxyBuilder.servicePath(servicePath);
}
info.s3Proxy = s3ProxyBuilder.build();
info.s3Proxy.start();
while (!info.s3Proxy.getState().equals(AbstractLifeCycle.STARTED)) {

Wyświetl plik

@ -1,5 +1,6 @@
s3proxy.endpoint=http://127.0.0.1:0
s3proxy.secure-endpoint=https://127.0.0.1:0
#s3proxy.service-path=s3proxy
# authorization must be aws-v2, aws-v4, aws-v2-or-v4, or none
s3proxy.authorization=aws-v2-or-v4
s3proxy.identity=local-identity