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:
Andrew Gaul 2016-01-21 20:12:02 -08:00
parent 13cf2cbab9
commit 1e04eafbab
4 changed files with 102 additions and 80 deletions

View File

@ -129,7 +129,7 @@ public class SharedKeyLiteAuthentication implements HttpRequestFilter {
.getContentType())).append("\n");
}
private String calculateSignature(String toSign) throws HttpException {
public String calculateSignature(String toSign) throws HttpException {
String signature = signString(toSign);
if (signatureWire.enabled())
signatureWire.input(Strings2.toInputStream(signature));

View File

@ -50,6 +50,7 @@ public class AzureStorageUtils {
AzureStorageError error = factory.create(errorHandlerProvider.get()).parse(content);
error.setRequestId(response.getFirstHeaderOrNull(AzureStorageHeaders.REQUEST_ID));
if ("AuthenticationFailed".equals(error.getCode())) {
// this signature is incorrect for URLs from AzureBlobRequestSigner
error.setStringSigned(signer.createStringToSign(command.getCurrentRequest()));
error.setSignature(signer.signString(error.getStringSigned()));
}

View File

@ -17,70 +17,77 @@
package org.jclouds.azureblob.blobstore;
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.Singleton;
import org.jclouds.azureblob.AzureBlobClient;
import org.jclouds.azureblob.blobstore.functions.BlobToAzureBlob;
import org.jclouds.azureblob.domain.AzureBlob;
import org.jclouds.azure.storage.filters.SharedKeyLiteAuthentication;
import org.jclouds.blobstore.BlobRequestSigner;
import org.jclouds.blobstore.domain.Blob;
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.Uris;
import org.jclouds.http.options.GetOptions;
import org.jclouds.reflect.Invocation;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.reflect.Invokable;
import com.google.common.base.Supplier;
import com.google.common.net.HttpHeaders;
import com.google.inject.Provider;
@Singleton
public class AzureBlobRequestSigner implements BlobRequestSigner {
private final Function<Invocation, HttpRequest> processor;
private final BlobToAzureBlob blobToBlob;
private final BlobToHttpGetOptions blob2HttpGetOptions;
private static final int DEFAULT_EXPIRY_SECONDS = 15 * 60;
private static final String API_VERSION = "2016-05-31";
private final Invokable<?, ?> getMethod;
private final Invokable<?, ?> deleteMethod;
private final Invokable<?, ?> createMethod;
private final String identity;
private final URI storageUrl;
private final BlobToHttpGetOptions blob2HttpGetOptions;
private final Provider<String> timeStampProvider;
private final DateService dateService;
private final SharedKeyLiteAuthentication auth;
@Inject
public AzureBlobRequestSigner(Function<Invocation, HttpRequest> processor, BlobToAzureBlob blobToBlob,
BlobToHttpGetOptions blob2HttpGetOptions) throws SecurityException, NoSuchMethodException {
this.processor = checkNotNull(processor, "processor");
this.blobToBlob = checkNotNull(blobToBlob, "blobToBlob");
public AzureBlobRequestSigner(
BlobToHttpGetOptions blob2HttpGetOptions, @TimeStamp Provider<String> timeStampProvider,
DateService dateService, SharedKeyLiteAuthentication auth,
@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.getMethod = method(AzureBlobClient.class, "getBlob", String.class, String.class, GetOptions[].class);
this.deleteMethod = method(AzureBlobClient.class, "deleteBlob", String.class, String.class);
this.createMethod = method(AzureBlobClient.class, "putBlob", String.class, AzureBlob.class);
this.timeStampProvider = checkNotNull(timeStampProvider, "timeStampProvider");
this.dateService = checkNotNull(dateService, "dateService");
this.auth = auth;
}
@Override
public HttpRequest signGetBlob(String container, String name) {
checkNotNull(container, "container");
checkNotNull(name, "name");
return cleanRequest(processor.apply(Invocation.create(getMethod, ImmutableList.<Object> of(container, name))));
return signGetBlob(container, name, DEFAULT_EXPIRY_SECONDS);
}
@Override
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
public HttpRequest signPutBlob(String container, Blob blob) {
checkNotNull(container, "container");
checkNotNull(blob, "blob");
return cleanRequest(processor.apply(Invocation.create(createMethod,
ImmutableList.<Object> of(container, blobToBlob.apply(blob)))));
return signPutBlob(container, blob, DEFAULT_EXPIRY_SECONDS);
}
@Override
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
@ -88,14 +95,73 @@ public class AzureBlobRequestSigner implements BlobRequestSigner {
public HttpRequest signRemoveBlob(String container, String name) {
checkNotNull(container, "container");
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
public HttpRequest signGetBlob(String container, String name, org.jclouds.blobstore.options.GetOptions options) {
checkNotNull(container, "container");
checkNotNull(name, "name");
return cleanRequest(processor.apply(Invocation.create(getMethod,
ImmutableList.of(container, name, blob2HttpGetOptions.apply(checkNotNull(options, "options"))))));
return sign("GET", 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();
}
}

View File

@ -16,12 +16,7 @@
*/
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.testng.SkipException;
import org.testng.annotations.Test;
@Test(groups = { "live" })
@ -29,44 +24,4 @@ public class AzureBlobSignerLiveTest extends BaseBlobSignerLiveTest {
public AzureBlobSignerLiveTest() {
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);
}
}
}