This repository has been archived by the owner on Nov 15, 2024. It is now read-only.
forked from aws/aws-cli
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding a --priv-launch-key parameter to get-password-data which allow…
…s the encrypted password to be decrypted using the supplied SSH private key file.
- Loading branch information
Showing
8 changed files
with
253 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"). You | ||
# may not use this file except in compliance with the License. A copy of | ||
# the License is located at | ||
# | ||
# http://aws.amazon.com/apache2.0/ | ||
# | ||
# or in the "license" file accompanying this file. This file 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. | ||
import logging | ||
import os | ||
import base64 | ||
import rsa | ||
import six | ||
|
||
from awscli.clidriver import BaseCLIArgument | ||
from botocore.parameters import StringParameter | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
HELP = """<p>The file that contains the private key used to launch | ||
the instance (e.g. windows-keypair.pem). If this is supplied, the | ||
password data sent from EC2 will be decrypted before display.</p>""" | ||
|
||
|
||
# This is a bit kludgy. | ||
# We need a way to pass some state between the event handlers. | ||
# One event handler determines if a path to the private key | ||
# file was specified and, if so, validates the path. | ||
# The other event handler attempts to decrypt the password data | ||
# if a private key path was specified. | ||
# I'm using the module-level globalvar to maintain that shared | ||
# state. In the context of a CLI, I think that's okay. | ||
|
||
_key_path = None | ||
|
||
def _set_key_path(path): | ||
global _key_path | ||
_key_path = path | ||
|
||
def _get_key_path(): | ||
global _key_path | ||
return _key_path | ||
|
||
|
||
def decrypt_password_data(event_name, shape, value, **kwargs): | ||
""" | ||
This handler gets called after the PasswordData element of the | ||
response has been parsed. It checks to see if a private launch | ||
key was specified on the command. If it was, it tries to use | ||
that private key to decrypt the password data and return it. | ||
""" | ||
key_path = _get_key_path() | ||
if key_path: | ||
logger.debug('decrypt_password_data: %s', key_path) | ||
try: | ||
private_key_file = open(key_path) | ||
private_key_contents = private_key_file.read() | ||
private_key_file.close() | ||
private_key = rsa.PrivateKey.load_pkcs1(six.b(private_key_contents)) | ||
value = base64.b64decode(value) | ||
value = rsa.decrypt(value, private_key) | ||
except: | ||
# TODO | ||
# Should we raise an exception or just return the | ||
# unencrypted data? Or maybe just print a message? | ||
logger.debug('Unable to decrypt PasswordData', exc_info=True) | ||
msg = ('Unable to decrypt password data using ' | ||
'provided private key file.') | ||
raise ValueError(msg) | ||
return value | ||
|
||
|
||
def ec2_add_priv_launch_key(argument_table, operation, **kwargs): | ||
""" | ||
This handler gets called after the argument table for the | ||
operation has been created. It's job is to add the | ||
``priv-launch-key`` parameter. | ||
""" | ||
argument_table['priv-launch-key'] = LaunchKeyArgument(operation, | ||
'priv-launch-key') | ||
|
||
|
||
def ec2_process_priv_launch_key(operation, parsed_args, **kwargs): | ||
""" | ||
This handler gets called after the command line arguments to | ||
the ``get-password-data`` command have been parsed. It is | ||
passed the ``Operation`` object and the ``Namespace`` containing | ||
the parsed args. | ||
It needs to check to see if ``priv-launch-key`` was supplied | ||
by the user. If it was, it checks to make sure the path provided | ||
points to a real file and, if so, stores the path in the module | ||
global var for access later by the decrypt method. | ||
""" | ||
if parsed_args.priv_launch_key: | ||
path = os.path.expandvars(parsed_args.priv_launch_key) | ||
path = os.path.expanduser(path) | ||
logger.debug(path) | ||
if os.path.isfile(path): | ||
_set_key_path(path) | ||
else: | ||
msg = ('priv-launch-key should be a path to the ' | ||
'local SSH private key file used to launch ' | ||
'the instance.') | ||
raise ValueError(msg) | ||
|
||
|
||
class LaunchKeyArgument(BaseCLIArgument): | ||
|
||
def __init__(self, operation, name): | ||
param = StringParameter(operation, | ||
name='priv_launch_key', | ||
type='string') | ||
super(LaunchKeyArgument, self).__init__( | ||
name, argument_object=param) | ||
self._operation = operation | ||
self._name = name | ||
|
||
@property | ||
def cli_name(self): | ||
return '--' + self._name | ||
|
||
@property | ||
def cli_type_name(self): | ||
return 'string' | ||
|
||
@property | ||
def required(self): | ||
return False | ||
|
||
@property | ||
def documentation(self): | ||
return HELP | ||
|
||
def add_to_parser(self, parser, cli_name=None): | ||
parser.add_argument(self.cli_name, metavar=self.py_name, | ||
help='Number of instances to launch') | ||
|
||
def add_to_params(self, parameters, value): | ||
""" | ||
Since the extra ``priv-launch-key`` parameter is local and | ||
doesn't need to be sent to the service, we don't have to do | ||
anything here. | ||
""" | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,3 +11,4 @@ nose==1.3.0 | |
colorama==0.2.5 | ||
mock==1.0.1 | ||
httpretty==0.6.1 | ||
rsa==3.1.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
#!/usr/bin/env python | ||
# Copyright 2012-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"). You | ||
# may not use this file except in compliance with the License. A copy of | ||
# the License is located at | ||
# | ||
# http://aws.amazon.com/apache2.0/ | ||
# | ||
# or in the "license" file accompanying this file. This file 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. | ||
from tests.unit import BaseAWSCommandParamsTest | ||
import os | ||
from awscli.clidriver import create_clidriver | ||
from awscli.customizations.ec2decryptpassword import _get_key_path | ||
from botocore.response import XmlResponse | ||
|
||
GET_PASSWORD_DATA_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?> | ||
<GetPasswordDataResponse xmlns="http://ec2.amazonaws.com/doc/2013-02-01/"> | ||
<requestId>000000000000</requestId> | ||
<instanceId>i-12345678</instanceId> | ||
<timestamp>2013-07-27T18:29:23.000Z</timestamp> | ||
<passwordData>
 | ||
