kopia lustrzana https://github.com/gaul/s3proxy
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
rodzic
8224f6fe6e
commit
8b2d056d40
|
@ -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());
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue