AWS CLIを使ってSSM(EC2 Run Command)を探ってみる

アイキャッチ AWS EC2

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

西澤です。一部のRegion限定ですが、先日リリースされたEC2 Run Commandって、どんなことができるのか?を探ってみようと思い、AWS CLIから操作してみることにしました。何のことやら?という方は、まず下記記事をご覧ください。

SSMって何だっけ?

先にご紹介した記事の通り、AWS上のWindowsにて自動構成の役割を担うEC2 Configと連動して、(今のところは)Windowsサーバ限定で構成管理を行うEC2のサブ的な位置付けのサービスです。

対応リージョン

現時点での対応リージョンは以下のみとなります。

  • US East (N. Virginia)
  • US West (Oregon)
  • EU (Ireland)

何ができるのか?

一番わかりやすいのは、Windows インスタンスの AWS Directory Service ドメインへの結合の機能です。上記の3リージョンからは、簡単にWindowsサーバを構築済みのAWS Directory Serviceと連動させて構築することができます。その手順は下記記事でもご紹介していますので、ぜひご覧ください。

ドキュメントによれば、下記のような操作をAPI経由で行うことができるそうです。

  • AWS Directory Service directoryへの参加(SSM Config and Run Command)
  • MSIパッケージのインストール、修復、アンインストール(SSM Config and Run Command)
  • PowerShellモジュールのインストール(SSM Config and Run Command)
  • CloudWatch Logsの設定(SSM Config and Run Command)
  • PowerShellコマンドやスクリプトの実行(Run Command only)
  • EC2Configサービスのアップデート(Run Command only)
  • Windows Update設定の構成(Run Command only)

どうやって動くのか?

先にご紹介した先にご紹介した記事にもある通り、json形式で定義したドキュメントをインスタンスに割り当てることで、EC2 Config経由でインスタンスに対して操作を行います。

runtimeConfigは定義した"ドキュメント"の種類を規定するもののようです。

  • aws:applications
  • aws:cloudWatch
  • aws:domainJoin
  • aws:psModule
  • aws:updateAgent※New
  • aws:runPowerShellScript※New

そして、APIでは下記が追加されています。これまでのCreateAssociationに加えて、Commandという新しいActionが利用できるようになっていることがわかります。

  • CancelCommand
  • DescribeInstanceInformation
  • ListCommandInvocations
  • ListCommands
  • ListDocuments
  • SendCommand

RunCommandの実体は?

それでは、少しこのRun Commandがどのように動作するのかもう少し探ってみることにします。

$ aws ssm list-documents --query "DocumentIdentifiers[?contains(Name,\`AWS-\`)]" --max-results 25
[
    {
        "Name": "AWS-ConfigureCloudWatch", 
        "PlatformTypes": [
            "Windows"
        ]
    }, 
    {
        "Name": "AWS-ConfigureWindowsUpdate", 
        "PlatformTypes": [
            "Windows"
        ]
    }, 
    {
        "Name": "AWS-InstallApplication", 
        "PlatformTypes": [
            "Windows"
        ]
    }, 
    {
        "Name": "AWS-InstallPowerShellModule", 
        "PlatformTypes": [
            "Windows"
        ]
    }, 
    {
        "Name": "AWS-JoinDirectoryServiceDomain", 
        "PlatformTypes": [
            "Windows"
        ]
    }, 
    {
        "Name": "AWS-RunPowerShellScript", 
        "PlatformTypes": [
            "Windows"
        ]
    }, 
    {
        "Name": "AWS-UpdateEC2Config", 
        "PlatformTypes": [
            "Windows"
        ]
    }
]

PlatformTypesにはLinuxへの拡張の期待が持てますね。さらにその詳細を確認してみましょう。