GWDnuoj/7pbMQkg125E8oGMUVCI+r98sGbFFl8SX+dEYxMZzz+byYwwjvyg8iSGKaLuLTIWatWopVu5cMWDKH65U4YFL2g3LqyajBrCFnuSE1piTeS/rPQpoSvBN5FGj9HWqNrglWAJgh9OZNSGgpEojBenL/0rwSpDWL7f/f52M5doYA6q+v0ygEoi1Wq6hcmrBfyA4seW1RlKgnUru5Y9oc1hFHi53E3b1EkjGqCsCemVUwumBj8uwCLJRaMcqrCxK1smtAsiSqk0Jk9jpN2vcQgnMPypEdmEEXyWHwq55fjy6ch+sqYcwumIL5QcFW2JQ5+XBEoFhC66gOsAXow==
 | ||
</passwordData> | ||
</GetPasswordDataResponse>""" | ||
|
||
|
||
class TestGetPasswordData(BaseAWSCommandParamsTest): | ||
|
||
prefix = 'ec2 get-password-data' | ||
|
||
def test_no_priv_launch_key(self): | ||
args = ' --instance-id i-12345678' | ||
cmdline = self.prefix + args | ||
result = {'InstanceId': 'i-12345678'} | ||
self.assert_params_for_cmd(cmdline, result) | ||
|
||
def test_nonexistent_priv_launch_key(self): | ||
args = ' --instance-id i-12345678 --priv-launch-key foo.pem' | ||
cmdline = self.prefix + args | ||
result = {} | ||
self.assert_params_for_cmd(cmdline, result, expected_rc=255) | ||
|
||
def test_priv_launch_key(self): | ||
driver = create_clidriver() | ||
key_path = os.path.join(os.path.dirname(__file__), | ||
'testcli.pem') | ||
args = ' --instance-id i-12345678 --priv-launch-key %s' % key_path | ||
cmdline = self.prefix + args | ||
cmdlist = cmdline.split() | ||
rc = driver.main(cmdlist) | ||
self.assertEqual(rc, 0) | ||
self.assertEqual(key_path, _get_key_path()) | ||
service = driver.session.get_service('ec2') | ||
operation = service.get_operation('GetPasswordData') | ||
r = XmlResponse(driver.session, operation) | ||
r.parse(GET_PASSWORD_DATA_RESPONSE, 'utf-8') | ||
response_data = r.get_value() | ||
self.assertEqual(response_data['PasswordData'], '=mG8.r$o-s') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
-----BEGIN RSA PRIVATE KEY----- | ||
MIIEowIBAAKCAQEAiaf6VsJ5w7emzh+6ffFj31ropPeW2N8wnNtfkItp4gmCdCpTQA4Kce/tzk5V | ||
VPTaPyanYXcdwib4KXqf77dSACxpqtel7jwotdLLNU9uUm9pIFsVh0qnLJxXdgJgujsY/HBq4BRE | ||
l7W1uA0e7QD1DH3QImELEXmnwO9ym/CiuwpUYGYrLLwIaezGjv/XUsn/KMzR2cQsseQu+jiBWXhA | ||
+4plnC4aNOWCXym/VlTgRDaGL8FijEzfiL+k4ZrG6/qakAt1xDkB8xGY4yOCexnL80Pi31ybT7QG | ||
odhuIkUr1CCgakvBpY1y8X8/yXmwZZtzl/qS47cIz9OUMjeFsfBBZwIDAQABAoIBAEXNN9PmqXfl | ||
GGBNFnPmg44uuulr4sH16uCfHMZe60IDMHNXQv+oHwPHdf63Ge4KeuCq6RUzIZPhztS5qYAUpTAR | ||
VUOcNjenqb0JNqHBtV93vwb5KOGBqWOlo3PjoMjOTs0y8/7MSDvlmE/L13K2mYvMAE5uhv5FghsD | ||
UEpiqyHMSl4gGqOxz1SbOLvmSdlmlLHW9qjrT+oEoqZX/nfQyJHk0zoZlfTJdnBL5R1GtTP2mI/E | ||
GCy4z7qllmGJR4x24GZxNAggr3mWvmkaVxivO2+3FN1TtwGJo/Wj5ncZLv889TXzj2V8HOcoZ8L/ | ||
GsZGxdJ5rWrxRLQySePSUKze+gECgYEA27n4p6uJLF8Ra25nQmgs2/VpzM1K0fd1tX3yKCYFGwbl | ||
45Lzi7a672GQryAmSVWSfCgf9uwZmVWcPW1gZT2pBqP1+8EF1T2L6yPnGR1psF9jMmWGJogFlM4o | ||
P++Ym3SHT4bd0we1iUnEH6JS65BEmopVhM8/7ZP50WHibDddoRcCgYEAoGGSLRLpvwczgOlirNSM | ||
RCsJ9uAWUBZYMO1XMoAet0CWat9YwOqiiavcsD+0bNH0bZISQrLtaoiDSdFpRYjcgpBeu4MaGGqJ | ||
EXYUGxNwmY1TZ3ml+9Oh1I84p0XIALeCueEFH9lJaXHzdhjNDn+KxKUg7UIz72+v3n/AyhvqdDEC | ||
gYEAsyUwR7xCreug7z9nbywyju/LYBBtFT22OdBC9FrzRLLeEirI6LuGNBAO/8mtjZL4SMQKM68R | ||
vAOhzC92LXUVb3WU47rff5mbj46JJ9/kQMm0ve0qcBXsvwNKq740ZWKfw8ZI63rYluOOxN/6zVal | ||
qH5q9Upoa9J/FyjAi8ykSOcCgYBJknjsFHEGINePm4CYqChwXQ4FImcZ9iYey8HkeMGebxKRlEOy | ||
u/A0F5L1h0PNZ8MpQIj/7/TZmiYgBuCz9USy4GeUvV+LM9QNHo26ngBZcGuCXFu4Wi0yxUDH+0r0 | ||
iTp+6qrfIV578LouwtHOhNOzwcyJCoWooSOcfh6CmKvFAQKBgH+6tVQi9Tj4Ig15HueURVVeiPVn | ||
L9MPiR4unUsPz6OevoNsRaDkj7XDFBuMiFPGoXoyL9SedSSFw+oRxcPi4YMMvsZNC0yxOnj2Qe8z | ||
V1Xgvb+rlN6NEIlPcscxE2F8gu1zIan/lXLMEXMr9hAAaGqS/JSALzt6XcoHwzEGnTrH | ||
-----END RSA PRIVATE KEY----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters