Repository: jclouds-labs-aws Updated Branches: refs/heads/master ed6d635e9 -> ca269db8b
JCLOUDS-457: Created the skeleton of the Glacier API. Project: http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/commit/ca269db8 Tree: http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/tree/ca269db8 Diff: http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/diff/ca269db8 Branch: refs/heads/master Commit: ca269db8b3d6ff93e61ac9c7410c3c2d99406de2 Parents: ed6d635 Author: Roman C. Coedo <[email protected]> Authored: Tue Apr 22 12:15:03 2014 +0200 Committer: Andrew Gaul <[email protected]> Committed: Thu May 15 02:32:27 2014 -0700 ---------------------------------------------------------------------- glacier/pom.xml | 128 +++++++++++++ .../org/jclouds/glacier/GlacierApiMetadata.java | 98 ++++++++++ .../org/jclouds/glacier/GlacierAsyncClient.java | 46 +++++ .../java/org/jclouds/glacier/GlacierClient.java | 25 +++ .../glacier/config/GlacierRestClientModule.java | 64 +++++++ .../filters/RequestAuthorizeSignature.java | 73 +++++++ .../glacier/reference/GlacierHeaders.java | 28 +++ .../glacier/util/AWSRequestSignerV4.java | 189 +++++++++++++++++++ .../services/org.jclouds.apis.ApiMetadata | 1 + .../jclouds/glacier/GlacierApiMetadataTest.java | 33 ++++ .../jclouds/glacier/GlacierClientMockTest.java | 77 ++++++++ .../glacier/util/AWSRequestSignerV4Test.java | 56 ++++++ .../services/org.jclouds.apis.ApiMetadata | 1 + pom.xml | 1 + 14 files changed, 820 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/pom.xml ---------------------------------------------------------------------- diff --git a/glacier/pom.xml b/glacier/pom.xml new file mode 100644 index 0000000..5ac9979 --- /dev/null +++ b/glacier/pom.xml @@ -0,0 +1,128 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.jclouds.labs</groupId> + <artifactId>jclouds-labs-aws</artifactId> + <version>1.8.0-SNAPSHOT</version> + </parent> + + <groupId>org.apache.jclouds.labs</groupId> + <artifactId>glacier</artifactId> + <name>jclouds glacier api</name> + <description>jclouds components to access an implementation of glacier</description> + <packaging>bundle</packaging> + + <properties> + <test.glacier.endpoint>https://glacier.eu-west-1.amazonaws.com</test.glacier.endpoint> + <test.glacier.api-version>2012-06-01</test.glacier.api-version> + <test.glacier.build-version /> + <test.glacier.identity>${test.aws.identity}</test.glacier.identity> + <test.glacier.credential>${test.aws.credential}</test.glacier.credential> + + <jclouds.osgi.export>org.jclouds.glacier*;version="${project.version}"</jclouds.osgi.export> + <jclouds.osgi.import> + org.jclouds.labs*;version="${project.version}", + org.jclouds*;version="${project.version}", + * + </jclouds.osgi.import> + </properties> + + <dependencies> + <dependency> + <groupId>org.apache.jclouds.api</groupId> + <artifactId>sts</artifactId> + <version>${project.version}</version> + <type>jar</type> + </dependency> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-blobstore</artifactId> + <version>${project.version}</version> + <type>jar</type> + </dependency> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-core</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds</groupId> + <artifactId>jclouds-blobstore</artifactId> + <version>${project.version}</version> + <type>test-jar</type> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.jclouds.driver</groupId> + <artifactId>jclouds-log4j</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.google.mockwebserver</groupId> + <artifactId>mockwebserver</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>live</id> + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-surefire-plugin</artifactId> + <executions> + <execution> + <id>integration</id> + <phase>integration-test</phase> + <goals> + <goal>test</goal> + </goals> + <configuration> + <systemPropertyVariables> + <jclouds.blobstore.httpstream.url>${jclouds.blobstore.httpstream.url}</jclouds.blobstore.httpstream.url> + <jclouds.blobstore.httpstream.md5>${jclouds.blobstore.httpstream.md5}</jclouds.blobstore.httpstream.md5> + <test.glacier.endpoint>${test.glacier.endpoint}</test.glacier.endpoint> + <test.glacier.api-version>${test.glacier.api-version}</test.glacier.api-version> + <test.glacier.build-version>${test.glacier.build-version}</test.glacier.build-version> + <test.glacier.identity>${test.glacier.identity}</test.glacier.identity> + <test.glacier.credential>${test.glacier.credential}</test.glacier.credential> + </systemPropertyVariables> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> + +</project> http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java ---------------------------------------------------------------------- diff --git a/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java b/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java new file mode 100644 index 0000000..c547e21 --- /dev/null +++ b/glacier/src/main/java/org/jclouds/glacier/GlacierApiMetadata.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.glacier; + +import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; +import static org.jclouds.reflect.Reflection2.typeToken; + +import java.net.URI; +import java.util.Properties; + +import org.jclouds.blobstore.BlobStoreContext; +import org.jclouds.glacier.config.GlacierRestClientModule; +import org.jclouds.glacier.reference.GlacierHeaders; +import org.jclouds.rest.internal.BaseRestApiMetadata; + +import com.google.common.collect.ImmutableSet; +import com.google.common.reflect.TypeToken; +import com.google.inject.Module; + +/** + * Implementation of ApiMetadata for Amazon Glacier API + * + * @author Roman Coedo + */ +public class GlacierApiMetadata extends BaseRestApiMetadata { + + @Deprecated + public static final TypeToken<org.jclouds.rest.RestContext<GlacierClient, GlacierAsyncClient>> CONTEXT_TOKEN = new TypeToken<org.jclouds.rest.RestContext<GlacierClient, GlacierAsyncClient>>() { + + private static final long serialVersionUID = 1L; + }; + + private static Builder builder() { + return new Builder(); + } + + @Override + public Builder toBuilder() { + return builder().fromApiMetadata(this); + } + + public GlacierApiMetadata() { + this(builder()); + } + + protected GlacierApiMetadata(Builder builder) { + super(new Builder()); + } + + public static Properties defaultProperties() { + Properties properties = BaseRestApiMetadata.defaultProperties(); + properties.setProperty(PROPERTY_HEADER_TAG, GlacierHeaders.DEFAULT_AMAZON_HEADERTAG); + return properties; + } + + public static class Builder extends BaseRestApiMetadata.Builder<Builder> { + + @SuppressWarnings("deprecation") + protected Builder() { + super(GlacierClient.class, GlacierAsyncClient.class); + id("glacier") + .name("Amazon Glacier API") + .identityName("Access Key ID") + .credentialName("Secret Access Key") + .defaultEndpoint("https://glacier.us-east-1.amazonaws.com") + .documentation(URI.create("http://docs.aws.amazon.com/amazonglacier/latest/dev/amazon-glacier-api.html")) + .version("2012-06-01") + .defaultProperties(GlacierApiMetadata.defaultProperties()) + .context(CONTEXT_TOKEN) + .view(typeToken(BlobStoreContext.class)) + .defaultModules(ImmutableSet.<Class<? extends Module>> of(GlacierRestClientModule.class)); + } + + @Override + public GlacierApiMetadata build() { + return new GlacierApiMetadata(this); + } + + @Override + protected Builder self() { + return this; + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java ---------------------------------------------------------------------- diff --git a/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java b/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java new file mode 100644 index 0000000..7f66811 --- /dev/null +++ b/glacier/src/main/java/org/jclouds/glacier/GlacierAsyncClient.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.glacier; + +import static org.jclouds.blobstore.attr.BlobScopes.CONTAINER; + +import java.io.Closeable; +import java.net.URI; + +import javax.inject.Named; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; + +import org.jclouds.blobstore.attr.BlobScope; +import org.jclouds.glacier.filters.RequestAuthorizeSignature; +import org.jclouds.glacier.reference.GlacierHeaders; +import org.jclouds.rest.annotations.Headers; +import org.jclouds.rest.annotations.RequestFilters; + +import com.google.common.util.concurrent.ListenableFuture; + +@Headers(keys = GlacierHeaders.VERSION, values = "2012-06-01") +@RequestFilters(RequestAuthorizeSignature.class) +@BlobScope(CONTAINER) +public interface GlacierAsyncClient extends Closeable { + + @Named("CreateVault") + @PUT + @Path("/-/vaults/{vault}") + ListenableFuture<URI> createVault(@PathParam("vault") String vaultName); +} http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java ---------------------------------------------------------------------- diff --git a/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java b/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java new file mode 100644 index 0000000..3d392b9 --- /dev/null +++ b/glacier/src/main/java/org/jclouds/glacier/GlacierClient.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.glacier; + +import java.io.Closeable; +import java.net.URI; + +public interface GlacierClient extends Closeable { + + URI createVault(String vaultName); +} http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java ---------------------------------------------------------------------- diff --git a/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java b/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java new file mode 100644 index 0000000..7631ce9 --- /dev/null +++ b/glacier/src/main/java/org/jclouds/glacier/config/GlacierRestClientModule.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.glacier.config; + +import java.util.concurrent.TimeUnit; + +import javax.inject.Named; + +import org.jclouds.Constants; +import org.jclouds.date.DateService; +import org.jclouds.date.TimeStamp; +import org.jclouds.glacier.GlacierAsyncClient; +import org.jclouds.glacier.GlacierClient; +import org.jclouds.glacier.filters.RequestAuthorizeSignature; +import org.jclouds.rest.ConfiguresRestClient; +import org.jclouds.rest.config.RestClientModule; + +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.inject.Provides; +import com.google.inject.Scopes; + +@ConfiguresRestClient +public class GlacierRestClientModule extends RestClientModule<GlacierClient, GlacierAsyncClient> { + + @Override + protected void configure() { + super.configure(); + bind(RequestAuthorizeSignature.class).in(Scopes.SINGLETON); + } + + @Provides + @TimeStamp + protected String provideTimeStamp(@TimeStamp Supplier<String> cache) { + return cache.get(); + } + + @Provides + @TimeStamp + Supplier<String> provideTimeStampCache(@Named(Constants.PROPERTY_SESSION_INTERVAL) long seconds, + final DateService dateService) { + return Suppliers.memoizeWithExpiration(new Supplier<String>() { + + @Override + public String get() { + return dateService.iso8601SecondsDateFormat().replaceAll("[-:]", ""); + } + }, seconds, TimeUnit.SECONDS); + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java ---------------------------------------------------------------------- diff --git a/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java b/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java new file mode 100644 index 0000000..8f46a0f --- /dev/null +++ b/glacier/src/main/java/org/jclouds/glacier/filters/RequestAuthorizeSignature.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.glacier.filters; + +import static com.google.common.base.Preconditions.checkNotNull; + +import javax.annotation.Resource; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +import org.jclouds.Constants; +import org.jclouds.crypto.Crypto; +import org.jclouds.date.TimeStamp; +import org.jclouds.domain.Credentials; +import org.jclouds.glacier.reference.GlacierHeaders; +import org.jclouds.glacier.util.AWSRequestSignerV4; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; +import org.jclouds.http.HttpRequestFilter; +import org.jclouds.http.HttpUtils; +import org.jclouds.logging.Logger; + +import com.google.common.base.Supplier; +import com.google.common.net.HttpHeaders; + +@Singleton +public class RequestAuthorizeSignature implements HttpRequestFilter { + + private final AWSRequestSignerV4 signer; + + @Resource + @Named(Constants.LOGGER_SIGNATURE) + Logger signatureLog = Logger.NULL; + + private final Provider<String> timeStampProvider; + private final HttpUtils utils; + + @Inject + public RequestAuthorizeSignature(@TimeStamp Provider<String> timeStampProvider, + @org.jclouds.location.Provider Supplier<Credentials> creds, Crypto crypto, HttpUtils utils) { + checkNotNull(creds); + this.signer = new AWSRequestSignerV4(creds.get().identity, creds.get().credential, checkNotNull(crypto)); + this.timeStampProvider = checkNotNull(timeStampProvider); + this.utils = checkNotNull(utils); + } + + @Override + public HttpRequest filter(HttpRequest request) throws HttpException { + request = request.toBuilder().removeHeader(HttpHeaders.DATE) + .replaceHeader(GlacierHeaders.ALTERNATE_DATE, timeStampProvider.get()) + .replaceHeader(HttpHeaders.HOST, request.getEndpoint().getHost()).build(); + utils.logRequest(signatureLog, request, ">>"); + request = this.signer.sign(request); + utils.logRequest(signatureLog, request, "<<"); + return request; + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java ---------------------------------------------------------------------- diff --git a/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java b/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java new file mode 100644 index 0000000..51a0afe --- /dev/null +++ b/glacier/src/main/java/org/jclouds/glacier/reference/GlacierHeaders.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.glacier.reference; + +public final class GlacierHeaders { + + public static final String DEFAULT_AMAZON_HEADERTAG = "amz"; + public static final String HEADER_PREFIX = "x-" + DEFAULT_AMAZON_HEADERTAG + "-"; + public static final String VERSION = HEADER_PREFIX + "glacier-version"; + public static final String ALTERNATE_DATE = HEADER_PREFIX + "date"; + + private GlacierHeaders() { + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/java/org/jclouds/glacier/util/AWSRequestSignerV4.java ---------------------------------------------------------------------- diff --git a/glacier/src/main/java/org/jclouds/glacier/util/AWSRequestSignerV4.java b/glacier/src/main/java/org/jclouds/glacier/util/AWSRequestSignerV4.java new file mode 100644 index 0000000..e893b03 --- /dev/null +++ b/glacier/src/main/java/org/jclouds/glacier/util/AWSRequestSignerV4.java @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.glacier.util; + +import static com.google.common.base.Charsets.UTF_8; +import static com.google.common.base.Preconditions.checkNotNull; + +import java.io.IOException; +import java.util.Locale; +import java.util.Map.Entry; + +import javax.crypto.Mac; + +import org.jclouds.crypto.Crypto; +import org.jclouds.glacier.reference.GlacierHeaders; +import org.jclouds.http.HttpException; +import org.jclouds.http.HttpRequest; + +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.Iterables; +import com.google.common.collect.Multimap; +import com.google.common.collect.SortedSetMultimap; +import com.google.common.collect.TreeMultimap; +import com.google.common.hash.Hashing; +import com.google.common.io.BaseEncoding; +import com.google.common.io.ByteStreams; +import com.google.common.net.HttpHeaders; + +// TODO: Query parameters, not necessary for Glacier +// TODO: Endpoint on buildCredentialScope is being read from the static string. Uncool. +/** + * Signs requests using the AWSv4 signing algorithm + * + * @see <a href="http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html" /> + * @author Roman Coedo + */ +public class AWSRequestSignerV4 { + + public static final String AUTH_TAG = "AWS4"; + public static final String HEADER_TAG = "x-amz-"; + public static final String ALGORITHM = AUTH_TAG + "-HMAC-SHA256"; + public static final String TERMINATION_STRING = "aws4_request"; + public static final String REGION = "us-east-1"; + public static final String SERVICE = "glacier"; + + private final Crypto crypto; + private final String identity; + private final String credential; + + public AWSRequestSignerV4(String identity, String credential, Crypto crypto) { + this.crypto = checkNotNull(crypto); + this.identity = checkNotNull(identity); + this.credential = checkNotNull(credential); + } + + private static String buildHashedCanonicalRequest(String method, String endpoint, String hashedPayload, + String canonicalizedHeadersString, String signedHeaders) { + return sha256((method + "\n" + endpoint + "\n" + "" + "\n" + canonicalizedHeadersString + "\n" + signedHeaders + + "\n" + hashedPayload).getBytes()); + } + + private static String createStringToSign(String date, String credentialScope, String hashedCanonicalRequest) { + return ALGORITHM + "\n" + date + "\n" + credentialScope + "\n" + hashedCanonicalRequest; + } + + private static String formatDateWithoutTimestamp(String date) { + return date.substring(0, 8); + } + + private static String buildCredentialScope(String dateWithoutTimeStamp) { + return dateWithoutTimeStamp + "/" + REGION + "/" + SERVICE + "/" + TERMINATION_STRING; + } + + private static Multimap<String, String> buildCanonicalizedHeadersMap(HttpRequest request) { + Multimap<String, String> headers = request.getHeaders(); + SortedSetMultimap<String, String> canonicalizedHeaders = TreeMultimap.create(); + for (Entry<String, String> header : headers.entries()) { + if (header.getKey() == null) + continue; + String key = header.getKey().toString().toLowerCase(Locale.getDefault()); + // Ignore any headers that are not particularly interesting. + if (key.equalsIgnoreCase(HttpHeaders.CONTENT_TYPE) || key.equalsIgnoreCase(HttpHeaders.CONTENT_MD5) + || key.equalsIgnoreCase(HttpHeaders.HOST) || key.startsWith(HEADER_TAG)) { + canonicalizedHeaders.put(key, header.getValue()); + } + } + return canonicalizedHeaders; + } + + private static String buildCanonicalizedHeadersString(Multimap<String, String> canonicalizedHeadersMap) { + StringBuilder canonicalizedHeadersBuffer = new StringBuilder(); + for (Entry<String, String> header : canonicalizedHeadersMap.entries()) { + String key = header.getKey(); + canonicalizedHeadersBuffer.append(key.toLowerCase()).append(':').append(header.getValue()).append('\n'); + } + return canonicalizedHeadersBuffer.toString(); + } + + private static String buildSignedHeaders(Multimap<String, String> canonicalizedHeadersMap) { + return Joiner.on(';').join(Iterables.transform(canonicalizedHeadersMap.keySet(), new Function<String, String>() { + + @Override + public String apply(String input) { + return input.toLowerCase(); + } + })); + } + + private static String sha256(byte[] unhashedBytes) { + return Hashing.sha256().hashBytes(unhashedBytes).toString(); + } + + private static String buildHashedPayload(HttpRequest request) { + try { + byte[] unhashedBytes = request.getPayload() == null ? "".getBytes() : ByteStreams.toByteArray(request + .getPayload().getInput()); + return sha256(unhashedBytes); + } catch (IOException e) { + throw new HttpException("Error signing request", e); + } + } + + private static String buildAuthHeader(String accessKey, String credentialScope, String signedHeaders, + String signature) { + return ALGORITHM + " " + "Credential=" + accessKey + "/" + credentialScope + "," + "SignedHeaders=" + + signedHeaders + "," + "Signature=" + signature; + } + + private byte[] hmacSha256(byte[] key, String s) { + try { + Mac hmacSHA256 = crypto.hmacSHA256(key); + return hmacSHA256.doFinal(s.getBytes()); + } catch (Exception e) { + throw new HttpException("Error signing request", e); + } + } + + private String buildSignature(String dateWithoutTimestamp, String stringToSign) { + byte[] kSecret = (AUTH_TAG + credential).getBytes(UTF_8); + byte[] kDate = hmacSha256(kSecret, dateWithoutTimestamp); + byte[] kRegion = hmacSha256(kDate, REGION); + byte[] kService = hmacSha256(kRegion, SERVICE); + byte[] kSigning = hmacSha256(kService, TERMINATION_STRING); + return BaseEncoding.base16().encode(hmacSha256(kSigning, stringToSign)).toLowerCase(); + } + + public HttpRequest sign(HttpRequest request) { + // Grab the needed data to build the signature + Multimap<String, String> canonicalizedHeadersMap = buildCanonicalizedHeadersMap(request); + String canonicalizedHeadersString = buildCanonicalizedHeadersString(canonicalizedHeadersMap); + String signedHeaders = buildSignedHeaders(canonicalizedHeadersMap); + String date = request.getFirstHeaderOrNull(GlacierHeaders.ALTERNATE_DATE); + String dateWithoutTimestamp = formatDateWithoutTimestamp(date); + String method = request.getMethod(); + String endpoint = request.getEndpoint().getRawPath(); + String credentialScope = buildCredentialScope(dateWithoutTimestamp); + String hashedPayload = buildHashedPayload(request); + + // Task 1: Create a Canonical Request For Signature Version 4. + String hashedCanonicalRequest = buildHashedCanonicalRequest(method, endpoint, hashedPayload, + canonicalizedHeadersString, signedHeaders); + + // Task 2: Create a String to Sign for Signature Version 4. + String stringToSign = createStringToSign(date, credentialScope, hashedCanonicalRequest); + + // Task 3: Calculate the AWS Signature Version 4. + String signature = buildSignature(dateWithoutTimestamp, stringToSign); + + // Sign the request + String authHeader = buildAuthHeader(identity, credentialScope, signedHeaders, signature); + request = request.toBuilder().replaceHeader(HttpHeaders.AUTHORIZATION, authHeader).build(); + return request; + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata ---------------------------------------------------------------------- diff --git a/glacier/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/glacier/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata new file mode 100644 index 0000000..ef20fba --- /dev/null +++ b/glacier/src/main/resources/META-INF/services/org.jclouds.apis.ApiMetadata @@ -0,0 +1 @@ +org.jclouds.glacier.GlacierApiMetadata http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/test/java/org/jclouds/glacier/GlacierApiMetadataTest.java ---------------------------------------------------------------------- diff --git a/glacier/src/test/java/org/jclouds/glacier/GlacierApiMetadataTest.java b/glacier/src/test/java/org/jclouds/glacier/GlacierApiMetadataTest.java new file mode 100644 index 0000000..0cb3272 --- /dev/null +++ b/glacier/src/test/java/org/jclouds/glacier/GlacierApiMetadataTest.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.glacier; + +import org.jclouds.blobstore.internal.BaseBlobStoreApiMetadataTest; +import org.testng.annotations.Test; + +/** + * + * @author Roman Coedo + */ +@Test(groups = "unit", testName = "GlacierApiMetadataTest") +public class GlacierApiMetadataTest extends BaseBlobStoreApiMetadataTest { + + public GlacierApiMetadataTest() { + super(new GlacierApiMetadata()); + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java ---------------------------------------------------------------------- diff --git a/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java b/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java new file mode 100644 index 0000000..b3ec6c2 --- /dev/null +++ b/glacier/src/test/java/org/jclouds/glacier/GlacierClientMockTest.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.glacier; + +import static com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor; +import static org.jclouds.Constants.PROPERTY_MAX_RETRIES; +import static org.jclouds.Constants.PROPERTY_SO_TIMEOUT; +import static org.testng.Assert.assertEquals; + +import java.io.IOException; +import java.net.URI; +import java.net.URL; +import java.util.Properties; +import java.util.Set; + +import org.jclouds.ContextBuilder; +import org.jclouds.concurrent.config.ExecutorServiceModule; + +import com.google.common.collect.ImmutableSet; +import com.google.inject.Module; +import com.google.mockwebserver.MockResponse; +import com.google.mockwebserver.MockWebServer; +import com.google.mockwebserver.RecordedRequest; + +public class GlacierClientMockTest { + + private static final String VAULT_NAME = "ConcreteVaultName"; + + private static final Set<Module> modules = ImmutableSet.<Module> of(new ExecutorServiceModule(sameThreadExecutor(), + sameThreadExecutor())); + + static GlacierClient getGlacierClient(URL server) { + Properties overrides = new Properties(); + // prevent expect-100 bug http://code.google.com/p/mockwebserver/issues/detail?id=6 + overrides.setProperty(PROPERTY_SO_TIMEOUT, "0"); + overrides.setProperty(PROPERTY_MAX_RETRIES, "1"); + return ContextBuilder.newBuilder("glacier").credentials("accessKey", "secretKey").endpoint(server.toString()) + .modules(modules).overrides(overrides).buildApi(GlacierClient.class); + } + + public void testCreateVault() throws IOException, InterruptedException { + // Prepare the response + MockResponse mr = new MockResponse(); + mr.setResponseCode(201); + mr.addHeader("x-amzn-RequestId", "AAABZpJrTyioDC_HsOmHae8EZp_uBSJr6cnGOLKp_XJCl-Q"); + mr.addHeader("Date", "Sun, 25 Mar 2012 12:02:00 GMT"); + mr.addHeader("Location", "/111122223333/vaults/" + VAULT_NAME); + MockWebServer server = new MockWebServer(); + server.enqueue(mr); + server.play(); + + // Send the request and check the response + try { + GlacierClient client = getGlacierClient(server.getUrl("/")); + URI responseUri = client.createVault(VAULT_NAME); + assertEquals(responseUri.toString(), server.getUrl("/") + "111122223333/vaults/" + VAULT_NAME); + RecordedRequest request = server.takeRequest(); + assertEquals(request.getRequestLine(), "PUT /-/vaults/" + VAULT_NAME + " HTTP/1.1"); + } finally { + server.shutdown(); + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/test/java/org/jclouds/glacier/util/AWSRequestSignerV4Test.java ---------------------------------------------------------------------- diff --git a/glacier/src/test/java/org/jclouds/glacier/util/AWSRequestSignerV4Test.java b/glacier/src/test/java/org/jclouds/glacier/util/AWSRequestSignerV4Test.java new file mode 100644 index 0000000..24d479a --- /dev/null +++ b/glacier/src/test/java/org/jclouds/glacier/util/AWSRequestSignerV4Test.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.glacier.util; + +import static org.testng.Assert.assertEquals; + +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +import org.jclouds.encryption.internal.JCECrypto; +import org.jclouds.http.HttpRequest; +import org.testng.annotations.Test; + +import com.google.common.collect.Multimap; +import com.google.common.collect.TreeMultimap; + +@Test(groups = "unit", testName = "AWSRequestSignerV4Test") +public class AWSRequestSignerV4Test { + + @Test + public void testSignatureCalculation() throws NoSuchAlgorithmException, CertificateException { + String auth = "AWS4-HMAC-SHA256 " + "Credential=AKIAIOSFODNN7EXAMPLE/20120525/us-east-1/glacier/aws4_request," + + "SignedHeaders=host;x-amz-date;x-amz-glacier-version," + + "Signature=3ce5b2f2fffac9262b4da9256f8d086b4aaf42eba5f111c21681a65a127b7c2a"; + String identity = "AKIAIOSFODNN7EXAMPLE"; + String credential = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"; + AWSRequestSignerV4 signer = new AWSRequestSignerV4(identity, credential, new JCECrypto()); + HttpRequest request = signer.sign(createRequest()); + assertEquals(request.getFirstHeaderOrNull("Authorization"), auth); + } + + private HttpRequest createRequest() { + Multimap<String, String> headers = TreeMultimap.create(); + headers.put("Host", "glacier.us-east-1.amazonaws.com"); + headers.put("x-amz-date", "20120525T002453Z"); + headers.put("x-amz-glacier-version", "2012-06-01"); + HttpRequest request = HttpRequest.builder().method("PUT") + .endpoint("https://glacier.us-east-1.amazonaws.com/-/vaults/examplevault").headers(headers).build(); + return request; + } + +} http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/glacier/src/test/resources/META-INF/services/org.jclouds.apis.ApiMetadata ---------------------------------------------------------------------- diff --git a/glacier/src/test/resources/META-INF/services/org.jclouds.apis.ApiMetadata b/glacier/src/test/resources/META-INF/services/org.jclouds.apis.ApiMetadata new file mode 100644 index 0000000..ef20fba --- /dev/null +++ b/glacier/src/test/resources/META-INF/services/org.jclouds.apis.ApiMetadata @@ -0,0 +1 @@ +org.jclouds.glacier.GlacierApiMetadata http://git-wip-us.apache.org/repos/asf/jclouds-labs-aws/blob/ca269db8/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 1c5c45a..722bdf7 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,7 @@ <module>elb</module> <module>iam</module> <module>rds</module> + <module>glacier</module> <module>aws-elb</module> <module>aws-iam</module> <module>aws-rds</module>
