AWS Service Catalogにセルフサービスアクションを設定する

AWSにおいてセルフサービスポータルを構築することができるAWS Service Catalogにセルフサービスアクション機能が追加されたので試したメモ。プロビジョンした製品(プロダクト)への操作(E2インスタンスの起動、停止など)をEC2のコンソールを操作することなくService Catalogのポータルから実施することができます。

https://docs.aws.amazon.com/servicecatalog/latest/adminguide/using-service-actions.html

# 前提条件
* Service Catalogにポートフォリオと製品がすでに登録されている。
* AWS CLIがインストールされており、クレデンシャルが設定されている。
* JSONファイル検証用にjsonlintがインストールされている。

# EC2インスタンス停止セルフサービスアクションの設定(Service Catalog管理者が実施)

## セルフサービスアクション実行用IAMロールの作成 

### Assume Role Documentの作成

* 作業フォルダの作成

```sh:コマンド
ls -d ${HOME}/tmp/

# 存在しない場合
mkdir -p ${HOME}/tmp/
```
 
* 変数の設定

```sh:コマンド
# Assume Role Document ディレクトリ
DIR_IAM_ROLE_DOC="${HOME}/tmp"
# Role名
IAM_ROLE_NAME='sc-ec2-selfservice-action-role'
# Principle名
IAM_PRINCIPAL='servicecatalog.amazonaws.com'
```

* ファイル名の設定

```sh:コマンド
FILE_IAM_ROLE_DOC="${DIR_IAM_ROLE_DOC}/${IAM_ROLE_NAME}.json" \
  && echo ${FILE_IAM_ROLE_DOC}
```
    
```sh:Output例
/Users/xxxxx/tmp/sc-ec2-selfservice-action-role.json
```

* Assume Role Documentの作成

```sh:コマンド
cat << EOF > ${FILE_IAM_ROLE_DOC}
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "${IAM_PRINCIPAL}"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF

cat ${FILE_IAM_ROLE_DOC}
```

```json:Output例
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "servicecatalog.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
```

* jsonlintでjsonファイルが壊れていないかチェックする。問題なければ何も出力されない。

```sh:コマンド
jsonlint -q ${FILE_IAM_ROLE_DOC}
```

### IAMロールの作成

```sh:コマンド
aws iam create-role \
  --role-name ${IAM_ROLE_NAME} \
  --assume-role-policy-document file://${FILE_IAM_ROLE_DOC}
```

```json:Output例
{
    "Role": {
        "Path": "/",
        "RoleName": "sc-ec2-selfservice-action-role",
        "RoleId": "AROAJBFA2Y24F66YV3AZQ",
        "Arn": "arn:aws:iam::xxxxxxxxxxxx:role/sc-ec2-selfservice-action-role",
        "CreateDate": "2019-01-31T13:05:42Z",
        "AssumeRolePolicyDocument": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Action": "sts:AssumeRole",
                    "Principal": {
                        "Service": "servicecatalog.amazonaws.com"
                    },
                    "Effect": "Allow",
                    "Sid": ""
                }
            ]
        }
    }
}
```

### IAMポリシーのアタッチ

* IAMポリシーARMの取得

```sh:コマンド
IAM_POLICY_NAME="AmazonSSMAutomationRole"
IAM_POLICY_ARN=$( \
  aws iam list-policies \
    --scope AWS \
    --max-items 1000 \
    --query "Policies[?PolicyName==\`${IAM_POLICY_NAME}\`].Arn" \
    --output text \
) \
  && echo "${IAM_POLICY_ARN}"
```

```:output例
arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole
```

* ポリシーのアタッチ

```sh:コマンド
aws iam attach-role-policy \
  --role-name ${IAM_ROLE_NAME} \
  --policy-arn ${IAM_POLICY_ARN}
```

```:Output例
出力なし
```


## Service Actionの作成 (EC2 Instance停止アクション)

* IAMロールARMの取得

```sh:コマンド
IAM_ROLE_ARN=$( \
  aws iam get-role \
    --role-name ${IAM_ROLE_NAME} \
    --query "Role.Arn" \
    --output text \
) \
  && echo "${IAM_ROLE_ARN}"
```

```:Output例
arn:aws:iam::xxxxxxxxxxxx:role/sc-ec2-selfservice-action-role
```

* 変数の設定

