この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
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 をご活用ください。