added AWS temporary security credentials and integrated with FormSigner

This commit is contained in:
adriancole 2013-01-21 10:07:44 -08:00
parent 847561ee00
commit 25ce398a44
7 changed files with 389 additions and 34 deletions

View File

@ -0,0 +1,177 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.aws.domain;
import static com.google.common.base.Preconditions.checkNotNull;
import java.util.Date;
import org.jclouds.domain.Credentials;
import com.google.common.base.Objects;
/**
* AWS credentials for API authentication.
*
* @see <a href=
* "http://docs.aws.amazon.com/STS/latest/APIReference/API_Credentials.html"
* />
*
* @author Adrian Cole
*/
public final class TemporaryCredentials extends Credentials {
public static Builder builder() {
return new Builder();
}
public Builder toBuilder() {
return builder().from(this);
}
public final static class Builder extends Credentials.Builder<TemporaryCredentials> {
private String accessKeyId;
private String secretAccessKey;
private String sessionToken;
private Date expiration;
@Override
public Builder identity(String identity) {
return accessKeyId(identity);
}
@Override
public Builder credential(String credential) {
return secretAccessKey(credential);
}
/**
* @see TemporaryCredentials#getAccessKeyId()
*/
public Builder accessKeyId(String accessKeyId) {
this.accessKeyId = accessKeyId;
return this;
}
/**
* @see TemporaryCredentials#getSecretAccessKey()
*/
public Builder secretAccessKey(String secretAccessKey) {
this.secretAccessKey = secretAccessKey;
return this;
}
/**
* @see TemporaryCredentials#getSessionToken()
*/
public Builder sessionToken(String sessionToken) {
this.sessionToken = sessionToken;
return this;
}
/**
* @see TemporaryCredentials#getExpiration()
*/
public Builder expiration(Date expiration) {
this.expiration = expiration;
return this;
}
public TemporaryCredentials build() {
return new TemporaryCredentials(accessKeyId, secretAccessKey, sessionToken, expiration);
}
public Builder from(TemporaryCredentials in) {
return this.accessKeyId(in.identity).secretAccessKey(in.credential).sessionToken(in.sessionToken)
.expiration(in.expiration);
}
}
private final String sessionToken;
private final Date expiration;
private TemporaryCredentials(String accessKeyId, String secretAccessKey, String sessionToken, Date expiration) {
super(checkNotNull(accessKeyId, "accessKeyId"), checkNotNull(secretAccessKey, "secretAccessKey for %s",
accessKeyId));
this.sessionToken = checkNotNull(sessionToken, "sessionToken for %s", accessKeyId);
this.expiration = checkNotNull(expiration, "expiration for %s", accessKeyId);
}
/**
* AccessKeyId ID that identifies the temporary credentials.
*/
public String getAccessKeyId() {
return identity;
}
/**
* The Secret Access Key to sign requests.
*/
public String getSecretAccessKey() {
return credential;
}
/**
* The security token that users must pass to the service API to use the
* temporary credentials.
*/
public String getSessionToken() {
return sessionToken;
}
/**
* The date on which these credentials expire.
*/
public Date getExpiration() {
return expiration;
}
/**
* {@inheritDoc}
*/
@Override
public int hashCode() {
return Objects.hashCode(identity, credential, sessionToken, expiration);
}
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
TemporaryCredentials other = (TemporaryCredentials) obj;
return Objects.equal(this.identity, other.identity) && Objects.equal(this.credential, other.credential)
&& Objects.equal(this.sessionToken, other.sessionToken) && Objects.equal(this.expiration, other.expiration);
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return Objects.toStringHelper(this).add("accessKeyId", identity).add("sessionToken", sessionToken)
.add("expiration", expiration).toString();
}
}

View File

@ -47,6 +47,7 @@ import javax.inject.Singleton;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import org.jclouds.Constants; import org.jclouds.Constants;
import org.jclouds.aws.domain.TemporaryCredentials;
import org.jclouds.crypto.Crypto; import org.jclouds.crypto.Crypto;
import org.jclouds.date.TimeStamp; import org.jclouds.date.TimeStamp;
import org.jclouds.domain.Credentials; import org.jclouds.domain.Credentials;
@ -114,10 +115,18 @@ public class FormSigner implements HttpRequestFilter, RequestSigner {
String signature = sign(stringToSign); String signature = sign(stringToSign);
addSignature(decodedParams, signature); addSignature(decodedParams, signature);
request = setPayload(request, decodedParams); request = setPayload(request, decodedParams);
Credentials current = creds.get();
if (current instanceof TemporaryCredentials) {
request = replaceSecurityTokenHeader(request, TemporaryCredentials.class.cast(current));
}
utils.logRequest(signatureLog, request, "<<"); utils.logRequest(signatureLog, request, "<<");
return request; return request;
} }
HttpRequest replaceSecurityTokenHeader(HttpRequest request, TemporaryCredentials current) {
return request.toBuilder().replaceHeader("SecurityToken", current.getSessionToken()).build();
}
HttpRequest setPayload(HttpRequest request, Multimap<String, String> decodedParams) { HttpRequest setPayload(HttpRequest request, Multimap<String, String> decodedParams) {
String queryLine = buildQueryLine(decodedParams); String queryLine = buildQueryLine(decodedParams);
request.setPayload(queryLine); request.setPayload(queryLine);

View File

@ -0,0 +1,83 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.aws.xml;
import javax.inject.Inject;
import org.jclouds.aws.domain.TemporaryCredentials;
import org.jclouds.date.DateService;
import org.jclouds.http.functions.ParseSax;
import org.jclouds.util.SaxUtils;
/**
* @see <a href=
* "http://docs.aws.amazon.com/STS/latest/APIReference/API_Credentials.html"
* />
*
* @author Adrian Cole
*/
public class TemporaryCredentialsHandler extends ParseSax.HandlerForGeneratedRequestWithResult<TemporaryCredentials> {
private final DateService dateService;
@Inject
protected TemporaryCredentialsHandler(DateService dateService) {
this.dateService = dateService;
}
private StringBuilder currentText = new StringBuilder();
private TemporaryCredentials.Builder builder = TemporaryCredentials.builder();
/**
* {@inheritDoc}
*/
@Override
public TemporaryCredentials getResult() {
try {
return builder.build();
} finally {
builder = TemporaryCredentials.builder();
}
}
/**
* {@inheritDoc}
*/
@Override
public void endElement(String uri, String name, String qName) {
if (qName.equals("AccessKeyId")) {
builder.accessKeyId(SaxUtils.currentOrNull(currentText));
} else if (qName.equals("SecretAccessKey")) {
builder.secretAccessKey(SaxUtils.currentOrNull(currentText));
} else if (qName.equals("SessionToken")) {
builder.sessionToken(SaxUtils.currentOrNull(currentText));
} else if (qName.equals("Expiration")) {
builder.expiration(dateService.iso8601DateParse(SaxUtils.currentOrNull(currentText)));
}
currentText = new StringBuilder();
}
/**
* {@inheritDoc}
*/
@Override
public void characters(char ch[], int start, int length) {
currentText.append(ch, start, length);
}
}

View File

@ -17,13 +17,16 @@
* under the License. * under the License.
*/ */
package org.jclouds.aws.filters; package org.jclouds.aws.filters;
import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG; import static org.jclouds.aws.reference.AWSConstants.PROPERTY_HEADER_TAG;
import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertEquals;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import org.jclouds.ContextBuilder; import org.jclouds.ContextBuilder;
import org.jclouds.aws.xml.TemporaryCredentialsHandlerTest;
import org.jclouds.date.TimeStamp; import org.jclouds.date.TimeStamp;
import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.IntegrationTestAsyncClient; import org.jclouds.http.IntegrationTestAsyncClient;
import org.jclouds.http.IntegrationTestClient; import org.jclouds.http.IntegrationTestClient;
@ -33,6 +36,7 @@ import org.jclouds.rest.RequestSigner;
import org.jclouds.rest.internal.BaseRestApiTest.MockModule; import org.jclouds.rest.internal.BaseRestApiTest.MockModule;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultimap;
import com.google.inject.AbstractModule; import com.google.inject.AbstractModule;
@ -45,49 +49,63 @@ import com.google.inject.name.Names;
* *
* @author Adrian Cole * @author Adrian Cole
*/ */
// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire // NOTE:without testName, this will not call @Before* and fail w/NPE during
@Test(groups = "unit", testName = "FormSignerTest") // surefire
@Test(groups = "unit", singleThreaded = true, testName = "FormSignerTest")
public class FormSignerTest { public class FormSignerTest {
public static final Injector INJECTOR = ContextBuilder public static Injector injector(Credentials creds) {
.newBuilder( return ContextBuilder
AnonymousProviderMetadata.forClientMappedToAsyncClientOnEndpoint(IntegrationTestClient.class, IntegrationTestAsyncClient.class, .newBuilder(
"http://localhost")) AnonymousProviderMetadata.forClientMappedToAsyncClientOnEndpoint(IntegrationTestClient.class,
.credentials("identity", "credential") IntegrationTestAsyncClient.class, "http://localhost"))
.apiVersion("apiVersion") .credentialsSupplier(Suppliers.<Credentials> ofInstance(creds)).apiVersion("apiVersion")
.modules(ImmutableList.<Module> of(new MockModule(), new NullLoggingModule(), .modules(ImmutableList.<Module> of(new MockModule(), new NullLoggingModule(), new AbstractModule() {
new AbstractModule() { @Override
@Override protected void configure() {
protected void configure() { bind(RequestSigner.class).to(FormSigner.class);
bind(RequestSigner.class).to(FormSigner.class); bind(String.class).annotatedWith(Names.named(PROPERTY_HEADER_TAG)).toInstance("amz");
bind(String.class).annotatedWith(Names.named(PROPERTY_HEADER_TAG)).toInstance("amz"); bind(String.class).annotatedWith(TimeStamp.class).toInstance("2009-11-08T15:54:08.897Z");
bind(String.class).annotatedWith(TimeStamp.class).toInstance("2009-11-08T15:54:08.897Z"); }
}
})).buildInjector(); })).buildInjector();
FormSigner filter = INJECTOR.getInstance(FormSigner.class); }
public static FormSigner filter(Credentials creds) {
return injector(creds).getInstance(FormSigner.class);
}
public static FormSigner staticCredentialsFilter = filter(new Credentials("identity", "credential"));
HttpRequest request = HttpRequest.builder().method("GET")
.endpoint("http://localhost")
.addHeader(HttpHeaders.HOST, "localhost")
.addFormParam("Action", "DescribeImages")
.addFormParam("ImageId.1", "ami-2bb65342").build();
@Test
void testAddsSecurityToken() {
HttpRequest filtered = filter(new TemporaryCredentialsHandlerTest().expected()).filter(request);
assertEquals(
filtered.getPayload().getRawContent(),
"Action=DescribeImages&ImageId.1=ami-2bb65342&Signature=waV%2B%2BIdRwHRlnK2126CqgHHd4FZb%2B5wAeRueidjFc/M%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=AKIAIOSFODNN7EXAMPLE");
assertEquals(filtered.getFirstHeaderOrNull("SecurityToken"), "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT");
}
@Test @Test
void testBuildCanonicalizedStringSetsVersion() { void testBuildCanonicalizedStringSetsVersion() {
HttpRequest filtered = staticCredentialsFilter.filter(request);
assertEquals( assertEquals(filtered.getPayload().getRawContent(),
filter.filter( "Action=DescribeImages&ImageId.1=ami-2bb65342&Signature=ugnt4m2eHE7Ka/vXTr9EhKZq7bhxOfvW0y4pAEqF97w%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=identity");
HttpRequest.builder()
.method("GET")
.endpoint("http://localhost")
.addHeader(HttpHeaders.HOST, "localhost")
.payload("Action=DescribeImages&ImageId.1=ami-2bb65342").build())
.getPayload().getRawContent(),
"Action=DescribeImages&ImageId.1=ami-2bb65342&Signature=ugnt4m2eHE7Ka/vXTr9EhKZq7bhxOfvW0y4pAEqF97w%3D&SignatureMethod=HmacSHA256&SignatureVersion=2&Timestamp=2009-11-08T15%3A54%3A08.897Z&Version=apiVersion&AWSAccessKeyId=identity");
} }
@Test @Test
void testBuildCanonicalizedString() { void testBuildCanonicalizedString() {
assertEquals( assertEquals(
filter.buildCanonicalizedString(new ImmutableMultimap.Builder<String, String>().put("AWSAccessKeyId", staticCredentialsFilter.buildCanonicalizedString(new ImmutableMultimap.Builder<String, String>()
"foo").put("Action", "DescribeImages").put("Expires", "2008-02-10T12:00:00Z").put("ImageId.1", .put("AWSAccessKeyId", "foo").put("Action", "DescribeImages").put("Expires", "2008-02-10T12:00:00Z")
"ami-2bb65342").put("SignatureMethod", "HmacSHA256").put("SignatureVersion", "2").put( .put("ImageId.1", "ami-2bb65342").put("SignatureMethod", "HmacSHA256").put("SignatureVersion", "2")
"Version", "2010-06-15").build()), .put("Version", "2010-06-15").build()),
"AWSAccessKeyId=foo&Action=DescribeImages&Expires=2008-02-10T12%3A00%3A00Z&ImageId.1=ami-2bb65342&SignatureMethod=HmacSHA256&SignatureVersion=2&Version=2010-06-15"); "AWSAccessKeyId=foo&Action=DescribeImages&Expires=2008-02-10T12%3A00%3A00Z&ImageId.1=ami-2bb65342&SignatureMethod=HmacSHA256&SignatureVersion=2&Version=2010-06-15");
} }
} }

View File

@ -29,6 +29,7 @@ import java.io.InputStream;
import org.jclouds.aws.domain.AWSError; import org.jclouds.aws.domain.AWSError;
import org.jclouds.aws.filters.FormSignerTest; import org.jclouds.aws.filters.FormSignerTest;
import org.jclouds.domain.Credentials;
import org.jclouds.http.HttpCommand; import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpRequest; import org.jclouds.http.HttpRequest;
import org.jclouds.http.HttpResponse; import org.jclouds.http.HttpResponse;
@ -49,7 +50,7 @@ public class AWSUtilsTest {
@BeforeTest @BeforeTest
protected void setUpInjector() throws IOException { protected void setUpInjector() throws IOException {
utils = FormSignerTest.INJECTOR.getInstance(AWSUtils.class); utils = FormSignerTest.injector(new Credentials("identity", "credential")).getInstance(AWSUtils.class);
command = createMock(HttpCommand.class); command = createMock(HttpCommand.class);
expect(command.getCurrentRequest()).andReturn(createMock(HttpRequest.class)).atLeastOnce(); expect(command.getCurrentRequest()).andReturn(createMock(HttpRequest.class)).atLeastOnce();

View File

@ -0,0 +1,61 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.aws.xml;
import static org.testng.Assert.assertEquals;
import java.io.InputStream;
import org.jclouds.aws.domain.TemporaryCredentials;
import org.jclouds.aws.xml.TemporaryCredentialsHandler;
import org.jclouds.date.internal.SimpleDateFormatDateService;
import org.jclouds.http.functions.BaseHandlerTest;
import org.testng.annotations.Test;
/**
* @author Adrian Cole
*/
// NOTE:without testName, this will not call @Before* and fail w/NPE during surefire
@Test(groups = "unit", testName = "TemporaryCredentialsHandlerTest")
public class TemporaryCredentialsHandlerTest extends BaseHandlerTest {
public void test() {
InputStream is = getClass().getResourceAsStream("/credentials.xml");
TemporaryCredentials expected = expected();
TemporaryCredentialsHandler handler = injector.getInstance(TemporaryCredentialsHandler.class);
TemporaryCredentials result = factory.create(handler).parse(is);
assertEquals(result, expected);
assertEquals(result.getAccessKeyId(), expected.getAccessKeyId());
assertEquals(result.getSecretAccessKey(), expected.getSecretAccessKey());
assertEquals(result.getSessionToken(), expected.getSessionToken());
assertEquals(result.getExpiration(), expected.getExpiration());
}
public TemporaryCredentials expected() {
return TemporaryCredentials.builder()
.accessKeyId("AKIAIOSFODNN7EXAMPLE")
.secretAccessKey("wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY")
.sessionToken("AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT")
.expiration(new SimpleDateFormatDateService().iso8601DateParse("2011-07-11T19:55:29.611Z")).build();
}
}

View File

@ -0,0 +1,6 @@
<Credentials>
<SessionToken>AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT</SessionToken>
<SecretAccessKey>wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY</SecretAccessKey>
<Expiration>2011-07-11T19:55:29.611Z</Expiration>
<AccessKeyId>AKIAIOSFODNN7EXAMPLE</AccessKeyId>
</Credentials>