オートスケール起動のEC2のリモート操作(Run Command)を、権限限定したIAMロールで行ってみた
AWSチームのすずきです。
AWS Systems Manager の 実行コマンド(Run Command)を利用する事で、複数インスタンスのリモート操作が可能になります。
今回、SSMドキュメントと実行対象のEC2インスタンスを限定した SSM (Run Command) 操作用のIAMロールを用意、 オートスケールで 起動した EC2インスタンス(Amazon Linux2)の セキュアなリモート操作を実現する機会がありましたので、紹介させていただきます。
構成図
AWS設定
以下のCloudFormationテンプレートを利用して、EC2、IAM、SSMの設定を実施しました。
ssm-sendcomand-ec2-autoscalling-al2.yaml
EC2
AMI
Amazon Linux2 (X86) の最新AMIをパラメータストアから取得して利用しました。
Parameters: Ec2ImageId: Type: AWS::SSM::Parameter::Value<String> Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-ebs Resources: Ec2LaunchTemplate: Type: AWS::EC2::LaunchTemplate Properties: LaunchTemplateData: ImageId: !Ref 'Ec2ImageId'
UserData
Amazon Linux 2 のAMI、SSMエージェントはプリインストールされていますが、 最新バージョンへのアップデートを実施して利用しました。
UserData: !Base64 Fn::Sub: | #cloud-config runcmd: - yum update amazon-ssm-agent
IAMロール
管理ポリシー「AmazonSSMManagedInstanceCore」をインスタンスプロファイルとして設定しました。
Properties: ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
VPC
VPC指定は省略、Internet Gateway(IGW)が利用できるデフォルトVPCを利用する指定としました。
SSM
リモート操作の内容を定義したドキュメントを用意しました。
Document
「systemctl」を実行し、エラー発生時はエラー応答(1)を戻す指定としました。
SsmDocument: Type: AWS::SSM::Document Properties: Content: schemaVersion: '2.2' description: Run a script on Linux (systemctl) mainSteps: - action: aws:runShellScript name: runCommands inputs: timeoutSeconds: '60' runCommand: - systemctl status amazon-ssm-agent.service - if [ $? -ne 0 ]; then exit 1; fi - sleep 10 DocumentType: Command
IAM
User
SSM実行用ロールへのスイッチを許可したIAMユーザを用意しました。
許可対象のリソースはCloudFormationのリソース間での循環参照を回避するため、前方一致での指定としています。
IamUserAssumeRole: Type: AWS::IAM::User Properties: Policies: - PolicyName: AllowAssumeSsmSendCommand PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sts:AssumeRole Resource: - !Sub 'arn:aws:iam::${AWS::AccountId}:role/${AWS::StackName}-*'
Role
スイッチロール用のIAMユーザを信頼先とした、IAMロールを用意しました。
リモート操作対象のEC2は、タグが指定したオートスケール名(aws:autoscaling:groupName)に完全一致する事。 指定したSSMドキュメントのみに限定した許可設定としました。
IamRoleSendCommand: Type: AWS::IAM::Role DependsOn: IamUserAssumeRole Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: AWS: !GetAtt 'IamUserAssumeRole.Arn' Action: - sts:AssumeRole - sts:TagSession Policies: - PolicyName: allow_ssm_sendcomand PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - ssm:ListCommandInvocations Resource: - '*' - Effect: Allow Action: - ssm:SendCommand Resource: - !Sub 'arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:document/${SsmDocument}' - Effect: Allow Action: - ssm:SendCommand Resource: - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/*' Condition: StringEquals: ssm:ResourceTag/aws:autoscaling:groupName: !Ref 'Ec2InstanceAutoScalingGroup'
Outputs
後述のCLI、実行スクリプトとして引数などを反映した内容を、CloudFormationのOutputできる設定としました。
Parameters: OutputCliScripts: Description: Output Cli Scripts Type: String Conditions: IsOutputCliScripts: !Equals [ !Ref OutputCliScripts, "Enable" ] Outputs: step5: Description: ssm send-command Value: !Sub 'TMP=`aws --region ${AWS::Region} ssm send-command --document-name "${SsmDocument}" --targets "Key=tag:aws:autoscaling:groupName,Values=${Ec2InstanceAutoScalingGroup}" --max-concurrency=1 --max-errors=0`; echo $TMP | jq .; CommandId=`echo $TMP | jq -r .Command.CommandId`' Condition: IsOutputCliScripts step6: Description: ssm list-command-invocations Value: !Sub 'TMP=`aws --region ${AWS::Region} ssm list-command-invocations --command-id $CommandId`; echo $TMP | jq ''.CommandInvocations[]|{InstanceId:.InstanceId,Status:.Status,StatusDetails:.StatusDetails,RequestedDateTime:.RequestedDateTime}''' Condition: IsOutputCliScripts
疎通確認後はアクセスキーは無効化、ローテーションを実施する前提でご利用ください。
実行
Amazon Linux2 付属パッケージの「awscli」と「jq」をインストールして利用しました。
$ aws --version aws-cli/1.18.107 Python/2.7.18 Linux/4.14.193-149.317.amzn2.aarch64 botocore/1.17.31 $ jq --version jq-1.5
STS
スイッチロール用のIAMアクセスキー、シークレットキーを利用してスイッチロール(AssumeRole)を行います。
export AWS_ACCESS_KEY_ID=aaaa export AWS_SECRET_ACCESS_KEY=aaaaaaa export AWS_DEFAULT_REGION=ap-northeast-1 TMP=`aws sts assume-role --role-arn arn:aws:iam::000000000000:role/aaa-IamRoleSendCommand-aaa --role-session-name cli-session` export AWS_ACCESS_KEY_ID=`echo $TMP| jq -r .Credentials.AccessKeyId` export AWS_SECRET_ACCESS_KEY=`echo $TMP| jq -r .Credentials.SecretAccessKey` export AWS_SESSION_TOKEN=`echo $TMP| jq -r .Credentials.SessionToken`
SSM
SSMドキュメントと、操作対象のEC2インスタンスのタグを指定してRun Commandを実行します。
send-command
TMP=`aws ssm send-command --document-name "aaa-SsmDocument-aaa" --targets "Key=tag:aws:autoscaling:groupName,Values=aaa-Ec2InstanceAutoScalingGroup-aaa" --max-concurrency=1`; CommandId=`echo $TMP | jq -r .Command.CommandId`; echo $TMP | jq .
- 実行結果
{ "Command": { "MaxErrors": "0", "Parameters": {}, "DocumentName": "aa-SsmDocument-aaa", "OutputS3BucketName": "", "OutputS3KeyPrefix": "", "StatusDetails": "Pending", "RequestedDateTime": 1599492314.094, "Status": "Pending", "TimeoutSeconds": 3600, "TargetCount": 0, "NotificationConfig": { "NotificationArn": "", "NotificationEvents": [], "NotificationType": "" }, "InstanceIds": [], "ErrorCount": 0, "MaxConcurrency": "1", "ServiceRole": "", "CloudWatchOutputConfig": { "CloudWatchLogGroupName": "", "CloudWatchOutputEnabled": false }, "DocumentVersion": "", "CompletedCount": 0, "Comment": "", "ExpiresAfter": 1599495974.094, "DeliveryTimedOutCount": 0, "CommandId": "aa-aa-aa-aa-aa", "Targets": [ { "Values": [ "aaa-Ec2InstanceAutoScalingGroup-aaa" ], "Key": "tag:aws:autoscaling:groupName" } ] } }
list-command-invocations
操作対象EC2インスタンス、のステータスが「成功」である事が確認できました。
TMP=`aws ssm list-command-invocations --command-id $CommandId` echo $TMP | jq '.CommandInvocations[]|{InstanceId:.InstanceId,Status:.Status,StatusDetails:.StatusDetails,RequestedDateTime:.RequestedDateTime}' { "InstanceId": "i-aaaaaaaa", "Status": "Success", "StatusDetails": "Success", "RequestedDateTime": 1599492314.476 } { "InstanceId": "i-bbbbbbbb", "Status": "Success", "StatusDetails": "Success", "RequestedDateTime": 1599492314.36 }
例外
許可対象以外
SSMドキュメント、EC2インスタンスが事前に許可されたもの以外である場合、SSM操作はエラーとなります。
$ aws ssm send-command --document-name "xx-SsmDocument-xx" An error occurred (AccessDeniedException) when calling the SendCommand operation: User: arn:aws:sts::000000000000:assumed-role/aa-IamRoleSendCommand-aaa/cli-session is not authorized to perform: ssm:SendCommand on resource: arn:aws:ssm:ap-northeast-1:784693731708:document/xx-SsmDocument-xx $ aws ssm send-command --document-name "aa-SsmDocument-aaa" --instance-ids i-cccccccc An error occurred (AccessDeniedException) when calling the SendCommand operation: User: arn:aws:sts::000000000000:assumed-role/aa-IamRoleSendCommand-aaa/cli-session is not authorized to perform: ssm:SendCommand on resource: arn:aws:ssm:ap-northeast-1:784693731708:document/xx-SsmDocument-xx
ドキュメント
SSMドキュメントに定義した、リモート操作コマンドが失敗した場合の動作を確認しました。
runCommand: - systemctl status dummy - if [ $? -ne 0 ]; then exit 1; fi - sleep 10
systemctl、無効なサービスを指定しました。
$ systemctl status dummy Unit dummy.service could not be found. $ echo $? 4
ssm list-command-invocations
より、1台目の処理で「Failed」、2台目は「Terminated」で処理が中断。
「max-concurrency=1、「max-errors=0」の指定が正しく機能し、エラーの影響範囲が限定した利用が可能な事を確認できました。
{ "InstanceId": "i-aaaaaaaa", "Status": "Failed", "StatusDetails": "Terminated", "RequestedDateTime": 1599492639.381 } { "InstanceId": "i-bbbbbbbb", "Status": "Failed", "StatusDetails": "Failed", "RequestedDateTime": 1599492639.291 }
- System Managerダッシュボード(Run Command)
まとめ
EC2 Auto Scaling で実行中のEC2インスタンス、リモート操作をSSM経由で実施する事ができました。
SSM を利用する事で OSの特権操作が可能となりますが、適切なIAM権限設定を行う事で意図せぬ誤操作の回避や、CloudTrail による証跡記録も期待できます。
大きな変更を実施する場合、より多機能 B/Gデプロイなどに対応する AWS CodeDeploy や、UpdateStack を利用した EC2 の交換 が望ましい場合もあると思われますが、 OSやミドルウェアの設定変更などで 小規模な変更を行う必要がある場合、Amazon Linux 2 のAMIで起動したEC2であれば、EC2ロールの設定のみで利用可能な SSM をご活用ください。