$ for i in $(aws ssm list-documents --query "DocumentIdentifiers[?contains(Name,\`AWS-\`)].Name" --max-results 25 --output text); do aws ssm get-document --name $i | sed -e 's/\\n/\
/g'; done
{
    "Content": "{
    \"schemaVersion\":\"1.2\",
    \"description\":\"Export metrics and log files from your instances to Amazon CloudWatch.\",
    \"parameters\":{
        \"status\":{
            \"type\":\"String\",
            \"default\":\"Enabled\",
            \"description\":\"(Optional) Enable or disable CloudWatch. Valid values: Enabled | Disabled\",
            \"allowedValues\":[
                \"Enabled\",
                \"Disabled\"
            ]
        },
        \"properties\":{
            \"type\":\"String\",
            \"default\":\"\",
            \"description\":\"(Optional) The configuration for CloudWatch in JSON format. Learn more at http://docs.aws.amazon.com/ssm/latest/APIReference/aws-cloudWatch.html\",
            \"displayType\":\"textarea\"
        }
    },
    \"runtimeConfig\":{
        \"aws:cloudWatch\":{
            \"settings\":{
                \"startType\":\"{{ status }}\"
            },
            \"properties\":\"{{ properties }}\"
        }
    }
}", 
    "Name": "AWS-ConfigureCloudWatch"
}
{
    "Content": "{
    \"schemaVersion\": \"1.2\",
    \"description\": \"Enable or disable automatic Windows Updates.\",
    \"parameters\": {
        \"updateLevel\": {
            \"type\": \"String\",
            \"description\": \"(Required) Install Updates Automatically: Windows automatically downloads and installs updates. If an update requires a reboot, the computer is automatically rebooted 15 minutes after updates have been installed. Never Check For Updates: Windows never checks for or downloads updates.\",
            \"allowedValues\": [
                \"InstallUpdatesAutomatically\",
                \"NeverCheckForUpdates\"
            ]
        },
        \"scheduledInstallDay\": {
            \"type\": \"String\",
            \"default\": \"Sunday\",
            \"description\": \"(Optional) The day of the week when you want Windows to download and install updates. Applies only if Install Updates Automatically is selected. Default is Sunday.\",
            \"allowedValues\": [
                \"Daily\",
                \"Sunday\",
                \"Monday\",
                \"Tuesday\",
                \"Wednesday\",
                \"Thursday\",
                \"Friday\",
                \"Saturday\"
            ]
        },
        \"scheduledInstallTime\": {
            \"type\": \"String\",
            \"default\": \"03:00\",
            \"description\": \"(Optional) The time of day when you want Windows to download and install updates. Applies only if Install Updates Automatically is selected. Default is 03:00.\",
            \"allowedValues\": [
                \"00:00\",
                \"01:00\",
                \"02:00\",
                \"03:00\",
                \"04:00\",
                \"05:00\",
                \"06:00\",
                \"07:00\",
                \"08:00\",
                \"09:00\",
                \"10:00\",
                \"11:00\",
                \"12:00\",
                \"13:00\",
                \"14:00\",
                \"15:00\",
                \"16:00\",
                \"17:00\",
                \"18:00\",
                \"19:00\",
                \"20:00\",
                \"21:00\",
                \"22:00\",
                \"23:00\"
            ]
        }
    },
    \"runtimeConfig\": {
        \"aws:runPowerShellScript\": {
            \"properties\": [
                {
                    \"id\": \"0.aws:runPowerShellScript\",
                    \"runCommand\": [
                        \"$configureWindowsUpdateFileVersion = 'Amazon.ConfigureWindowsUpdate-1.2.zip'\",
                        \"$configureWindowsUpdateFileHash = '2f67fedbc27a405b0adafd3e8ecfefa877a6219e78abd3abecea4157b37edae5'\",
                        \"$metadataLocation = 'http://169.254.169.254/latest/dynamic/instance-identity/document/region'\",
                        \"$s3Location = 'https://s3{0}.amazonaws.com/aws-ssm-{1}/aws-configurewindowsupdate/' + $configureWindowsUpdateFileVersion\",
                        \"$tempLocation = [Environment]::GetEnvironmentVariable('Temp') + '\\\\' + $configureWindowsUpdateFileVersion\",
                        \"$powerShellModuleLocation = [Environment]::GetEnvironmentVariable('Windir') + '\\\\System32\\\\WindowsPowerShell\\\\v1.0\\\\Modules'\",
                        \"try {\",
                        \"    Write-Host 'Obtaining instance region from instance metadata.'\",
                        \"    $metadata = (New-Object Net.WebClient).DownloadString($metadataLocation)\",
                        \"    $region = (ConvertFrom-JSON $metadata).region\",
                        \"    if ($region -eq 'us-east-1') {\",
                        \"        $s3Location = $s3Location -f '', $region\",
                        \"    } else {\",
                        \"        $s3Address = '-' + $region\",
                        \"        $s3Location = $s3Location -f $s3Address, $region\",
                        \"    }\",
                        \"    Write-Host 'Downloading ConfigureWindowsUpdate PowerShell module from S3.'\",
                        \"    (New-Object Net.WebClient).DownloadFile($s3Location, $tempLocation)\",
                        \"    Write-Host 'Verifying SHA 256 of the ConfigureWindowsUpdate PowerShell module zip file.'\",
                        \"    $fileStream = New-Object System.IO.FileStream($tempLocation, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read)\",
                        \"    $sha256 = [System.Security.Cryptography.SHA256]::Create()\",
                        \"    $sourceHash = [System.BitConverter]::ToString($sha256.ComputeHash($fileStream), 0).Replace('-', '').ToLower()\",
                        \"    $sha256.Dispose()\",
                        \"    $fileStream.Dispose()\",
                        \"    if ($sourceHash -ne $configureWindowsUpdateFileHash) {\",
                        \"        Write-Error -Message 'The SHA of the PowerShell module does not pass match the expected value.' -Category InvalidResult  \",
                        \"        rm $tempLocation\",
                        \"        exit 1\",
                        \"    }\",
                        \"    Write-Host 'Extracting ConfigureWindowsUpdate zip file contents to the Windows PowerShell module folder.'\",
                        \"    (New-Object -Com Shell.Application).namespace($powerShellModuleLocation).CopyHere((New-Object -Com Shell.Application).namespace($tempLocation).Items(), 16)\",
                        \"    rm $tempLocation\",
                        \"    Write-Host 'Successfully downloaded and installed PowerShell module for the AWS-ConfigureWindowsUpdate document.'\",
                        \"} catch [Exception] {\",
                        \"    $exceptionMessage = 'Exception thrown while downloading ConfigureWindowsUpdate PowerShell module with message: {0}' -f $_.Exception.Message \",
                        \"    Write-Error $exceptionMessage\",
                        \"    if (Test-Path $tempLocation) {\",
                        \"        rm $tempLocation\",
                        \"    }\",
                        \"    exit 1\",
                        \"}\",
                        \"try {\",
                        \"    $datetime = [DateTime]::MinValue\",
                        \"    if ([DateTime]::TryParse('{{ scheduledInstallTime }}',  *1$datetime)) {\",
                        \"        Set-WindowsUpdate -UpdateLevel {{ updateLevel }} -ScheduledInstallDay {{ scheduledInstallDay }} -ScheduledInstallTime $datetime.Hour\",
                        \"    } else {\",
                        \"        Write-Error -Message 'Invalid value for the 'scheduledInstallTime' parameter.' -Category InvalidArgument\",
                        \"        exit 1\",
                        \"    }\",
                        \"} catch [Exception] {\",
                        \"    $exceptionMessage = 'Exception thrown while setting Windows update message: {0}' -f $_.Exception.Message \",
                        \"    Write-Error $exceptionMessage\",
                        \"    exit 1\",
                        \"}\"
                    ]
                }
            ]
        }
    }
}

