mirror of https://github.com/apache/jclouds.git
JCLOUDS-255: JCLOUDS-1109: Azure signed URLs expiration
This commit also changes signed URLs from header-based to query parameter-based.
This commit is contained in:
parent
13cf2cbab9
commit
1e04eafbab
|
@ -129,7 +129,7 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
|
||||||
.getContentType())).append("\n");
|
.getContentType())).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private String calculateSignature(String toSign) throws HttpException {
|
public String calculateSignature(String toSign) throws HttpException {
|
||||||
String signature = signString(toSign);
|
String signature = signString(toSign);
|
||||||
if (signatureWire.enabled())
|
if (signatureWire.enabled())
|
||||||
signatureWire.input(Strings2.toInputStream(signature));
|
signatureWire.input(Strings2.toInputStream(signature));
|
||||||
|
|
|
@ -50,6 +50,7 @@ public class AzureStorageUtils {
|
||||||
AzureStorageError error = factory.create(errorHandlerProvider.get()).parse(content);
|
AzureStorageError error = factory.create(errorHandlerProvider.get()).parse(content);
|
||||||
error.setRequestId(response.getFirstHeaderOrNull(AzureStorageHeaders.REQUEST_ID));
|
error.setRequestId(response.getFirstHeaderOrNull(AzureStorageHeaders.REQUEST_ID));
|
||||||
if ("AuthenticationFailed".equals(error.getCode())) {
|
if ("AuthenticationFailed".equals(error.getCode())) {
|
||||||
|
// this signature is incorrect for URLs from AzureBlobRequestSigner
|
||||||
error.setStringSigned(signer.createStringToSign(command.getCurrentRequest()));
|
error.setStringSigned(signer.createStringToSign(command.getCurrentRequest()));
|
||||||
error.setSignature(signer.signString(error.getStringSigned()));
|
error.setSignature(signer.signString(error.getStringSigned()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,70 +17,77 @@
|
||||||
package org.jclouds.azureblob.blobstore;
|
package org.jclouds.azureblob.blobstore;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static org.jclouds.blobstore.util.BlobStoreUtils.cleanRequest;
|
|
||||||
import static org.jclouds.reflect.Reflection2.method;
|
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import org.jclouds.azureblob.AzureBlobClient;
|
import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication;
|
||||||
import org.jclouds.azureblob.blobstore.functions.BlobToAzureBlob;
|
|
||||||
import org.jclouds.azureblob.domain.AzureBlob;
|
|
||||||
import org.jclouds.blobstore.BlobRequestSigner;
|
import org.jclouds.blobstore.BlobRequestSigner;
|
||||||
import org.jclouds.blobstore.domain.Blob;
|
import org.jclouds.blobstore.domain.Blob;
|
||||||
import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
|
import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
|
||||||
|
import org.jclouds.date.DateService;
|
||||||
|
import org.jclouds.date.TimeStamp;
|
||||||
|
import org.jclouds.domain.Credentials;
|
||||||
import org.jclouds.http.HttpRequest;
|
import org.jclouds.http.HttpRequest;
|
||||||
|
import org.jclouds.http.Uris;
|
||||||
import org.jclouds.http.options.GetOptions;
|
import org.jclouds.http.options.GetOptions;
|
||||||
import org.jclouds.reflect.Invocation;
|
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Supplier;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.net.HttpHeaders;
|
||||||
import com.google.common.reflect.Invokable;
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
public class AzureBlobRequestSigner implements BlobRequestSigner {
|
public class AzureBlobRequestSigner implements BlobRequestSigner {
|
||||||
private final Function<Invocation, HttpRequest> processor;
|
private static final int DEFAULT_EXPIRY_SECONDS = 15 * 60;
|
||||||
private final BlobToAzureBlob blobToBlob;
|
private static final String API_VERSION = "2016-05-31";
|
||||||
private final BlobToHttpGetOptions blob2HttpGetOptions;
|
|
||||||
|
|
||||||
private final Invokable<?, ?> getMethod;
|
private final String identity;
|
||||||
private final Invokable<?, ?> deleteMethod;
|
private final URI storageUrl;
|
||||||
private final Invokable<?, ?> createMethod;
|
private final BlobToHttpGetOptions blob2HttpGetOptions;
|
||||||
|
private final Provider<String> timeStampProvider;
|
||||||
|
private final DateService dateService;
|
||||||
|
private final SharedKeyLiteAuthentication auth;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AzureBlobRequestSigner(Function<Invocation, HttpRequest> processor, BlobToAzureBlob blobToBlob,
|
public AzureBlobRequestSigner(
|
||||||
BlobToHttpGetOptions blob2HttpGetOptions) throws SecurityException, NoSuchMethodException {
|
BlobToHttpGetOptions blob2HttpGetOptions, @TimeStamp Provider<String> timeStampProvider,
|
||||||
this.processor = checkNotNull(processor, "processor");
|
DateService dateService, SharedKeyLiteAuthentication auth,
|
||||||
this.blobToBlob = checkNotNull(blobToBlob, "blobToBlob");
|
@org.jclouds.location.Provider Supplier<Credentials> creds)
|
||||||
|
throws SecurityException, NoSuchMethodException {
|
||||||
|
this.identity = creds.get().identity;
|
||||||
|
this.storageUrl = URI.create("https://" + creds.get().identity + ".blob.core.windows.net/");
|
||||||
this.blob2HttpGetOptions = checkNotNull(blob2HttpGetOptions, "blob2HttpGetOptions");
|
this.blob2HttpGetOptions = checkNotNull(blob2HttpGetOptions, "blob2HttpGetOptions");
|
||||||
this.getMethod = method(AzureBlobClient.class, "getBlob", String.class, String.class, GetOptions[].class);
|
this.timeStampProvider = checkNotNull(timeStampProvider, "timeStampProvider");
|
||||||
this.deleteMethod = method(AzureBlobClient.class, "deleteBlob", String.class, String.class);
|
this.dateService = checkNotNull(dateService, "dateService");
|
||||||
this.createMethod = method(AzureBlobClient.class, "putBlob", String.class, AzureBlob.class);
|
this.auth = auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpRequest signGetBlob(String container, String name) {
|
public HttpRequest signGetBlob(String container, String name) {
|
||||||
checkNotNull(container, "container");
|
return signGetBlob(container, name, DEFAULT_EXPIRY_SECONDS);
|
||||||
checkNotNull(name, "name");
|
|
||||||
return cleanRequest(processor.apply(Invocation.create(getMethod, ImmutableList.<Object> of(container, name))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
|
public HttpRequest signGetBlob(String container, String name, long timeInSeconds) {
|
||||||
throw new UnsupportedOperationException();
|
checkNotNull(container, "container");
|
||||||
|
checkNotNull(name, "name");
|
||||||
|
return sign("GET", container, name, null, timeInSeconds, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpRequest signPutBlob(String container, Blob blob) {
|
public HttpRequest signPutBlob(String container, Blob blob) {
|
||||||
checkNotNull(container, "container");
|
return signPutBlob(container, blob, DEFAULT_EXPIRY_SECONDS);
|
||||||
checkNotNull(blob, "blob");
|
|
||||||
return cleanRequest(processor.apply(Invocation.create(createMethod,
|
|
||||||
ImmutableList.<Object> of(container, blobToBlob.apply(blob)))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) {
|
public HttpRequest signPutBlob(String container, Blob blob, long timeInSeconds) {
|
||||||
throw new UnsupportedOperationException();
|
checkNotNull(container, "container");
|
||||||
|
checkNotNull(blob, "blob");
|
||||||
|
return sign("PUT", container, blob.getMetadata().getName(), null, timeInSeconds,
|
||||||
|
blob.getMetadata().getContentMetadata().getContentLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
@ -88,14 +95,73 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
|
||||||
public HttpRequest signRemoveBlob(String container, String name) {
|
public HttpRequest signRemoveBlob(String container, String name) {
|
||||||
checkNotNull(container, "container");
|
checkNotNull(container, "container");
|
||||||
checkNotNull(name, "name");
|
checkNotNull(name, "name");
|
||||||
return cleanRequest(processor.apply(Invocation.create(deleteMethod, ImmutableList.<Object> of(container, name))));
|
return sign("DELETE", container, name, null, DEFAULT_EXPIRY_SECONDS, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpRequest signGetBlob(String container, String name, org.jclouds.blobstore.options.GetOptions options) {
|
public HttpRequest signGetBlob(String container, String name, org.jclouds.blobstore.options.GetOptions options) {
|
||||||
checkNotNull(container, "container");
|
checkNotNull(container, "container");
|
||||||
checkNotNull(name, "name");
|
checkNotNull(name, "name");
|
||||||
return cleanRequest(processor.apply(Invocation.create(getMethod,
|
return sign("GET", container, name, blob2HttpGetOptions.apply(checkNotNull(options, "options")),
|
||||||
ImmutableList.of(container, name, blob2HttpGetOptions.apply(checkNotNull(options, "options"))))));
|
DEFAULT_EXPIRY_SECONDS, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpRequest sign(String method, String container, String name, GetOptions options, long expires, Long contentLength) {
|
||||||
|
checkNotNull(method, "method");
|
||||||
|
checkNotNull(container, "container");
|
||||||
|
checkNotNull(name, "name");
|
||||||
|
|
||||||
|
String nowString = timeStampProvider.get();
|
||||||
|
Date now = dateService.rfc1123DateParse(nowString);
|
||||||
|
Date expiration = new Date(now.getTime() + TimeUnit.SECONDS.toMillis(expires));
|
||||||
|
String iso8601 = dateService.iso8601SecondsDateFormat(expiration);
|
||||||
|
String signedPermission;
|
||||||
|
if (method.equals("PUT")) {
|
||||||
|
signedPermission = "w";
|
||||||
|
} else if (method.equals("DELETE")) {
|
||||||
|
signedPermission = "d";
|
||||||
|
} else {
|
||||||
|
signedPermission = "r";
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpRequest.Builder request = HttpRequest.builder()
|
||||||
|
.method(method)
|
||||||
|
.endpoint(Uris.uriBuilder(storageUrl).appendPath(container).appendPath(name).build())
|
||||||
|
.replaceHeader(HttpHeaders.DATE, nowString)
|
||||||
|
.addQueryParam("sv", API_VERSION)
|
||||||
|
.addQueryParam("se", iso8601)
|
||||||
|
.addQueryParam("sr", "b") // blob resource
|
||||||
|
.addQueryParam("sp", signedPermission); // permission
|
||||||
|
|
||||||
|
if (contentLength != null) {
|
||||||
|
request.replaceHeader(HttpHeaders.CONTENT_LENGTH, contentLength.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options != null) {
|
||||||
|
request.headers(options.buildRequestHeaders());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method.equals("PUT")) {
|
||||||
|
request.replaceHeader("x-ms-blob-type", "BlockBlob");
|
||||||
|
}
|
||||||
|
|
||||||
|
String stringToSign =
|
||||||
|
signedPermission + "\n" + // signedpermission
|
||||||
|
"\n" + // signedstart
|
||||||
|
iso8601 + "\n" + // signedexpiry
|
||||||
|
"/blob/" + identity + "/" + container + "/" + name + "\n" + // canonicalizedresource
|
||||||
|
"\n" + // signedidentifier
|
||||||
|
"\n" + // signedIP
|
||||||
|
"\n" + // signedProtocol
|
||||||
|
API_VERSION + "\n" + // signedversion
|
||||||
|
"\n" + // rscc
|
||||||
|
"\n" + // rscd
|
||||||
|
"\n" + // rsce
|
||||||
|
"\n" + // rscl
|
||||||
|
""; // rsct
|
||||||
|
|
||||||
|
String signature = auth.calculateSignature(stringToSign);
|
||||||
|
request.addQueryParam("sig", signature);
|
||||||
|
return request.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.jclouds.azureblob.blobstore.integration;
|
package org.jclouds.azureblob.blobstore.integration;
|
||||||
|
|
||||||
import static org.testng.Assert.fail;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.jclouds.blobstore.integration.internal.BaseBlobSignerLiveTest;
|
import org.jclouds.blobstore.integration.internal.BaseBlobSignerLiveTest;
|
||||||
import org.testng.SkipException;
|
|
||||||
import org.testng.annotations.Test;
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
@Test(groups = { "live" })
|
@Test(groups = { "live" })
|
||||||
|
@ -29,44 +24,4 @@ public class AzureBlobSignerLiveTest extends BaseBlobSignerLiveTest {
|
||||||
public AzureBlobSignerLiveTest() {
|
public AzureBlobSignerLiveTest() {
|
||||||
provider = "azureblob";
|
provider = "azureblob";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSignGetUrlWithTime() throws InterruptedException, IOException {
|
|
||||||
try {
|
|
||||||
super.testSignGetUrlWithTime();
|
|
||||||
fail();
|
|
||||||
} catch (UnsupportedOperationException uoe) {
|
|
||||||
throw new SkipException("not supported in Azure", uoe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSignGetUrlWithTimeExpired() throws InterruptedException, IOException {
|
|
||||||
try {
|
|
||||||
super.testSignGetUrlWithTimeExpired();
|
|
||||||
fail();
|
|
||||||
} catch (UnsupportedOperationException uoe) {
|
|
||||||
throw new SkipException("not supported in Azure", uoe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSignPutUrlWithTime() throws Exception {
|
|
||||||
try {
|
|
||||||
super.testSignPutUrlWithTime();
|
|
||||||
fail();
|
|
||||||
} catch (UnsupportedOperationException uoe) {
|
|
||||||
throw new SkipException("not supported in Azure", uoe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSignPutUrlWithTimeExpired() throws Exception {
|
|
||||||
try {
|
|
||||||
super.testSignPutUrlWithTimeExpired();
|
|
||||||
fail();
|
|
||||||
} catch (UnsupportedOperationException uoe) {
|
|
||||||
throw new SkipException("not supported in Azure", uoe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue