forked from awslabs/aws-systems-manager
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* added automation document to attach a role to an ec2 instance * added test case where an instance is already associated to a role * added optional lambda role for automation cloudformation * changed lambda role policy to have more specific actions
- Loading branch information
1 parent
f3a9106
commit a95a540
Showing
11 changed files
with
943 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# Attach an IAM role to an Instance | ||
|
||
## Notes | ||
|
||
To associate a role to an instance, we have to create an instance profile. | ||
By AWS Design, we can only have one role associated to an instance profile. | ||
So the new instance profile name will be the name of the role. | ||
Then we can associate the instance to an instance profile. | ||
|
||
There are two things to consider about attaching a role to an instance | ||
1. Instance is already associated to a role | ||
2. Instance is not associated to any role | ||
|
||
## Document Design | ||
|
||
Refer to schema.json | ||
|
||
Document Steps: | ||
1. aws:createStack - Execute CloudFormation Template to create lambda. | ||
* Inputs: | ||
* StackName: {{DocumentStackName}} - Stack name or Unique ID | ||
* Parameters: | ||
* LambdaRole: {{LambdaAssumeRole}} - role assumed by lambda | ||
* LambdaName: UpdateCFTemplate-{{automation:EXECUTION_ID}} | ||
2. aws:invokeLambdaFunction - Execute Lambda to detach the volume | ||
* Inputs: | ||
* FunctionName: AttachIAMToInstanceLambda-{{automation:EXECUTION_ID}} - Lambda name to use | ||
* Payload: | ||
* Instance: {{Instance}} - The ID of the instance. | ||
* RoleName: {{RoleName}} - Role Name to associate the Instance | ||
3. aws:deleteStack - Delete CloudFormation Template. | ||
* Inputs: | ||
* StackName: {{DocumentStackName}} - Stack name or Unique ID | ||
|
||
## Test script | ||
|
||
Instance is already associated to a role: | ||
1. Create a test stack with an instance already associated to a role | ||
2. Execute automation document to attach the role to an instance | ||
3. Verify instance profile is replaced | ||
4. Clean up test stack | ||
|
||
Instance is not associated to a role: | ||
1. Create a test stack with an instance that is not associated to any role | ||
2. Execute automation document to attach the role to an instance | ||
3. Verify instance is associated to the role | ||
4. Clean up test stack | ||
|
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,30 @@ | ||
{ | ||
"description": "Attach IAM to Instance", | ||
"schemaVersion": "0.3", | ||
"assumeRole": "{{ AutomationAssumeRole }}", | ||
"parameters": { | ||
"InstanceId": { | ||
"type": "String", | ||
"description": "(Required) The ID of the instance." | ||
}, | ||
"RoleName": { | ||
"type": "String", | ||
"description": "(Required) Role Name to add" | ||
}, | ||
"LambdaAssumeRole": { | ||
"type": "String", | ||
"description": "(Optional) The ARN of the role assumed by lambda", | ||
"default": "" | ||
}, | ||
"AutomationAssumeRole": { | ||
"type": "String", | ||
"description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf. ", | ||
"default": "" | ||
} | ||
}, | ||
"mainSteps": [], | ||
"outputs":[ | ||
"attachIAMToInstance.LogResult", | ||
"attachIAMToInstance.Payload" | ||
] | ||
} |
58 changes: 58 additions & 0 deletions
58
...n/AttachIAMToInstance/Documents/CloudFormationTemplates/AttachIAMToInstanceCFTemplate.yml
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,58 @@ | ||
--- | ||
AWSTemplateFormatVersion: '2010-09-09' | ||
Parameters: | ||
LambdaRoleArn: | ||
Description: > | ||
The ARN of the role that allows Lambda created by Automation to perform the action on your behalf | ||
Type: String | ||
Default: "" | ||
LambdaName: | ||
Description: > | ||
The lambda function name | ||
Type: String | ||
Conditions: | ||
LambdaAssumeRoleNotSpecified: | ||
!Or | ||
- !Equals [!Ref LambdaRoleArn, ''] | ||
- !Equals [!Ref LambdaRoleArn, 'undefined'] | ||
Resources: | ||
# Assume role used by the lambda function (only created if not passed in) | ||
LambdaRole: | ||
Type: AWS::IAM::Role | ||
Condition: LambdaAssumeRoleNotSpecified | ||
Properties: | ||
AssumeRolePolicyDocument: | ||
Version: "2012-10-17" | ||
Statement: | ||
- Action: ["sts:AssumeRole"] | ||
Effect: "Allow" | ||
Principal: | ||
Service: | ||
- lambda.amazonaws.com | ||
Policies: | ||
- PolicyName: AttachIAMToInstanceLambdaPolicy | ||
PolicyDocument: | ||
Version: "2012-10-17" | ||
Statement: | ||
Action: | ||
- ec2:AssociateIamInstanceProfile | ||
- ec2:DescribeIamInstanceProfileAssociations | ||
- ec2:DisassociateIamInstanceProfile | ||
- iam:AddRoleToInstanceProfile | ||
- iam:CreateInstanceProfile | ||
- iam:ListInstanceProfilesForRole | ||
- iam:PassRole | ||
Effect: Allow | ||
Resource: "*" | ||
Path: "/" | ||
AttachIAMToInstanceLambda: | ||
Type: AWS::Lambda::Function | ||
Properties: | ||
Code: | ||
ZipFile: "{}" | ||
FunctionName: !Ref LambdaName | ||
Role: !If ["LambdaAssumeRoleNotSpecified", !GetAtt LambdaRole.Arn, !Ref LambdaRoleArn] | ||
Timeout: 60 | ||
Handler: "index.handler" | ||
Runtime: python2.7 | ||
MemorySize: 128 |
85 changes: 85 additions & 0 deletions
85
Automation/AttachIAMToInstance/Documents/Lambdas/attach_iam_to_instance.py
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,85 @@ | ||
import boto3 | ||
import time | ||
import logging | ||
|
||
logger = logging.getLogger() | ||
logger.setLevel(logging.INFO) | ||
|
||
iam_client = boto3.client('iam') | ||
ec2_client = boto3.client('ec2') | ||
|
||
|
||
def find_or_create_instance_profile(role_name): | ||
response = iam_client.list_instance_profiles_for_role(RoleName=role_name) | ||
if len(response['InstanceProfiles']) != 0: | ||
logger.info("Instance profile with role " + role_name + " already exists") | ||
instance_profile = response['InstanceProfiles'][0] | ||
else: | ||
logger.info("Creating instance profile for role " + role_name) | ||
response = iam_client.create_instance_profile( | ||
InstanceProfileName=role_name, | ||
Path='/' | ||
) | ||
instance_profile = response['InstanceProfile'] | ||
|
||
# Now assign the role to the profile | ||
iam_client.add_role_to_instance_profile( | ||
InstanceProfileName=instance_profile['InstanceProfileName'], | ||
RoleName=role_name | ||
) | ||
|
||
return { | ||
'InstanceProfileName': instance_profile['InstanceProfileName'], | ||
'Arn': instance_profile['Arn'] | ||
} | ||
|
||
|
||
def associate_instance_profile(profile_name, profile_arn, instance_id): | ||
logger.info("Associating instance profile: " + profile_name + " to " + instance_id) | ||
# For whatever reason, new instance profiles are not available immediately. So we try again | ||
retry_count = 6 | ||
while True: | ||
try: | ||
return ec2_client.associate_iam_instance_profile( | ||
IamInstanceProfile={ | ||
'Arn': profile_arn, | ||
'Name': profile_name | ||
}, | ||
InstanceId=instance_id | ||
) | ||
except Exception as e: | ||
retry_count -= 1 | ||
if retry_count == 0: | ||
raise e | ||
|
||
logger.info("Unable to associate instance profile... trying again in 10 sec") | ||
time.sleep(10) | ||
|
||
|
||
def handler(event, context): | ||
instance_id = event['InstanceId'] | ||
role_name = event['RoleName'] | ||
|
||
response = ec2_client.describe_iam_instance_profile_associations(Filters=[{ | ||
'Name': 'instance-id', | ||
'Values': [instance_id] | ||
}]) | ||
|
||
if len(response['IamInstanceProfileAssociations']) != 0: | ||
logger.info("Instance Profile already exists. Will attach role to existing instance profile") | ||
|
||
iam_instance_profile_association = response['IamInstanceProfileAssociations'][0] | ||
association_id = iam_instance_profile_association['AssociationId'] | ||
ec2_client.disassociate_iam_instance_profile(AssociationId=association_id) | ||
|
||
instance_profile = find_or_create_instance_profile(role_name) | ||
association_response = associate_instance_profile(instance_profile['InstanceProfileName'], instance_profile['Arn'], | ||
instance_id) | ||
association_id = association_response['IamInstanceProfileAssociation']['AssociationId'] | ||
|
||
return { | ||
"InstanceProfileName": instance_profile['InstanceProfileName'], | ||
"Arn": instance_profile['Arn'], | ||
"RoleName": event['RoleName'], | ||
"AssociationId": association_id | ||
} |
68 changes: 68 additions & 0 deletions
68
Automation/AttachIAMToInstance/Documents/aws-AttachIAMToInstance.json
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,68 @@ | ||
{ | ||
"description": "Attach IAM to Instance", | ||
"schemaVersion": "0.3", | ||
"assumeRole": "{{ AutomationAssumeRole }}", | ||
"parameters": { | ||
"InstanceId": { | ||
"type": "String", | ||
"description": "(Required) The ID of the instance." | ||
}, | ||
"RoleName": { | ||
"type": "String", | ||
"description": "(Required) Role Name to add" | ||
}, | ||
"LambdaAssumeRole": { | ||
"type": "String", | ||
"description": "(Optional) The ARN of the role assumed by lambda", | ||
"default": "" | ||
}, | ||
"AutomationAssumeRole": { | ||
"type": "String", | ||
"description": "(Optional) The ARN of the role that allows Automation to perform the actions on your behalf. ", | ||
"default": "" | ||
} | ||
}, | ||
"mainSteps": [ | ||
{ | ||
"name": "createDocumentStack", | ||
"action": "aws:createStack", | ||
"inputs": { | ||
"Capabilities": [ | ||
"CAPABILITY_IAM" | ||
], | ||
"StackName": "AttachIAMToInstanceStack{{automation:EXECUTION_ID}}", | ||
"Parameters": [ | ||
{ | ||
"ParameterKey": "LambdaRoleArn", | ||
"ParameterValue": "{{LambdaAssumeRole}}" | ||
}, | ||
{ | ||
"ParameterKey": "LambdaName", | ||
"ParameterValue": "AttachIAMToInstanceLambda-{{automation:EXECUTION_ID}}" | ||
} | ||
], | ||
"TemplateBody": "..." | ||
} | ||
}, | ||
{ | ||
"name": "attachIAMToInstance", | ||
"action": "aws:invokeLambdaFunction", | ||
"inputs": { | ||
"FunctionName": "AttachIAMToInstanceLambda-{{automation:EXECUTION_ID}}", | ||
"Payload": "{\"InstanceId\": \"{{InstanceId}}\", \"RoleName\": \"{{RoleName}}\"}", | ||
"LogType": "Tail" | ||
} | ||
}, | ||
{ | ||
"name": "deleteCloudFormationTemplate", | ||
"action": "aws:deleteStack", | ||
"inputs": { | ||
"StackName": "AttachIAMToInstanceStack{{automation:EXECUTION_ID}}" | ||
} | ||
} | ||
], | ||
"outputs":[ | ||
"attachIAMToInstance.LogResult", | ||
"attachIAMToInstance.Payload" | ||
] | ||
} |
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,18 @@ | ||
TARGET_DIR = "./Output" | ||
|
||
documents: targetdir createdocuments | ||
@echo "Done making documents" | ||
|
||
targetdir: | ||
@echo "Making $(TARGET_DIR)" | ||
mkdir -p ./Output | ||
|
||
createdocuments: | ||
python ./Setup/create_document.py > ./Output/aws-AttachIAMToInstance.json | ||
|
||
test: documents | ||
python -m unittest discover Tests | ||
|
||
clean: | ||
@echo "Removing $(TARGET_DIR)" | ||
@rm -rf ./Output |
Oops, something went wrong.