Merge pull request #349 from andreisavu/windows-passwords

Support retrieving and decrypting the Administrator password of EC2 Windows instances
This commit is contained in:
Adrian Cole 2012-02-04 10:24:16 -08:00
commit db53c00748
15 changed files with 576 additions and 36 deletions

View File

@ -83,6 +83,12 @@
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jclouds.driver</groupId>
<artifactId>jclouds-bouncycastle</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>

View File

@ -48,6 +48,7 @@ import org.jclouds.ec2.compute.functions.CredentialsForInstance;
import org.jclouds.ec2.compute.functions.LoadPublicIpForInstanceOrNull;
import org.jclouds.ec2.compute.functions.RegionAndIdToImage;
import org.jclouds.ec2.compute.functions.RunningInstanceToNodeMetadata;
import org.jclouds.ec2.compute.functions.WindowsLoginCredentialsFromEncryptedData;
import org.jclouds.ec2.compute.internal.EC2TemplateBuilderImpl;
import org.jclouds.ec2.compute.options.EC2TemplateOptions;
import org.jclouds.ec2.compute.predicates.SecurityGroupPresent;
@ -110,6 +111,7 @@ public class EC2ComputeServiceDependenciesModule extends AbstractModule {
bind(new TypeLiteral<ComputeServiceContext>() {
}).to(new TypeLiteral<ComputeServiceContextImpl<EC2Client, EC2AsyncClient>>() {
}).in(Scopes.SINGLETON);
bind(WindowsLoginCredentialsFromEncryptedData.class);
}
/**

View File

@ -0,0 +1,45 @@
/**
* 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.ec2.compute.domain;
import org.jclouds.ec2.domain.PasswordData;
/**
* An encrypted Windows Administrator password, and the private key that can decrypt it.
*
* @author Richard Downer
*/
public class PasswordDataAndPrivateKey {
private final PasswordData passwordData;
private final String privateKey;
public PasswordDataAndPrivateKey(PasswordData passwordData, String privateKey) {
this.passwordData = passwordData;
this.privateKey = privateKey;
}
public PasswordData getPasswordData() {
return passwordData;
}
public String getPrivateKey() {
return privateKey;
}
}

View File

@ -86,6 +86,7 @@ public class EC2ImageParser implements Function<org.jclouds.ec2.domain.Image, Im
ImageBuilder builder = new ImageBuilder();
builder.providerId(from.getId());
builder.id(from.getRegion() + "/" + from.getId());
builder.name(from.getName());
builder.description(from.getDescription() != null ? from.getDescription() : from.getImageLocation());
builder.userMetadata(ImmutableMap.<String, String> builder().put("owner", from.getImageOwnerId()).put(
"rootDeviceType", from.getRootDeviceType().value()).put("virtualizationType",

View File

@ -0,0 +1,78 @@
/**
* 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.ec2.compute.functions;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.inject.Singleton;
import org.jclouds.crypto.Crypto;
import org.jclouds.crypto.Pems;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.ec2.compute.domain.PasswordDataAndPrivateKey;
import org.jclouds.encryption.internal.Base64;
import javax.annotation.Nullable;
import javax.crypto.Cipher;
import javax.inject.Inject;
import java.nio.charset.Charset;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.spec.KeySpec;
/**
* Given an encrypted Windows Administrator password and the decryption key, return a LoginCredentials instance.
*
* @author Richard Downer
*/
@Singleton
public class WindowsLoginCredentialsFromEncryptedData implements Function<PasswordDataAndPrivateKey, LoginCredentials> {
private final Crypto crypto;
@Inject
public WindowsLoginCredentialsFromEncryptedData(Crypto crypto) {
this.crypto = crypto;
}
@Override
public LoginCredentials apply(@Nullable PasswordDataAndPrivateKey dataAndKey) {
if(dataAndKey == null)
return null;
try {
KeySpec keySpec = Pems.privateKeySpec(dataAndKey.getPrivateKey());
KeyFactory kf = crypto.rsaKeyFactory();
PrivateKey privKey = kf.generatePrivate(keySpec);
Cipher cipher = crypto.cipher("RSA/NONE/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privKey);
byte[] cipherText = Base64.decode(dataAndKey.getPasswordData().getPasswordData());
byte[] plainText = cipher.doFinal(cipherText);
String password = new String(plainText, Charset.forName("ASCII"));
return LoginCredentials.builder()
.user("Administrator")
.password(password)
.noPrivateKey()
.build();
} catch(Exception e) {
throw Throwables.propagate(e);
}
}
}

View File

@ -0,0 +1,129 @@
/**
* 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.ec2.domain;
import java.util.Date;
/**
* Holds the encrypted Windows Administrator password for an instance.
*
* @author Richard Downer
*/
public class PasswordData {
public static Builder builder() {
return new Builder();
}
public static class Builder {
private String requestId;
private String instanceId;
private Date timestamp;
private String passwordData;
private Builder() {}
public Builder requestId(String requestId) {
this.requestId = requestId;
return this;
}
public Builder instanceId(String instanceId) {
this.instanceId = instanceId;
return this;
}
public Builder timestamp(Date timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder passwordData(String passwordData) {
this.passwordData = passwordData;
return this;
}
public PasswordData build() {
return new PasswordData(requestId, instanceId, timestamp, passwordData);
}
}
private String requestId;
private String instanceId;
private Date timestamp;
private String passwordData;
public PasswordData(String requestId, String instanceId, Date timestamp, String passwordData) {
this.requestId = requestId;
this.instanceId = instanceId;
this.timestamp = timestamp;
this.passwordData = passwordData;
}
public String getRequestId() {
return requestId;
}
public String getInstanceId() {
return instanceId;
}
public Date getTimestamp() {
return timestamp;
}
public String getPasswordData() {
return passwordData;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PasswordData that = (PasswordData) o;
if (instanceId != null ? !instanceId.equals(that.instanceId) : that.instanceId != null) return false;
if (passwordData != null ? !passwordData.equals(that.passwordData) : that.passwordData != null) return false;
if (requestId != null ? !requestId.equals(that.requestId) : that.requestId != null) return false;
if (timestamp != null ? !timestamp.equals(that.timestamp) : that.timestamp != null) return false;
return true;
}
@Override
public int hashCode() {
int result = requestId != null ? requestId.hashCode() : 0;
result = 31 * result + (instanceId != null ? instanceId.hashCode() : 0);
result = 31 * result + (timestamp != null ? timestamp.hashCode() : 0);
result = 31 * result + (passwordData != null ? passwordData.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "PasswordData{" +
"requestId='" + requestId + '\'' +
", instanceId='" + instanceId + '\'' +
", timestamp=" + timestamp +
", passwordData='" + passwordData + '\'' +
'}';
}
}

View File

@ -23,6 +23,8 @@ import static org.jclouds.aws.reference.FormParameters.VERSION;
import java.util.Set;
import org.jclouds.ec2.domain.PasswordData;
import org.jclouds.ec2.xml.GetPasswordDataResponseHandler;
import org.jclouds.javax.annotation.Nullable;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
@ -96,4 +98,15 @@ public interface WindowsAsyncClient {
@EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
@BinderParam(BindBundleIdsToIndexedFormParams.class) String... bundleTaskIds);
/**
* @see WindowsClient#getPasswordDataInRegion
*/
@POST
@Path("/")
@FormParams(keys = ACTION, values = "GetPasswordData")
@XMLResponseParser(GetPasswordDataResponseHandler.class)
ListenableFuture<PasswordData> getPasswordData(
@EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
@FormParam("InstanceId") String instanceId);
}

View File

@ -18,14 +18,14 @@
*/
package org.jclouds.ec2.services;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.jclouds.concurrent.Timeout;
import org.jclouds.ec2.domain.BundleTask;
import org.jclouds.ec2.domain.PasswordData;
import org.jclouds.ec2.options.BundleInstanceS3StorageOptions;
import org.jclouds.javax.annotation.Nullable;
import org.jclouds.ec2.domain.BundleTask;
import org.jclouds.ec2.options.BundleInstanceS3StorageOptions;
import org.jclouds.concurrent.Timeout;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Provides windows services for EC2. For more information, refer to the Amazon
@ -109,4 +109,14 @@ public interface WindowsClient {
* />
*/
Set<BundleTask> describeBundleTasksInRegion(@Nullable String region, String... bundleTaskIds);
/**
*
* Retrieves the encrypted administrator password for the instances running Windows.
*
* @param region The region where the instance is based
* @param instanceId The ID of the instance to query
* @see <a href="http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-GetPasswordData.html" />
*/
PasswordData getPasswordDataInRegion(@Nullable String region, String instanceId);
}

View File

@ -0,0 +1,58 @@
/**
* 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.ec2.xml;
import org.jclouds.date.DateService;
import org.jclouds.ec2.domain.PasswordData;
import org.jclouds.http.functions.ParseSax;
import javax.inject.Inject;
/**
* @author Richard Downer
*/
public class GetPasswordDataResponseHandler extends ParseSax.HandlerWithResult<PasswordData> {
private StringBuilder currentText = new StringBuilder();
private PasswordData.Builder builder = PasswordData.builder();
@Inject protected DateService dateService;
@Override
public PasswordData getResult() {
return builder.build();
}
public void endElement(String uri, String name, String qName) {
if (qName.equals("requestId")) {
builder.requestId(currentText.toString().trim());
} else if (qName.equals("instanceId")) {
builder.instanceId(currentText.toString().trim());
} else if (qName.equals("timestamp")) {
builder.timestamp(dateService.iso8601DateParse(currentText.toString().trim()));
} else if (qName.equals("passwordData")) {
builder.passwordData(currentText.toString().trim());
}
currentText = new StringBuilder();
}
public void characters(char ch[], int start, int length) {
currentText.append(ch, start, length);
}
}

View File

@ -61,16 +61,16 @@ public class EC2ImageParserTest {
assertEquals(Iterables.get(result, 0), new ImageBuilder().operatingSystem(
new OperatingSystem.Builder().family(OsFamily.UNRECOGNIZED).arch("paravirtual").version("").description(
"137112412989/amzn-ami-0.9.7-beta.i386-ebs").is64Bit(false).build()).description("Amazon")
.defaultCredentials(new LoginCredentials("ec2-user", false)).id("us-east-1/ami-82e4b5c7").providerId(
"ami-82e4b5c7").location(defaultLocation).userMetadata(
.defaultCredentials(new LoginCredentials("ec2-user", false)).id("us-east-1/ami-82e4b5c7").name("amzn-ami-0.9.7-beta.i386-ebs")
.providerId("ami-82e4b5c7").location(defaultLocation).userMetadata(
ImmutableMap.of("owner", "137112412989", "rootDeviceType", "ebs")).build());
assertEquals(Iterables.get(result, 3), new ImageBuilder().operatingSystem(
new OperatingSystem.Builder().family(OsFamily.UNRECOGNIZED).arch("paravirtual").version("").description(
"amzn-ami-us-west-1/amzn-ami-0.9.7-beta.x86_64.manifest.xml").is64Bit(true).build())
.description("Amazon Linux AMI x86_64 S3").defaultCredentials(new LoginCredentials("ec2-user", false)).id(
"us-east-1/ami-f2e4b5b7").providerId("ami-f2e4b5b7").location(defaultLocation).userMetadata(
ImmutableMap.of("owner", "137112412989", "rootDeviceType", "ebs")).build());
"us-east-1/ami-f2e4b5b7").providerId("ami-f2e4b5b7").name("amzn-ami-0.9.7-beta.x86_64-S3").location(defaultLocation)
.userMetadata(ImmutableMap.of("owner", "137112412989", "rootDeviceType", "ebs")).build());
}
static Location defaultLocation = new LocationBuilder().scope(LocationScope.REGION).id("us-east-1").description(

View File

@ -0,0 +1,74 @@
/**
* 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.ec2.compute.functions;
import org.jclouds.crypto.Crypto;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.ec2.compute.domain.PasswordDataAndPrivateKey;
import org.jclouds.ec2.domain.PasswordData;
import org.jclouds.encryption.bouncycastle.BouncyCastleCrypto;
import org.testng.annotations.Test;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
/**
* @author Richard Downer
*/
public class WindowsLoginCredentialsFromEncryptedDataTest {
private static final String PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\n" +
"MIIEowIBAAKCAQEAmN6GOSMnyGNWN19ETBh11tJB5OGs3Dps8kPWqAhF9RyL/mKwkW26vH+h/5Z5\n" +
"cA5T80pK72kNnXObFaMHNoX3lavrc6yXF+8F3f1tlFX2Z+iB1pYXz1oBPqT6oOmc2XzcsJuJRakd\n" +
"zwRwHDaqljpaW7+TZlxhMa1DmUkD/HHMxDCK8jbUIZDc6BZSrnj2uPwHwW737NRE4aC3fcu4LMwf\n" +
"b2VotbNGNiAnNmrb/vtIIGkFE8NYEMpiz0WYTWX4eVKpJImv1PR6G1fMLSvudJs0ARObuLDvuonn\n" +
"SCFFdkibrwMKYbHVGGh6FoY1Vy0sqI55dgQU1kSNouiDgOGxgx+TIwIDAQABAoIBAHCS/nk5QGS7\n" +
"cpRYXa1EHhNSxx/MaUXM6MoH1x3q6cm1egqdlrWh/vAtdZkIsOkqQ/xX65Me493dcomegwNN6KOZ\n" +
"9Uw7/xCq/sEZjga8vzaJ7IOgCGy0NVJyn/a70rv+zW5pO8/G2KLI+95rC3iSBFSoYd3xjcnNdIh/\n" +
"UqYnD8oxYpKmf7418pMPsBrkglkFlbVBPiDXdpoSziqSN6uWQG4Yh0WR87aElhM9JJW50Hh6h7g5\n" +
"OvgCBzS8G+KXCjqimk108+/ed5Nl6VhPAf79yCVZUueKBhaf2r0Kkyxg7M/Y+LJwcoUusIP7Cv7G\n" +
"xyzG2vi21prWRCm2sVCUDyQy5qECgYEA92jGVAaB3OGEUIXn7eVE3U3FQH37XcJMGsHqBIzDG13p\n" +
"C97HdN21rwRkz+G2eAsIxA+p9BsO7dSmtKC60kl6iMRgltS3W7xoC37N9BtjhpciHcLg8c70oyDx\n" +
"qHiLKuDi90mZ1FPmWupO4FJnGEB3evHUKZSpTrVVMzt+tyEn/psCgYEAni1hrYoMkQgN3sEC3CKB\n" +
"0jQkrOMvY219B8Tdf9LXSuP6z9POagDBDhkeT3xn8rAOmOfVGHYdO0CvPqmAkmXhf+g+OREdecQa\n" +
"uY0FmvcTt+Dx0c6pRZmm5AhvUVXFXqONsSg79iviXbUy5Hik0k5HTs5E6B4obrh5W+xfMTUXghkC\n" +
"gYBn92uAW8uumkYT4HF6EuJBbTD6zPYYjFGW3O4OQ2ip02jfSBrhDVoP1fTXNq6K+3gPi9WLcuNv\n" +
"JfF37iMTwzTuzDcaqwDyV9YRHpRFhEzqfhAkGYSVmLZM5scmWKGCv0YhTJiMFUWz5sqGkZopIs4S\n" +
"qBTT9FjBbooDIXk6U4CPCQKBgFdVBxEhnz6UC9RpDIMuKi88yuMJrChhUx7u+ryQVH3s0ZXdg6HT\n" +
"OMPn6mxIa7v6qJSTq3wN+qW0WQ1n2Kz7wz0zpOctI/EO7RJ1YhrlP+XONLV6PMtIwnQ0lAF8MbTG\n" +
"6HxfknugTyMd4DN0yMu0nHpOOI1P2VMIVzkBkK1CevBBAoGBALROGR7a+eijHdp0/A0chfUoBmud\n" +
"/TsUt+0g/vf1p69rMt6DqEGMgMtp2jIRnwvLElS7gVqnCTEclxNU/0rCXR+V7ImJm8J4f0ff8m0Y\n" +
"Fir9nfCYStszo25NvLFfynS9d/aoBuvqGJaiQyNXiyBJ4MaxxFYagzAWTnDX+kzTlkZ2\n" +
"-----END RSA PRIVATE KEY-----";
private static final String ENCRYPTED_PASSWORD = "gO1oMoIjjIifv2iqcfIKiQD7ziOTVXsuaBJFEQrZdb8uJH/LsAiJXZeGKEeXlHl/oMoR3HEIoYuHxl+p5iHdrpP889RmxWBDGOWC5iTUzK6CRa5mFmF1I5Lpt7v2YeVoQWihSM8B19BEdBdY1svQp9nyhPB4AqLDrY28x/OrmRh/qYq953i6Y4Z8c76OHqqGcUYM4ePysRlcizSgQjdkEDmKC10Ak3OFRRx3/LqYsFIMiOHeg47APg+UANNTyRiTIia5FDhSeHJzaeYCBRQ7UYH0z2rg4cX3YjOz/MoznjHiaaN4MO+5N3v84VawnqwKOvlwPyI2bmz0+9Tr6DKzqA==";
@Test
public void testApply() throws Exception {
Crypto crypto = new BouncyCastleCrypto();
WindowsLoginCredentialsFromEncryptedData f = new WindowsLoginCredentialsFromEncryptedData(crypto);
PasswordData passwordData = PasswordData.builder().passwordData(ENCRYPTED_PASSWORD).build();
LoginCredentials credentials = f.apply(new PasswordDataAndPrivateKey(passwordData, PRIVATE_KEY));
assertEquals(credentials.getUser(), "Administrator");
assertEquals(credentials.getPassword(), "u4.y9mb;nR.");
assertFalse(credentials.getOptionalPrivateKey().isPresent());
}
}

View File

@ -18,19 +18,44 @@
*/
package org.jclouds.ec2.services;
import java.util.Properties;
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.inject.Module;
import org.jclouds.compute.BaseVersionedServiceLiveTest;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.ComputeServiceContext;
import org.jclouds.compute.ComputeServiceContextFactory;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.domain.OsFamily;
import org.jclouds.compute.domain.Template;
import org.jclouds.compute.options.TemplateOptions;
import org.jclouds.domain.LoginCredentials;
import org.jclouds.ec2.EC2AsyncClient;
import org.jclouds.ec2.EC2Client;
import org.jclouds.ec2.EC2ContextBuilder;
import org.jclouds.ec2.EC2PropertiesBuilder;
import org.jclouds.ec2.compute.domain.PasswordDataAndPrivateKey;
import org.jclouds.ec2.compute.functions.WindowsLoginCredentialsFromEncryptedData;
import org.jclouds.ec2.domain.InstanceType;
import org.jclouds.ec2.domain.PasswordData;
import org.jclouds.ec2.reference.EC2Constants;
import org.jclouds.encryption.bouncycastle.config.BouncyCastleCryptoModule;
import org.jclouds.logging.log4j.config.Log4JLoggingModule;
import org.jclouds.predicates.RetryablePredicate;
import org.jclouds.rest.RestContext;
import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Module;
import javax.annotation.Nullable;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
/**
* Tests behavior of {@code WindowsClient}
@ -43,19 +68,30 @@ public class WindowsClientLiveTest extends BaseVersionedServiceLiveTest {
provider = "ec2";
}
private ComputeService computeService;
private WindowsClient client;
private static final String DEFAULT_INSTANCE = "i-TODO";
private static final String DEFAULT_BUCKET = "TODO";
private RestContext<EC2Client, EC2AsyncClient> context;
@Override
public Properties setupRestProperties() {
Properties rest = super.setupRestProperties();
rest.put("ec2.contextbuilder", EC2ContextBuilder.class.getName());
rest.put("ec2.propertiesbuilder", EC2PropertiesBuilder.class.getName());
return rest;
}
@BeforeGroups(groups = { "live" })
public void setupClient() {
setupCredentials();
Properties overrides = setupProperties();
context = new ComputeServiceContextFactory().createContext(provider,
ImmutableSet.<Module> of(new Log4JLoggingModule()), overrides).getProviderSpecificContext();
overrides.put(EC2Constants.PROPERTY_EC2_AMI_OWNERS, "206029621532"); /* Amazon Owner ID */
ComputeServiceContext serviceContext = new ComputeServiceContextFactory(setupRestProperties()).createContext(provider,
ImmutableSet.<Module>of(new Log4JLoggingModule(), new BouncyCastleCryptoModule()), overrides);
computeService = serviceContext.getComputeService();
context = serviceContext.getProviderSpecificContext();
client = context.getApi().getWindowsServices();
}
@ -82,4 +118,52 @@ public class WindowsClientLiveTest extends BaseVersionedServiceLiveTest {
public void testDescribeBundleTasksInRegion() {
}
@Test
public void testGetPasswordDataInRegion() throws Exception {
// Spin up a new node. Make sure to open the RDP port 3389
Template template = computeService.templateBuilder()
.osFamily(OsFamily.WINDOWS)
.os64Bit(true)
.imageNameMatches("Windows-2008R2-SP1-English-Base-")
.hardwareId(InstanceType.M1_LARGE)
.options(TemplateOptions.Builder.inboundPorts(3389))
.build();
Set<? extends NodeMetadata> nodes = computeService.createNodesInGroup("test", 1, template);
NodeMetadata node = Iterables.getOnlyElement(nodes);
boolean shutdown = true;
try {
// The Administrator password will take some time before it is ready - Amazon says sometimes 15 minutes.
// So we create a predicate that tests if the password is ready, and wrap it in a retryable predicate.
Predicate<String> passwordReady = new Predicate<String>() {
@Override
public boolean apply(@Nullable String s) {
if (Strings.isNullOrEmpty(s)) return false;
PasswordData data = client.getPasswordDataInRegion(null, s);
if (data == null) return false;
return !Strings.isNullOrEmpty(data.getPasswordData());
}
};
RetryablePredicate<String> passwordReadyRetryable = new RetryablePredicate<String>(passwordReady, 600, 10, TimeUnit.SECONDS);
assertTrue(passwordReadyRetryable.apply(node.getProviderId()));
// Now pull together Amazon's encrypted password blob, and the private key that jclouds generated
PasswordDataAndPrivateKey dataAndKey = new PasswordDataAndPrivateKey(
client.getPasswordDataInRegion(null, node.getProviderId()),
node.getCredentials().getPrivateKey());
// And apply it to the decryption function
WindowsLoginCredentialsFromEncryptedData f = context.getUtils().getInjector().getInstance(WindowsLoginCredentialsFromEncryptedData.class);
LoginCredentials credentials = f.apply(dataAndKey);
assertEquals(credentials.getUser(), "Administrator");
assertFalse(Strings.isNullOrEmpty(credentials.getPassword()));
} finally {
computeService.destroyNode(node.getId());
}
}
}

View File

@ -25,7 +25,9 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateFactory;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import org.jclouds.encryption.internal.JCECrypto;
@ -61,4 +63,6 @@ public interface Crypto {
MessageDigest sha512();
Cipher cipher(String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException;
}

View File

@ -28,7 +28,10 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import org.jclouds.javax.annotation.Nullable;
import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -86,6 +89,11 @@ public class JCECrypto implements Crypto {
return provider == null ? MessageDigest.getInstance(algorithm) : MessageDigest.getInstance(algorithm, provider);
}
@Override
public Cipher cipher(String algorithm) throws NoSuchAlgorithmException, NoSuchPaddingException {
return provider == null ? Cipher.getInstance(algorithm) : Cipher.getInstance(algorithm, provider);
}
public final static String MD5 = "MD5";
public final static String SHA1 = "SHA1";
public final static String SHA256 = "SHA-256";

View File

@ -18,11 +18,14 @@
*/
package org.jclouds.aws.ec2.compute.strategy;
import static org.testng.Assert.assertEquals;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Predicates;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.inject.Guice;
import org.jclouds.compute.config.BaseComputeServiceContextModule;
import org.jclouds.compute.domain.ImageBuilder;
import org.jclouds.compute.domain.OperatingSystem;
@ -40,14 +43,10 @@ import org.jclouds.json.Json;
import org.jclouds.json.config.GsonModule;
import org.testng.annotations.Test;
import com.google.common.base.Predicates;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import com.google.inject.Guice;
import java.util.Map;
import java.util.Set;
import static org.testng.Assert.assertEquals;
/**
* @author Adrian Cole
@ -68,7 +67,12 @@ public class AWSEC2ImageParserTest {
.description("ubuntu-images-us/ubuntu-hardy-8.04-i386-server-20091130.manifest.xml")
.defaultCredentials(new LoginCredentials("ubuntu", false)).id("us-east-1/ami-7e28ca17")
.providerId("ami-7e28ca17").location(defaultLocation).version("20091130")
.userMetadata(ImmutableMap.of("owner", "099720109477", "rootDeviceType", "instance-store")).build());
.userMetadata(ImmutableMap.of(
"owner", "099720109477",
"rootDeviceType", "instance-store",
"virtualizationType", "paravirtual",
"hypervisor", "xen"))
.build());
assertEquals(
Iterables.get(result, 4),
@ -84,6 +88,7 @@ public class AWSEC2ImageParserTest {
assertEquals(
Iterables.get(result, 6),
new ImageBuilder()
.name("ebs/ubuntu-images/ubuntu-lucid-10.04-i386-server-20100827")
.operatingSystem(
new OperatingSystem.Builder().family(OsFamily.UBUNTU).arch("paravirtual").version("10.04")
.description("099720109477/ebs/ubuntu-images/ubuntu-lucid-10.04-i386-server-20100827")
@ -91,7 +96,12 @@ public class AWSEC2ImageParserTest {
.description("099720109477/ebs/ubuntu-images/ubuntu-lucid-10.04-i386-server-20100827")
.defaultCredentials(new LoginCredentials("ubuntu", false)).id("us-east-1/ami-10f3a255")
.providerId("ami-10f3a255").location(defaultLocation).version("20100827")
.userMetadata(ImmutableMap.of("owner", "099720109477", "rootDeviceType", "ebs")).build());
.userMetadata(ImmutableMap.of(
"owner", "099720109477",
"rootDeviceType", "ebs",
"virtualizationType", "paravirtual",
"hypervisor", "xen"))
.build());
}
@ -120,13 +130,19 @@ public class AWSEC2ImageParserTest {
assertEquals(
Iterables.get(result, 0),
new ImageBuilder()
.name("EC2 CentOS 5.4 HVM AMI")
.operatingSystem(
new OperatingSystem.Builder().family(OsFamily.CENTOS).arch("hvm").version("5.4")
.description("amazon/EC2 CentOS 5.4 HVM AMI").is64Bit(true).build())
.description("EC2 CentOS 5.4 HVM AMI")
.defaultCredentials(new LoginCredentials("root", false)).id("us-east-1/ami-7ea24a17")
.providerId("ami-7ea24a17").location(defaultLocation)
.userMetadata(ImmutableMap.of("owner", "206029621532", "rootDeviceType", "ebs")).build());
.userMetadata(ImmutableMap.of(
"owner", "206029621532",
"rootDeviceType", "ebs",
"virtualizationType", "hvm",
"hypervisor", "xen"))
.build());
}
@ -147,11 +163,11 @@ public class AWSEC2ImageParserTest {
assertEquals(
new Gson().toJson(Iterables.get(result, 1)),
"{\"operatingSystem\":{\"family\":\"UBUNTU\",\"arch\":\"paravirtual\",\"version\":\"9.10\",\"description\":\"411009282317/RightImage_Ubuntu_9.10_x64_v4.5.3_EBS_Alpha\",\"is64Bit\":true},\"version\":\"4.5.3_EBS_Alpha\",\"description\":\"RightImage_Ubuntu_9.10_x64_v4.5.3_EBS_Alpha\",\"defaultCredentials\":{\"authenticateSudo\":false,\"identity\":\"root\"},\"id\":\"us-east-1/ami-c19db6b5\",\"type\":\"IMAGE\",\"tags\":[],\"providerId\":\"ami-c19db6b5\",\"location\":{\"scope\":\"REGION\",\"id\":\"us-east-1\",\"description\":\"us-east-1\",\"iso3166Codes\":[],\"metadata\":{}},\"userMetadata\":{\"owner\":\"411009282317\",\"rootDeviceType\":\"ebs\",\"virtualizationType\":\"paravirtual\",\"hypervisor\":\"xen\"}}");
"{\"operatingSystem\":{\"family\":\"UBUNTU\",\"arch\":\"paravirtual\",\"version\":\"9.10\",\"description\":\"411009282317/RightImage_Ubuntu_9.10_x64_v4.5.3_EBS_Alpha\",\"is64Bit\":true},\"version\":\"4.5.3_EBS_Alpha\",\"description\":\"RightImage_Ubuntu_9.10_x64_v4.5.3_EBS_Alpha\",\"defaultCredentials\":{\"authenticateSudo\":false,\"identity\":\"root\"},\"id\":\"us-east-1/ami-c19db6b5\",\"type\":\"IMAGE\",\"tags\":[],\"providerId\":\"ami-c19db6b5\",\"name\":\"RightImage_Ubuntu_9.10_x64_v4.5.3_EBS_Alpha\",\"location\":{\"scope\":\"REGION\",\"id\":\"us-east-1\",\"description\":\"us-east-1\",\"iso3166Codes\":[],\"metadata\":{}},\"userMetadata\":{\"owner\":\"411009282317\",\"rootDeviceType\":\"ebs\",\"virtualizationType\":\"paravirtual\",\"hypervisor\":\"xen\"}}");
assertEquals(
new Gson().toJson(Iterables.get(result, 2)),
"{\"operatingSystem\":{\"family\":\"WINDOWS\",\"arch\":\"hvm\",\"version\":\"2003\",\"description\":\"411009282317/RightImage Windows_2003_i386_v5.4.3\",\"is64Bit\":false},\"version\":\"5.4.3\",\"description\":\"Built by RightScale\",\"defaultCredentials\":{\"authenticateSudo\":false,\"identity\":\"root\"},\"id\":\"us-east-1/ami-710c2605\",\"type\":\"IMAGE\",\"tags\":[],\"providerId\":\"ami-710c2605\",\"location\":{\"scope\":\"REGION\",\"id\":\"us-east-1\",\"description\":\"us-east-1\",\"iso3166Codes\":[],\"metadata\":{}},\"userMetadata\":{\"owner\":\"411009282317\",\"rootDeviceType\":\"ebs\",\"virtualizationType\":\"hvm\",\"hypervisor\":\"xen\"}}");
"{\"operatingSystem\":{\"family\":\"WINDOWS\",\"arch\":\"hvm\",\"version\":\"2003\",\"description\":\"411009282317/RightImage Windows_2003_i386_v5.4.3\",\"is64Bit\":false},\"version\":\"5.4.3\",\"description\":\"Built by RightScale\",\"defaultCredentials\":{\"authenticateSudo\":false,\"identity\":\"root\"},\"id\":\"us-east-1/ami-710c2605\",\"type\":\"IMAGE\",\"tags\":[],\"providerId\":\"ami-710c2605\",\"name\":\"RightImage Windows_2003_i386_v5.4.3\",\"location\":{\"scope\":\"REGION\",\"id\":\"us-east-1\",\"description\":\"us-east-1\",\"iso3166Codes\":[],\"metadata\":{}},\"userMetadata\":{\"owner\":\"411009282317\",\"rootDeviceType\":\"ebs\",\"virtualizationType\":\"hvm\",\"hypervisor\":\"xen\"}}");
}
public void testParseAmznImage() {
@ -161,17 +177,24 @@ public class AWSEC2ImageParserTest {
assertEquals(
Iterables.get(result, 0),
new ImageBuilder()
.name("amzn-ami-0.9.7-beta.i386-ebs")
.operatingSystem(
new OperatingSystem.Builder().family(OsFamily.AMZN_LINUX).arch("paravirtual")
.version("0.9.7-beta").description("137112412989/amzn-ami-0.9.7-beta.i386-ebs")
.is64Bit(false).build()).description("Amazon")
.defaultCredentials(new LoginCredentials("ec2-user", false)).id("us-east-1/ami-82e4b5c7")
.providerId("ami-82e4b5c7").location(defaultLocation).version("0.9.7-beta")
.userMetadata(ImmutableMap.of("owner", "137112412989", "rootDeviceType", "ebs")).build());
.userMetadata(ImmutableMap.of(
"owner", "137112412989",
"rootDeviceType", "ebs",
"virtualizationType", "paravirtual",
"hypervisor", "xen"))
.build());
assertEquals(
Iterables.get(result, 3),
new ImageBuilder()
.name("amzn-ami-0.9.7-beta.x86_64-S3")
.operatingSystem(
new OperatingSystem.Builder().family(OsFamily.AMZN_LINUX).arch("paravirtual")
.version("0.9.7-beta")
@ -179,7 +202,12 @@ public class AWSEC2ImageParserTest {
.build()).description("Amazon Linux AMI x86_64 S3")
.defaultCredentials(new LoginCredentials("ec2-user", false)).id("us-east-1/ami-f2e4b5b7")
.providerId("ami-f2e4b5b7").location(defaultLocation).version("0.9.7-beta")
.userMetadata(ImmutableMap.of("owner", "137112412989", "rootDeviceType", "ebs")).build());
.userMetadata(ImmutableMap.of(
"owner", "137112412989",
"rootDeviceType", "ebs",
"virtualizationType", "paravirtual",
"hypervisor", "xen"))
.build());
}
static Location defaultLocation = new LocationBuilder().scope(LocationScope.REGION).id("us-east-1")