```sh:コマンド
# Service Action名
SERVICE_ACTION_NAME='ec2-instance-stop'
# Service action definition type (現時点ではSSM_AUTOMATIONのみ)
SERVICE_ACTION_DEFINITION_TYPE='SSM_AUTOMATION'
# Desctiption
SERVICE_ACTION_DESCRIPTION='Stop EC2 Instance'
# DEFINITION
SERVICE_ACTION_DEFINITION="Name=AWS-StopEC2Instance,Version=1,AssumeRole=${IAM_ROLE_ARN},Parameters='[{\"Name\":\"InstanceId\",\"Type\":\"TARGET\"}]'"
```

* Service Actionの作成

```sh:コマンド
aws servicecatalog create-service-action \
    --name ${SERVICE_ACTION_NAME} \
    --definition-type ${SERVICE_ACTION_DEFINITION_TYPE} \
    --definition ${SERVICE_ACTION_DEFINITION} \
    --description "${SERVICE_ACTION_DESCRIPTION}"
```

```json:Output例
{
    "ServiceActionDetail": {
        "ServiceActionSummary": {
            "Id": "act-mrwnkma3yxi6s",
            "Name": "ec2-instance-stop",
            "Description": "Stop EC2 Instance",
            "DefinitionType": "SSM_AUTOMATION"
        },
        "Definition": {
            "AssumeRole": "arn:aws:iam::xxxxxxxxxxxx:role/sc-ec2-selfservice-action-role",
            "Name": "AWS-StopEC2Instance",
            "Parameters": "[{\"Name\":\"InstanceId\",\"Type\":\"TARGET\"}]",
            "Version": "1"
        }
    }
}
```

## Service ActionとService Catalog製品の関連付け

* Service Action IDの取得

```sh:コマンド
SERVICE_ACTION_ID=$( \
aws servicecatalog list-service-actions \
    --query "ServiceActionSummaries[?Name==\`${SERVICE_ACTION_NAME}\`].Id" \
    --output text \
) \
&& echo ${SERVICE_ACTION_ID}
```

```:Output例
act-mrwnkma3yxi6s
```

* Product IDの取得
    * 今回はすでにService CatalogにBase Amazon LinuxというEC2インスタンス1台を起動するプロダクトが作成されている前提。
    * 自分(操作しているIAMユーザ/ロール)がその製品を登録したポートフォリオにAssociateされていない場合は<code>search-products</code>ではなく<code>search-products-as-admin</code>を使ってプロダクトを検索する。

```sh:コマンド
PRODUCT_NAME='Base Amazon Linux'
PRODUCT_ID=$( \
    aws servicecatalog search-products-as-admin \
        --filters FullTextSearch="${PRODUCT_NAME}" \
        --query "ProductViewDetails[?ProductViewSummary.Name==\`${PRODUCT_NAME}\`].ProductViewSummary.ProductId" \
        --output text \
)\
&& echo "${PRODUCT_ID}"
```

```:Output例
prod-eejduplwqswgk
```

* Provisioning Artifact ID(=製品に登録されているバージョンのID)の取得
    * 1.0というバージョンが登録されている前提。
    * 自分(操作しているIAMユーザ/ロール)がその製品を登録したポートフォリオにAssociateされていない場合は<code>describe-product</code>ではなく<code>describe-product-as-admin</code>を使ってプロダクトを検索する。

```sh:コマンド
PRODUCT_VERSION='1.0'
PROVISIONING_ARTIFACT_ID=$( \
    aws servicecatalog describe-product-as-admin \
        --id ${PRODUCT_ID} \
        --query "ProvisioningArtifactSummaries[?Name==to_string(\`${PRODUCT_VERSION}\`)].Id" \
        --output text \
) \
&& echo ${PROVISIONING_ARTIFACT_ID}
```

```:Output例
pa-uwsudknf7eowo
```

* Service ActionをProvisioning Artifactに紐付ける

```sh:コマンド
aws servicecatalog associate-service-action-with-provisioning-artifact \
    --product-id ${PRODUCT_ID} \
    --provisioning-artifact-id ${PROVISIONING_ARTIFACT_ID} \
    --service-action-id ${SERVICE_ACTION_ID}
```

```:Output例
出力なし
```

# 動作確認 (Service Catalogユーザで実施)

* セルフサービスアクションを設定した製品を起動しておく。
* "プロビジョニングされた製品のリスト"から起動した製品をクリックし、詳細画面を表示する。
* 画面右上の"アクション" -> "ec2-instance-stop"をクリックする。

