Skip to content
This repository was archived by the owner on Jan 2, 2019. It is now read-only.

Commit 5fb4abc

Browse files
EarthmanTearthmant
authored andcommitted
Support password
1 parent 037a462 commit 5fb4abc

File tree

5 files changed

+121
-3
lines changed

5 files changed

+121
-3
lines changed

CHANGELOG.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
2.4.0: Support Using Password with Windows Agents.
12
2.3.5: Support EC2-Classic Security Groups.
23
2.3.4: Verify resource ID exists on existing resources.
34
2.3.3: Support connecting ENI to Security Group via relationships.

cloudify_awssdk/ec2/decrypt.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# https://github.com/tomrittervg/decrypt-windows-ec2-passwd/blob/master/decrypt-windows-ec2-passwd.py
2+
3+
import base64
4+
import binascii
5+
6+
7+
def pkcs1_unpad(text):
8+
# From http://kfalck.net/2011/03/07/decoding-pkcs1-padding-in-python
9+
if len(text) > 0 and text[0] == '\x02':
10+
# Find end of padding marked by nul
11+
pos = text.find('\x00')
12+
if pos > 0:
13+
return text[pos + 1:]
14+
return None
15+
16+
17+
def long_to_bytes(val, endianness='big'):
18+
# From http://stackoverflow.com/questions/8730927/
19+
# convert-python-long-int-to-fixed-size-byte-array
20+
21+
# one (1) hex digit per four (4) bits
22+
try:
23+
# Python < 2.7 doesn't have bit_length =(
24+
width = val.bit_length()
25+
except Exception:
26+
width = len(val.__hex__()[2:-1]) * 4
27+
28+
# unhexlify wants an even multiple of eight (8) bits, but we don't
29+
# want more digits than we need (hence the ternary-ish 'or')
30+
width += 8 - ((width % 8) or 8)
31+
32+
# format width specifier: four (4) bits per hex digit
33+
fmt = '%%0%dx' % (width // 4)
34+
35+
# prepend zero (0) to the width, to zero-pad the output
36+
s = binascii.unhexlify(fmt % val)
37+
38+
if endianness == 'little':
39+
# see http://stackoverflow.com/a/931095/309233
40+
s = s[::-1]
41+
42+
return s
43+
44+
45+
def decrypt_password(rsaKey, password):
46+
# Undo the whatever-they-do to the ciphertext to get the integer
47+
encryptedData = base64.b64decode(password)
48+
ciphertext = int(binascii.hexlify(encryptedData), 16)
49+
50+
# Decrypt it
51+
plaintext = rsaKey.decrypt(ciphertext)
52+
53+
# This is the annoying part. long -> byte array
54+
decryptedData = long_to_bytes(plaintext)
55+
# Now Unpad it
56+
unpaddedData = pkcs1_unpad(decryptedData)
57+
58+
# Done
59+
return unpaddedData

cloudify_awssdk/ec2/resources/instances.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@
1919
'''
2020

2121
# Common
22+
from Crypto.PublicKey import RSA
2223
from collections import defaultdict
2324
import json
25+
import os
2426

2527
# Cloudify
2628
from cloudify import compute
@@ -29,6 +31,8 @@
2931
from cloudify_awssdk.common import decorators, utils
3032
from cloudify_awssdk.common.constants import EXTERNAL_RESOURCE_ID
3133
from cloudify_awssdk.ec2 import EC2Base
34+
from cloudify_awssdk.ec2.decrypt import decrypt_password
35+
3236
# Boto
3337
from botocore.exceptions import ClientError, ParamValidationError
3438

@@ -46,6 +50,7 @@
4650
GROUP_TYPE = 'cloudify.nodes.aws.ec2.SecurityGroup'
4751
NETWORK_INTERFACE_TYPE = 'cloudify.nodes.aws.ec2.Interface'
4852
SUBNET_TYPE = 'cloudify.nodes.aws.ec2.Subnet'
53+
KEY_TYPE = 'cloudify.nodes.aws.ec2.Keypair'
4954
GROUPIDS = 'SecurityGroupIds'
5055
NETWORK_INTERFACES = 'NetworkInterfaces'
5156
SUBNET_ID = 'SubnetId'
@@ -145,6 +150,17 @@ def modify_instance_attribute(self, params):
145150
self.logger.debug('Response: {0}'.format(res))
146151
return res
147152

153+
def get_password(self, params):
154+
'''
155+
Modify attribute of AWS EC2 Instances.
156+
'''
157+
self.logger.debug(
158+
'Getting {0} password with parameters: {1}'.format(
159+
self.type_name, params))
160+
res = self.client.get_password_data(**params)
161+
self.logger.debug('Response: {0}'.format(res))
162+
return res
163+
148164

149165
@decorators.aws_resource(EC2Instances, resource_type=RESOURCE_TYPE)
150166
def prepare(ctx, iface, resource_config, **_):
@@ -265,6 +281,10 @@ def start(ctx, iface, resource_config, **_):
265281
ctx.instance.runtime_properties['ip'] = ip
266282
ctx.instance.runtime_properties['public_ip_address'] = pip
267283
ctx.instance.runtime_properties['private_ip_address'] = ip
284+
if not _handle_password(iface):
285+
raise OperationRetry(
286+
'{0} ID# {1} password is empty.'.format(
287+
iface.type_name, iface.resource_id))
268288
return
269289

270290
elif ctx.operation.retry_number == 0:
@@ -412,3 +432,37 @@ def _handle_userdata(existing_userdata):
412432
[existing_userdata, install_agent_userdata])
413433

414434
return final_userdata
435+
436+
437+
def _handle_password(iface):
438+
if not ctx.node.properties['use_password']:
439+
return True
440+
key_data = ctx.node.properties['agent_config'].get('key')
441+
if not key_data:
442+
rel = utils.find_rel_by_node_type(ctx.instance, KEY_TYPE)
443+
if rel:
444+
key_data = \
445+
rel.target.instance.runtime_properties.get(
446+
'create_response', {}).get('KeyMaterial')
447+
if not key_data:
448+
raise NonRecoverableError(
449+
'No key_data was provided in agent config property or rel.')
450+
if os.path.exists(key_data):
451+
with open(key_data, 'r') as outfile:
452+
key_data = outfile.readlines()
453+
password_data = iface.get_password(
454+
{
455+
'InstanceId': ctx.instance.runtime_properties[EXTERNAL_RESOURCE_ID]
456+
}
457+
)
458+
if not isinstance(password_data, dict):
459+
return False
460+
encrypted_password = password_data.get('PasswordData')
461+
if not encrypted_password:
462+
ctx.logger.error('password_data is {0}'.format(password_data))
463+
return False
464+
key = RSA.importKey(key_data)
465+
password = decrypt_password(key, encrypted_password)
466+
ctx.instance.runtime_properties['password'] = \
467+
password
468+
return True

plugin.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ plugins:
22

33
awssdk:
44
executor: central_deployment_agent
5-
source: https://github.com/cloudify-incubator/cloudify-awssdk-plugin/archive/2.3.5.zip
5+
source: https://github.com/cloudify-incubator/cloudify-awssdk-plugin/archive/2.4.0.dev6.zip
66
package_name: cloudify-awssdk-plugin
7-
package_version: '2.3.5'
7+
package_version: '2.4.0.dev6'
88

99
data_types:
1010

@@ -1767,6 +1767,10 @@ node_types:
17671767
Tells the deployment to use the public IP (if available) of the resource
17681768
for Cloudify Agent connections
17691769
default: false
1770+
use_password:
1771+
type: boolean
1772+
description: Whether to use a password for agent communication.
1773+
default: false
17701774
interfaces:
17711775
cloudify.interfaces.lifecycle:
17721776
create:

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
setup(
2222
name='cloudify-awssdk-plugin',
23-
version='2.3.5',
23+
version='2.4.0.dev6',
2424
license='LICENSE',
2525
packages=find_packages(exclude=['tests*']),
2626
description='A Cloudify plugin for AWS',

0 commit comments

Comments
 (0)