", 
    "Name": "AWS-ConfigureWindowsUpdate"
}
{
    "Content": "{
    \"schemaVersion\":\"1.2\",
    \"description\":\"Install, repair, or uninstall an application using an .msi file.\",
    \"parameters\":{
        \"action\":{
            \"type\":\"String\",
            \"default\":\"Install\",
            \"description\":\"(Optional) The type of action to perform. Valid values: Install | Repair | Uninstall\",
            \"allowedValues\":[
                \"Install\",
                \"Repair\",
                \"Uninstall\"
            ]
        },
        \"parameters\":{
            \"type\":\"String\",
            \"default\":\"\",
            \"description\":\"(Optional) The parameters for the installer.\"
        },
        \"source\":{
            \"type\":\"String\",
            \"description\":\"(Required) The URL or local path on the instance to the application .msi file.\"
        },
        \"sourceHash\":{
            \"type\":\"String\",
            \"default\":\"\",
            \"description\":\"(Optional) The SHA256 hash of the .msi file.\"
        }
    },
    \"runtimeConfig\":{
        \"aws:applications\":{
            \"properties\":[
                {
                    \"id\":\"0.aws:applications\",
                    \"action\":\"{{ action }}\",
                    \"parameters\":\"{{ parameters }}\",
                    \"source\":\"{{ source }}\",
                    \"sourceHash\":\"{{ sourceHash }}\"
                }
            ]
        }
    }
}", 
    "Name": "AWS-InstallApplication"
}
{
    "Content": "{
    \"schemaVersion\":\"1.2\",
    \"description\":\"Deploy and install PowerShell modules.\",
    \"parameters\":{
        \"workingDirectory\":{
            \"type\":\"String\",
            \"default\":\"\",
            \"description\":\"(Optional) The path to the working directory on your instance.\",
            \"maxChars\":4096
        },
        \"source\":{
            \"type\":\"String\",
            \"description\":\"(Optional) The URL or local path on the instance to the application .zip file.\"
        },
        \"sourceHash\":{
            \"type\":\"String\",
            \"default\":\"\",
            \"description\":\"(Optional) The SHA256 hash of the zip file.\"
        },
        \"commands\":{
            \"type\":\"StringList\",
            \"default\":[
            ],
            \"description\":\"(Optional) Specify PowerShell commands to run on your instance.\",
            \"displayType\":\"textarea\"
        },
        \"executionTimeout\":{
            \"type\":\"String\",
            \"default\":\"3600\",
            \"description\":\"(Optional) The time in seconds for a command to be completed before it is considered to have failed. Default is 3600 (1 hour). Maximum is 28800 (8 hours).\",
            \"allowedPattern\":\"([1-9][0-9]{0,3})|(1[0-9]{1,4})|(2[0-7][0-9]{1,3})|(28[0-7][0-9]{1,2})|(28800)\"
        }
    },
    \"runtimeConfig\":{
        \"aws:psModule\":{
            \"properties\":[
                {
                    \"id\":\"0.aws:psModule\",
                    \"runCommand\":\"{{ commands }}\",
                    \"source\":\"{{ source }}\",
                    \"sourceHash\":\"{{ sourceHash }}\",
                    \"workingDirectory\":\"{{ workingDirectory }}\",
                    \"timeoutSeconds\":\"{{ executionTimeout }}\"
                }
            ]
        }
    }
}", 
    "Name": "AWS-InstallPowerShellModule"
}
{
    "Content": "{
    \"schemaVersion\":\"1.2\",
    \"description\":\"Join your instances to an AWS Directory Service domain.\",
    \"parameters\":{
        \"directoryId\":{
            \"type\":\"String\",
            \"description\":\"(Required) The ID of the AWS Directory Service directory.\"
        },
        \"directoryName\":{
            \"type\":\"String\",
            \"description\":\"(Required) The name of the directory; for example, test.example.com\"
        },
        \"directoryOU\":{
            \"type\":\"String\",
            \"default\":\"\",
            \"description\":\"(Optional) The Organizational Unit (OU) and Directory Components (DC) for the directory; for example, OU=test,DC=example,DC=com\"
        },
        \"dnsIpAddresses\":{
            \"type\":\"StringList\",
            \"default\":[
            ],
            \"description\":\"(Optional) The IP addresses of the DNS servers in the directory. Required when DHCP is not configured. Learn more at http://docs.aws.amazon.com/directoryservice/latest/simple-ad/join_get_dns_addresses.html\",
            \"allowedPattern\":\"((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\"
        }
    },
    \"runtimeConfig\":{
        \"aws:domainJoin\":{
            \"properties\":{
                \"directoryId\":\"{{ directoryId }}\",
                \"directoryName\":\"{{ directoryName }}\",
                \"directoryOU\":\"{{ directoryOU }}\",
                \"dnsIpAddresses\":\"{{ dnsIpAddresses }}\"
            }
        }
    }
}", 
    "Name": "AWS-JoinDirectoryServiceDomain"
}
{
    "Content": "{
    \"schemaVersion\":\"1.2\",
    \"description\":\"Run a PowerShell script or specify the paths to scripts to run.\",
    \"parameters\":{
        \"commands\":{
            \"type\":\"StringList\",
            \"description\":\"(Required) Specify the commands to run or the paths to existing scripts on the instance.\",
            \"minItems\":1,
            \"displayType\":\"textarea\"
        },
        \"workingDirectory\":{
            \"type\":\"String\",
            \"default\":\"\",
            \"description\":\"(Optional) The path to the working directory on your instance.\",
            \"maxChars\":4096
        },
        \"executionTimeout\":{
            \"type\":\"String\",
            \"default\":\"3600\",
            \"description\":\"(Optional) The time in seconds for a command to be completed before it is considered to have failed. Default is 3600 (1 hour). Maximum is 28800 (8 hours).\",
            \"allowedPattern\":\"([1-9][0-9]{0,3})|(1[0-9]{1,4})|(2[0-7][0-9]{1,3})|(28[0-7][0-9]{1,2})|(28800)\"
        }
    },
    \"runtimeConfig\":{
        \"aws:runPowerShellScript\":{
            \"properties\":[
                {
                    \"id\":\"0.aws:runPowerShellScript\",
                    \"runCommand\":\"{{ commands }}\",
                    \"workingDirectory\":\"{{ workingDirectory }}\",
                    \"timeoutSeconds\":\"{{ executionTimeout }}\"
                }
            ]
        }
    }
}