<img width="852" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/345960/a08fffbd-6172-a9ea-c029-e799a5fe82c6.png">

* "アクションの実行"をクリックする。
<img width="491" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/345960/ba6eae64-ff9c-4119-6033-3ea2ed3a791c.png">

* セルフサービスアクションが実行され、成否が表示さす。
<img width="1437" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/345960/41189444-174e-cc00-0753-40283543b501.png">


# EC2インスタンス起動セルフサービスアクションの追加(Service Catalog管理者が実施)

## Service Actionの作成 (EC2 Instance起動アクション)

* IAMロールARMの取得

```sh:コマンド
IAM_ROLE_ARN=$( \
  aws iam get-role \
    --role-name ${IAM_ROLE_NAME} \
    --query "Role.Arn" \
    --output text \
) \
  && echo "${IAM_ROLE_ARN}"
```

```:Output例
arn:aws:iam::xxxxxxxxxxxx:role/sc-ec2-selfservice-action-role
```

* 変数の設定

```sh:コマンド
# Service Action名
SERVICE_ACTION_NAME='ec2-instance-start'
# Service action definition type (現時点ではSSM_AUTOMATIONのみ)
SERVICE_ACTION_DEFINITION_TYPE='SSM_AUTOMATION'
# Desctiption
SERVICE_ACTION_DESCRIPTION='Start EC2 Instance'
# DEFINITION
SERVICE_ACTION_DEFINITION="Name=AWS-StartEC2Instance,Version=1,AssumeRole=${IAM_ROLE_ARN},Parameters='[{\"Name\":\"InstanceId\",\"Type\":\"TARGET\"}]'"
```

* Service Actionの作成

```sh:コマンド
aws servicecatalog create-service-action \
    --name ${SERVICE_ACTION_NAME} \
    --definition-type ${SERVICE_ACTION_DEFINITION_TYPE} \
    --definition ${SERVICE_ACTION_DEFINITION} \
    --description "${SERVICE_ACTION_DESCRIPTION}"
```

```json:Output例
{
    "ServiceActionDetail": {
        "ServiceActionSummary": {
            "Id": "act-2okqvu53sxzo2",
            "Name": "ec2-instance-start",
            "Description": "Start EC2 Instance",
            "DefinitionType": "SSM_AUTOMATION"
        },
        "Definition": {
            "AssumeRole": "arn:aws:iam::xxxxxxxxxxxx:role/sc-ec2-selfservice-action-role",
            "Name": "AWS-StartEC2Instance",
            "Parameters": "[{\"Name\":\"InstanceId\",\"Type\":\"TARGET\"}]",
            "Version": "1"
        }
    }
}
```

## Service ActionとService Catalog製品の関連付け

* Service Action IDの取得

```sh:コマンド
SERVICE_ACTION_ID=$( \
aws servicecatalog list-service-actions \
    --query "ServiceActionSummaries[?Name==\`${SERVICE_ACTION_NAME}\`].Id" \
    --output text \
) \
&& echo ${SERVICE_ACTION_ID}
```

```:Output例
act-2okqvu53sxzo2
```

* Product IDの取得

```sh:コマンド
PRODUCT_NAME='Base Amazon Linux'
PRODUCT_ID=$( \
    aws servicecatalog search-products-as-admin \
        --filters FullTextSearch="${PRODUCT_NAME}" \
        --query "ProductViewDetails[?ProductViewSummary.Name==\`${PRODUCT_NAME}\`].ProductViewSummary.ProductId" \
        --output text \
)\
&& echo "${PRODUCT_ID}"
```

```:Output例
prod-eejduplwqswgk
```

* Provisioning Artifact ID(=製品に登録されているバージョンのID)の取得

```sh:コマンド
PRODUCT_VERSION='1.0'
PROVISIONING_ARTIFACT_ID=$( \
    aws servicecatalog describe-product-as-admin \
        --id ${PRODUCT_ID} \
        --query "ProvisioningArtifactSummaries[?Name==to_string(\`${PRODUCT_VERSION}\`)].Id" \
        --output text \
) \
&& echo ${PROVISIONING_ARTIFACT_ID}
```

```:Output例
pa-uwsudknf7eowo
```

* Service ActionをProvisioning Artifactに紐付ける

