diff --git a/apis/ec2/pom.xml b/apis/ec2/pom.xml
index ccaffbf752..24e64f9bcc 100644
--- a/apis/ec2/pom.xml
+++ b/apis/ec2/pom.xml
@@ -83,6 +83,12 @@
${project.version}
test
+
+ org.jclouds.driver
+ jclouds-bouncycastle
+ ${project.version}
+ test
+
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceDependenciesModule.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceDependenciesModule.java
index a97476765f..6281f63be4 100644
--- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceDependenciesModule.java
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/config/EC2ComputeServiceDependenciesModule.java
@@ -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() {
}).to(new TypeLiteral>() {
}).in(Scopes.SINGLETON);
+ bind(WindowsLoginCredentialsFromEncryptedData.class);
}
/**
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/domain/PasswordDataAndPrivateKey.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/domain/PasswordDataAndPrivateKey.java
new file mode 100644
index 0000000000..7cee4b7228
--- /dev/null
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/domain/PasswordDataAndPrivateKey.java
@@ -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;
+ }
+}
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/EC2ImageParser.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/EC2ImageParser.java
index 1cdaf72b5e..7f14e25ad5 100644
--- a/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/EC2ImageParser.java
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/EC2ImageParser.java
@@ -86,6 +86,7 @@ public class EC2ImageParser implements Function builder().put("owner", from.getImageOwnerId()).put(
"rootDeviceType", from.getRootDeviceType().value()).put("virtualizationType",
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/WindowsLoginCredentialsFromEncryptedData.java b/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/WindowsLoginCredentialsFromEncryptedData.java
new file mode 100644
index 0000000000..5873b73f12
--- /dev/null
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/compute/functions/WindowsLoginCredentialsFromEncryptedData.java
@@ -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 {
+
+ 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);
+ }
+ }
+}
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/domain/PasswordData.java b/apis/ec2/src/main/java/org/jclouds/ec2/domain/PasswordData.java
new file mode 100644
index 0000000000..cea5639dd5
--- /dev/null
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/domain/PasswordData.java
@@ -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 + '\'' +
+ '}';
+ }
+}
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/services/WindowsAsyncClient.java b/apis/ec2/src/main/java/org/jclouds/ec2/services/WindowsAsyncClient.java
index 5710656611..305310bc08 100644
--- a/apis/ec2/src/main/java/org/jclouds/ec2/services/WindowsAsyncClient.java
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/services/WindowsAsyncClient.java
@@ -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 getPasswordData(
+ @EndpointParam(parser = RegionToEndpointOrProviderIfNull.class) @Nullable String region,
+ @FormParam("InstanceId") String instanceId);
+
}
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/services/WindowsClient.java b/apis/ec2/src/main/java/org/jclouds/ec2/services/WindowsClient.java
index ae04093cea..5a1af0e0ad 100644
--- a/apis/ec2/src/main/java/org/jclouds/ec2/services/WindowsClient.java
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/services/WindowsClient.java
@@ -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 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
+ */
+ PasswordData getPasswordDataInRegion(@Nullable String region, String instanceId);
}
diff --git a/apis/ec2/src/main/java/org/jclouds/ec2/xml/GetPasswordDataResponseHandler.java b/apis/ec2/src/main/java/org/jclouds/ec2/xml/GetPasswordDataResponseHandler.java
new file mode 100644
index 0000000000..3cdd7d80e9
--- /dev/null
+++ b/apis/ec2/src/main/java/org/jclouds/ec2/xml/GetPasswordDataResponseHandler.java
@@ -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 {
+
+ 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);
+ }
+
+}
diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/compute/functions/EC2ImageParserTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/compute/functions/EC2ImageParserTest.java
index 54dafaa650..147210fdcc 100644
--- a/apis/ec2/src/test/java/org/jclouds/ec2/compute/functions/EC2ImageParserTest.java
+++ b/apis/ec2/src/test/java/org/jclouds/ec2/compute/functions/EC2ImageParserTest.java
@@ -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(
diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/compute/functions/WindowsLoginCredentialsFromEncryptedDataTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/compute/functions/WindowsLoginCredentialsFromEncryptedDataTest.java
new file mode 100644
index 0000000000..e8ebd12c36
--- /dev/null
+++ b/apis/ec2/src/test/java/org/jclouds/ec2/compute/functions/WindowsLoginCredentialsFromEncryptedDataTest.java
@@ -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());
+ }
+}
diff --git a/apis/ec2/src/test/java/org/jclouds/ec2/services/WindowsClientLiveTest.java b/apis/ec2/src/test/java/org/jclouds/ec2/services/WindowsClientLiveTest.java
index a11244ec77..3f1b888eaf 100644
--- a/apis/ec2/src/test/java/org/jclouds/ec2/services/WindowsClientLiveTest.java
+++ b/apis/ec2/src/test/java/org/jclouds/ec2/services/WindowsClientLiveTest.java
@@ -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 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. of(new Log4JLoggingModule()), overrides).getProviderSpecificContext();
+ overrides.put(EC2Constants.PROPERTY_EC2_AMI_OWNERS, "206029621532"); /* Amazon Owner ID */
+ ComputeServiceContext serviceContext = new ComputeServiceContextFactory(setupRestProperties()).createContext(provider,
+ ImmutableSet.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 passwordReady = new Predicate() {
+ @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 passwordReadyRetryable = new RetryablePredicate(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());
+ }
+ }
+
}
diff --git a/core/src/main/java/org/jclouds/crypto/Crypto.java b/core/src/main/java/org/jclouds/crypto/Crypto.java
index 9c17654817..b6617c7ce7 100644
--- a/core/src/main/java/org/jclouds/crypto/Crypto.java
+++ b/core/src/main/java/org/jclouds/crypto/Crypto.java
@@ -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;
+
}
\ No newline at end of file
diff --git a/core/src/main/java/org/jclouds/encryption/internal/JCECrypto.java b/core/src/main/java/org/jclouds/encryption/internal/JCECrypto.java
index 4efa54abe0..4c6dfd6fee 100644
--- a/core/src/main/java/org/jclouds/encryption/internal/JCECrypto.java
+++ b/core/src/main/java/org/jclouds/encryption/internal/JCECrypto.java
@@ -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";
diff --git a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ImageParserTest.java b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ImageParserTest.java
index 7e2498a842..e21dbd8d01 100644
--- a/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ImageParserTest.java
+++ b/providers/aws-ec2/src/test/java/org/jclouds/aws/ec2/compute/strategy/AWSEC2ImageParserTest.java
@@ -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")