", 
    "Name": "AWS-RunPowerShellScript"
}
{
    "Content": "{
    \"schemaVersion\": \"1.2\",
    \"description\": \"Update the EC2Config service to the latest version or specify an older version.\",
    \"parameters\": {
        \"version\": {
            \"default\": \"\",
            \"description\": \"(Optional) A specific version of the EC2Config service to install. If not specified, the service will be updated to the latest version.\",
            \"type\": \"String\"
        },
        \"allowDowngrade\": {
            \"default\": \"false\",
            \"description\": \"(Optional) Allow the EC2Config service to be downgraded to an earlier version. If set to false, the service can be upgraded to newer versions only (default). If set to true, specify the earlier version.\",
            \"type\": \"String\",
            \"allowedValues\": [
                \"true\",
                \"false\"
            ]
        }
    },
    \"runtimeConfig\": {
        \"aws:updateAgent\": {
            \"properties\": {
                \"agentName\": \"Ec2Config\",
                \"allowDowngrade\": \"{{ allowDowngrade }}\",
                \"targetVersion\": \"{{ version }}\"
            }
        }
    }
}", 
    "Name": "AWS-UpdateEC2Config"
}

そのままでは読みづらいので改行を入れましたが、各種runtimeConfigに定義された内容に、パラメータを渡す形式で実装されていることがわかりました。runtimeConfigが今後も拡張されていけば、より便利に使うことができそうです。ただ、Windowsの操作ではPowershellが渡せるようになっているので、既にできないこともほとんど無いとも言えますが。

まとめ

AWS CLIからRun Commandの実体を探ってみました。注意点としては、SSMはEC2に割り当てたインスタンスプロファイル(IAMロール)の権限で動作する為、割り当てる権限は十分に検討する必要があります。また、実際にいくつかコマンドを実行してみたのですが、ローカルAdministrators権限で動作しますので、OSに対するほとんどの操作が簡単に実行できてしまいます。例えば、ログインするOSユーザのパスワードがわからなくなってしまった!なんて場合でも、Remote Desktopで接続することなく変更できてしまうのです。とても便利ではあるものの、セキュリティ観点ではよく理解して使う必要のあるサービスとも言えるのではないでしょうか。
改めて、実際のコマンドの使い方をもう少し掘り下げてみたいと思います。

脚注

    AWS Cloud Roadshow 2017 福岡