```sh:コマンド
aws servicecatalog associate-service-action-with-provisioning-artifact \
    --product-id ${PRODUCT_ID} \
    --provisioning-artifact-id ${PROVISIONING_ARTIFACT_ID} \
    --service-action-id ${SERVICE_ACTION_ID}
```

```:Output例
出力なし
```

# EC2インスタンス再起動セルフサービスアクションの追加(Service Catalog管理者が実施)

## Service Actionの作成 (EC2 Instanc再起動アクション)

* IAMロールARMの取得

```sh:コマンド
IAM_ROLE_ARN=$( \
  aws iam get-role \
    --role-name ${IAM_ROLE_NAME} \
    --query "Role.Arn" \
    --output text \
) \
  && echo "${IAM_ROLE_ARN}"
```

```:Output例
arn:aws:iam::xxxxxxxxxxxx:role/sc-ec2-selfservice-action-role
```

* 変数の設定

```sh:コマンド
# Service Action名
SERVICE_ACTION_NAME='ec2-instance-restart'
# Service action definition type (現時点ではSSM_AUTOMATIONのみ)
SERVICE_ACTION_DEFINITION_TYPE='SSM_AUTOMATION'
# Desctiption
SERVICE_ACTION_DESCRIPTION='Restart EC2 Instance'
# DEFINITION
SERVICE_ACTION_DEFINITION="Name=AWS-RestartEC2Instance,Version=1,AssumeRole=${IAM_ROLE_ARN},Parameters='[{\"Name\":\"InstanceId\",\"Type\":\"TARGET\"}]'"
```

* Service Actionの作成

```sh:コマンド
aws servicecatalog create-service-action \
    --name ${SERVICE_ACTION_NAME} \
    --definition-type ${SERVICE_ACTION_DEFINITION_TYPE} \
    --definition ${SERVICE_ACTION_DEFINITION} \
    --description "${SERVICE_ACTION_DESCRIPTION}"
```

```json:Output例
{
    "ServiceActionDetail": {
        "ServiceActionSummary": {
            "Id": "act-sxovcxrrds3nu",
            "Name": "ec2-instance-restart",
            "Description": "Restart EC2 Instance",
            "DefinitionType": "SSM_AUTOMATION"
        },
        "Definition": {
            "AssumeRole": "arn:aws:iam::xxxxxxxxxxxx:role/sc-ec2-selfservice-action-role",
            "Name": "AWS-RestartEC2Instance",
            "Parameters": "[{\"Name\":\"InstanceId\",\"Type\":\"TARGET\"}]",
            "Version": "1"
        }
    }
}
```

## Service ActionとService Catalog製品の関連付け

* Service Action IDの取得

```sh:コマンド
SERVICE_ACTION_ID=$( \
aws servicecatalog list-service-actions \
    --query "ServiceActionSummaries[?Name==\`${SERVICE_ACTION_NAME}\`].Id" \
    --output text \
) \
&& echo ${SERVICE_ACTION_ID}
```

```:Output例
act-sxovcxrrds3nu
```

* Product IDの取得

```sh:コマンド
PRODUCT_NAME='Base Amazon Linux'
PRODUCT_ID=$( \
    aws servicecatalog search-products-as-admin \
        --filters FullTextSearch="${PRODUCT_NAME}" \
        --query "ProductViewDetails[?ProductViewSummary.Name==\`${PRODUCT_NAME}\`].ProductViewSummary.ProductId" \
        --output text \
)\
&& echo "${PRODUCT_ID}"
```

```:Output例
prod-eejduplwqswgk
```

* Provisioning Artifact ID(=製品に登録されているバージョンのID)の取得

```sh:コマンド
PRODUCT_VERSION='1.0'
PROVISIONING_ARTIFACT_ID=$( \
    aws servicecatalog describe-product-as-admin \
        --id ${PRODUCT_ID} \
        --query "ProvisioningArtifactSummaries[?Name==to_string(\`${PRODUCT_VERSION}\`)].Id" \
        --output text \
) \
&& echo ${PROVISIONING_ARTIFACT_ID}
```

```:Output例
pa-uwsudknf7eowo
```

* Service ActionをProvisioning Artifactに紐付ける

```sh:コマンド
aws servicecatalog associate-service-action-with-provisioning-artifact \
    --product-id ${PRODUCT_ID} \
    --provisioning-artifact-id ${PROVISIONING_ARTIFACT_ID} \
    --service-action-id ${SERVICE_ACTION_ID}
```

```:Output例
出力なし
```

以上