のぴぴのメモ

自分用のLinuxとかの技術メモ

Jira Service Management Rest APIでカスタム フィールドのオプションを変更したメモ

選択リスト型のカスタム フィールドの選択できる値のリスト更新を Rest APIで実行したメモです。

手順メモ

事前設定

ID="JiraユーザのID"
PASSWD="API トークン"
PROJECT_URL="プロジェクトのURLのxxxx.atlassian.netのxxxx部分"

カスタムフィールドのオプション(リストの選択値一覧)情報の取得

事前設定
FIELD_NAME='AwsSupportServices'
PROJECT_NAME='aws portal'
ISSUE_TYPE_NAME='AWS Support'
プロジェクトのID取得
PROJECT_ID=$(
    curl \
        -s -u "${ID}:${PASSWD}" \
        --request GET \
        --header 'accept: application/json'  \
        --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/project/search" | \
    jq -r '.values[] | select ( .name == "'"${PROJECT_NAME}"'" ) | .id' )
echo ${PROJECT_ID}
課題タイプのID取得
ISSUE_TYPE_ID=$(
    curl \
        -s -u "${ID}:${PASSWD}" \
        --request GET \
        --header 'accept: application/json'  \
        --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/issuetype" | \
    jq -r '.[] | select( .name == "'"${ISSUE_TYPE_NAME}"'" ) | .id' )
echo ${ISSUE_TYPE_ID}
カスタム フィールドのID取得
CUSTOME_FIELD_ID=$(
    curl \
        -s -u "${ID}:${PASSWD}" \
        --request GET \
        --header 'accept: application/json'  \
        --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/search?maxResults=1&expand=names" | \
     jq -r '.names | to_entries | .[] | select( .value == "'"${FIELD_NAME}"'" ) | .key' )
echo $CUSTOME_FIELD_ID
カスタム フィールドの コンテキスト情報の取得

アトラシアンのドキュメントによると、「カスタム フィールド コンテキストは、同じフィールドの異なる設定です。プロジェクトに対して既定値が異なる複数のカスタム フィールドを作成する代わりに、コンテキストを使用して同じ結果を得られます。」とのことです。
同じカスタムフィールドでもプロジェクトごとにリストから選択できる値を変えることなどができるようです。

BODY_DATA='{
  "mappings": [
    {
      "issueTypeId": "'"${ISSUE_TYPE_ID}"'",
      "projectId": "'"${PROJECT_ID}"'"
    }
  ]
}'

CONTEXT_ID=$( \
    curl \
        -s -u "${ID}:${PASSWD}" \
        --request POST \
        --header 'Content-Type: application/json' \
        --header 'accept: application/json'  \
        --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/field/${CUSTOME_FIELD_ID}/context/mapping" \
        --data "${BODY_DATA}" \
    | jq -r '.values[0].contextId' )
echo $CONTEXT_ID
カスタム フィールドのオプション(リストの選択値一覧)の取得
curl \
    -s -u "${ID}:${PASSWD}" \
    --request GET \
    --header 'accept: application/json'  \
    --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/field/${CUSTOME_FIELD_ID}/context/${CONTEXT_ID}/option" | jq

カスタムフィールドのオプション更新

ここでは選択リスト(カスケード)のオプション変更を例にメモを記載します。

親リスト追加
BODY_DATA='{
  "options": [
    {
      "disabled": false,
      "value": "hoge hoge"
    }
  ]
}'
curl \
    -s -u "${ID}:${PASSWD}" \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'accept: application/json'  \
    --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/field/${CUSTOME_FIELD_ID}/context/${CONTEXT_ID}/option" \
    --data "${BODY_DATA}"
作成した親リストにカスタム フィールド オプションを追加する
PARENT_ID='作成した親のIDを指定'
BODY_DATA='{
  "options": [
    {
      "optionId": "'"${PARENT_ID}"'",
      "disabled": true,
      "value": "Manhattan"
    }
  ]
}'

curl \
    -s -u "${ID}:${PASSWD}" \
    --request POST \
    --header 'Content-Type: application/json' \
    --header 'accept: application/json'  \
    --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/field/${CUSTOME_FIELD_ID}/context/${CONTEXT_ID}/option" \
    --data "${BODY_DATA}"
既存のカスタムフィールド オプションの更新
CUSTOME_FIELD_OPTION_ID='変更したい既存フィールドのIDを指定'
BODY_DATA='{
  "options": [
    {
      "id": "'"${CUSTOME_FIELD_OPTION_ID}"'",
      "disabled": false,
      "value": "Manhattan"
    }
  ]
}'

curl \
    -s -u "${ID}:${PASSWD}" \
    --request PUT \
    --header 'Content-Type: application/json' \
    --header 'accept: application/json'  \
    --url "https://${PROJECT_URL}.atlassian.net/rest/api/2/field/${CUSTOME_FIELD_ID}/context/${CONTEXT_ID}/option" \
    --data "${BODY_DATA}"

Jira Service ManagementでRest APIで課題を操作したメモ

はじめに

Jira Service ManagementのREST API操作を調査したメモです。
最終的にServer PlatformでREST APIを叩く必要があったため、Jira REST API V3ではなくJira REST API V2前提で調べていますので、注意ください。

JiraのREST API

REST API リファレンス

Jiraにはクラウド版とサーバ版があり、それぞれAPIのバージョンが異なるようです。

2022/9/1時点では、クラウド版がJira REST API V3で、サーバ版の最新が Jira REST API V2(Jira 9.2.0)のようです。
ただ使った限りではクラウド版でもJira REST API V2が実行できるようです。

Jira Server platformのREST APIの種類

JiraのAPIは以下の3種類があるようですが、とりあえず基本の Jira Server platform REST API を見ておけばよさそうです。

REST APIで課題の情報を取得する

curl -u "${ID}:${PASSWD}" https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID} | jq

以下はあらかじめ変数で設定しておきます。

  • ${ID} : 接続用に使うJiraユーザのID
  • ${PASSWD}: : API トークン(ログインパスワードではないです。アカウント管理 -> セキュリティ -> APIトークンで生成します)
  • ${ISSUE_ID}/ : 課題のキー(XX-99)か、課題ID(XMLエクスポートして、<key id="99999">XX-99</key>の99999部分)を指定

REST APIで課題をトラジションする

Transitionsの取得

curl -u "${ID}:${PASSWD}" https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID}/transitions

Transitionsする

Transitionsの取得で取得したデータから、実行したいTransitionのidを取得して以下を実行します。

TRANSITION_ID=99   #取得したISSUE IDに書き換え

BODY='{
    "transition": {
        "id": "'"${TRANSITION_ID}"'"
    }
}'

curl \
    -u "${ID}:${PASSWD}"  \
    -X POST -d "${BODY}"  \
    --header 'Content-Type: application/json'  \
    --header 'accept: application/json'  \
    --url https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID}/transitions

Transitionsしてかつコメントを追記する

前提

前提として該当Transistionを実行したときに以下のような画面が出てくるよう、Transistionを設定する必要があります。

  • Trasition時にコメント追加ができるようにする設定方法
    • 画面(Screen)を新規作成します。フィールドタブは何も設定しないでOKです。
    • 該当ワークフローを編集します。
      • 対象のトランジットを開き、編集を開きます
      • 画面で、新規作成した画面を指定して保存します。
REST API実行
TRANSITION_ID=99   #取得したISSUE IDに書き換え

BODY='{
    "update": {
        "comment": [
            {
                "add": {
                    "body": "Test Message."
                }
            }
        ]
    },
    "transition": {
        "id": "'"${TRANSITION_ID}"'"
    }
}'

curl \
    -u "${ID}:${PASSWD}"  \
    -X POST -d "${BODY}"  \
    --header 'Content-Type: application/json'  \
    --header 'accept: application/json'  \
    --url https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID}/transitions

Transitions時に特定のフィールド値を変更する

前提
  • その1) Transitionの画面設定

前提として、該当Transitionでフィールド更新ができるよう、Transitionに割り当てた画面設定で、更新したいフィールドを追加する必要があります。

  • その2) カスタムフィールドの内部キー取得

動かした限りでは、例えばカスタムフィールドAwsAccountIdを更新しようとする時、transitionでAwsAccountIdは指定できず、内部で管理しているcustomfield_10016のような無機質なキーで指定する必要があるようです。Jiraの残念な仕様ですね。

カスタムフィールドの名前と内部のキーのマッピングの一覧は以下で取得することができるので、これで事前に変更したいカスタフィールドの内部キーが何かを確認しておく必要があります。

 curl -u "${ID}:${PASSWD}" "https://<Project-name>.atlassian.net/rest/api/2/search?maxResults=1&expand=names"
REST API実行
TRANSITION_ID=99   #取得したISSUE IDに書き換え

BODY='{
    "fields": {
        "customfield_10075": "bbb hoge hoge"
    },
    "transition": {
        "id": "'"${TRANSITION_ID}"'"
    }
}'

curl \
    -u "${ID}:${PASSWD}"  \
    -X POST -d "${BODY}"  \
    --header 'Content-Type: application/json' \
    --header 'accept: application/json'  \
    --url "https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID}/transitions?expand=transitions.fields"

課題(ISSUE)の特定フィールドを変更する

ISSUEの特定フィールドを更新する場合は、ISSUEの編集で対応できます。

BODY='{
    "fields": {
        "customfield_10075": "000000000000"
    }
}'

curl \
    -u "${ID}:${PASSWD}"  \
    -X PUT -d "${BODY}"  \
    --header 'Content-Type: application/json' \
    --header 'accept: application/json'  \
    --url "https://<Project-name>.atlassian.net/rest/api/2/issue/${ISSUE_ID}"

Windowsサーバ ドメイン参加 + AD管理用サーバをPower Shellでやってみる

備忘録として、メモしておきます。

手順

AD管理ツールのセットアップ

  • Windowsに管理者権限でログイン
  • AD管理に必要なツールをPowerShellでインストールする。
Import-Module ServerManager
Install-WindowsFeature -Name GPMC,RSAT-AD-PowerShell,RSAT-AD-AdminCenter,RSAT-ADDS-Tools,RSAT-DNS-Server

ドメイン参加

DNS参照先をADのDNSアドレス(IPアドレス)に変更
Get-NetAdapter | Set-DnsClientServerAddress -ServerAddresses <ADの1つ目のIPアドレス>,<ADの2つ目のIPアドレス>

設定の確認

Get-NetAdapter | Get-DnsClientServerAddress
ドメイン追加

ドメインに参加させます。実行すると 管理者ユーザのパスワードを聞かれるので、パスワードを入力します。

Add-Computer -DomainName <ドメイン名> -Credential <ドメインの管理者ユーザ名>

例えば、ドメインhogehoge.internal、管理者ユーザadministratorの場合の実行コマンドは、Add-Computer -DomainName hogehoge.internal -Credential administratorとなる。

サーバのリブート

  • 有効にするためリブートする。
Restart-Computer
  • 再起動したら、ドメインユーザ&パスワードでログインする。

GitHub ActionsによるTerraform実行環境(更新ディレクトリ特定+checkovによるセキュリティチェック付き)

はじめに

GitHub ActionsによるTerraformのCIサンプルです。
実際のコードはGitHubのリポジトリを参照ください。
このサンプルの特徴は以下の通りです。

  • OpenID Connect(OIDC)でのAWS認証によるActions実行により、アクセスキー&シークレットキー管理が不要
  • Terraformのデファクトとなっているディレクトリ構成で作成
  • その上でActionsでは、更新があったディレクトリを特定し、そのディレクトリのみTerraformを実行
  • CIに、IaCコードの静的解析を行うcheckovを組み込み、Pull Requestで自動実行(最近流行りの、DevSecOpsぽいものを実現)


terrafrom/envs/xxxのように環境面でTerraform実行先が固定されている場合(実行面数が増加しない場合)は、こんな面倒なやり方をせずワークフローを分けてon:で、paths:条件で実行する構成にした方がシンプルで望ましいです。
一方で、アカウントやプロジェクトなどTerraform実行ディレクトリが単純増加するユースケースで、GitHub ActionsでTerraformを実行したいケースでは、このサンプルのワークフローが効果を発揮すると思います。

前提とするTerraformのディレクトリ構成

terraformのモジュールの標準的なディレクトリ構成を前提としています。
/terraform/accounts/配下にそれぞれのアカウント単位でterrafomを管理するイメージです。
アカウントを追加する場合は、_templateフォルダを任意の名称でコピーして適切に設定を変更してterraformでapplyします。

.
└── terraform
    ├── accounts
    │   ├── _template
    │   ├── user-a
    │   │   ├── backend.tf
    │   │   ├── main.tf
    │   │   ├── provider.tf
    │   │   └── terraform.tf
    │   ├── user-b
    │   └── user-c
    └── modules
        └── xxxxx

ActionsからのAWS認証

ActionsからAWS環境にアクセスする際の認証には、OIDCを利用しています。OIDC利用の詳細は下記ドキュメントを参照ください。

Actionsのワークフローコード

実際のコードはこちらのワークフローのYAMLファイルを確認ください。
ジョブは、(1)更新ディレクトリ特定ジョブと、(2)Terraform実行ジョブ、の2つで構成されています。

更新ディレクトリ特定ジョブ

シェル芸の塊です。ここではざっくりとは以下の処理をしています。

  1. git logによる更新一覧取得
  2. sedawkuniqなどを駆使して、更新ディレクトリ情報のみ抽出
  3. 抽出した情報をjpコマンドでJSONに変換
  4. 変換したJSONは、次のTerraform実行ジョブでのmatrixに利用

具体的な処理の中身については、GitHubのREADMEこちらの記事を参照ください。

 detect_dirs:
    name: "Detect modified directories"
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash
    outputs:
      TARGET_DIR: ${{ steps.detectddir.outputs.TARGET_DIR }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
      - name: Detect modified project directories
        id: detectddir
        run: |
          # git fetch
          echo "::group::git fetch"
          TARGET_BRANCH="${{ github.base_ref }}"  
          echo "TAGET_BRANCE = ${TARGET_BRANCH}"
          git fetch --depth 1 origin ${TARGET_BRANCH}

          # If modules is changed, execute terraform to all .
          echo "::group::check modules directory"
          LINES=$( git diff origin/${TARGET_BRANCH} HEAD --name-only -- ${{env.TERRAFORM_MODULES_DIR}} | wc -l )
          if [ ${LINES} -gt 0 ]; then
            flag_all_envs='true'
          else
            flag_all_envs='false'
          fi
          echo "::group::flag_all_envs = ${flag_all_envs}"

          # Detect target directories
          echo "::group::detect target directories"
          if [ "${flag_all_envs}" == 'true' ]; then
            TARGET_DIR=$( \
              find ${{env.TERRAFORM_TARGET_DIR}} -type d -not -name ${{env.TERRAFORM_ENVS_EXCLUDED_DIR}} -maxdepth 1 -mindepth 1 | \
              jq -scR 'split("\n") | .[:-1]' \
            );
          else
            TARGET_DIR=$( \
              git diff origin/${TARGET_BRANCH} HEAD --name-only -- ${{ env.TERRAFORM_TARGET_DIR }} | \
              sed -E 's:(^${{ env.TERRAFORM_TARGET_DIR }}/[^/]*/)(.*$):\1:' | \
              sort | uniq | \
              awk '{ if( system("[ -d "$1" ]") == 0 && $1 !~ /${{env.TERRAFORM_ENVS_EXCLUDED_DIR}}/ ){print $1} }'  | \
              jq -scR 'split("\n") | .[:-1]'
            );
          fi

          # Output target directories
          echo "::endgroup::"
          # Output results
          echo "::group::results"
          echo "TARGET_DIR = ${TARGET_DIR}"
          echo "::endgroup::"
          # End processing
          echo "::set-output name=TARGET_DIR::${TARGET_DIR}"
          exit 0

Terraform実行ジョブ

先の更新ディレクトリ特定ジョブから、jobのoutputsを利用し特定したディレクトリ一覧を取得し、Matrixを利用し並列実行しています。
checkovの詳細については、こちらの記事を参照ください。

  run_terraform:
    name: "Run terraform"
    needs: detect_dirs
    if: ${{ needs.detect_dirs.outputs.TARGET_DIR != '[]' }}
    strategy:
      fail-fast: false
      max-parallel: 2
      matrix:
        target: ${{fromJson(needs.detect_dirs.outputs.TARGET_DIR)}}
    runs-on: ubuntu-latest
    defaults:
      run:
        shell: bash
    env:
      TERRAFORM_WORK_DIR: ${{ matrix.target }}
    permissions:
      id-token: write
      contents: read
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2
        with:
          fetch-depth: 0
      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          role-to-assume: ${{secrets.ASSUME_ROLE_ARN}}
          role-session-name: pullrequest
          aws-region: ap-northeast-1
      - name: AWS Sts Get Caller Identity
        run: aws sts get-caller-identity
      - name: Setup terraform
        uses: hashicorp/setup-terraform@v1
        with:
          terraform_version: ${{env.TERRAFORM_VERSION}}
      - name: Set up Python 3.9
        uses: actions/setup-python@v4
        with:
          python-version: 3.9
      - name: Test with Checkov
        id: checkov
        uses: bridgecrewio/checkov-action@master
        with:
          directory: ${{env.TERRAFORM_ROOT_DIR}}
          framework: terraform
          quiet: true
          skip_check: ${{env.CHECKOV_SKIP_CHECK}}
      - name: Terraform Format
        run: terraform fmt -recursive -check=true
      - name: Terraform Init
        run: terraform -chdir=${TERRAFORM_WORK_DIR} init
      - name: Terraform Validate
        run: terraform -chdir=${TERRAFORM_WORK_DIR} validate -no-color
      - name: Terraform Plan
        run: terraform -chdir=${TERRAFORM_WORK_DIR} plan

セットアップ手順

具体的手順は、GitHubのリポジトリのREADMEに記載しています。

  1. Terraform実行用にIODCプロバイダー、IAMロール、S3バケット、DynamoDB作成と設定
    1. 作成用のCloudFormationを準備しているので、これで作成します。
    2. IAMロールのANRをGitHubリポジトリに、ASSUME_ROLE_ARNという名称のシークレットに格納します。
    3. 作成したS3バケット、DynamoDBを、Terraformコードに設定
  2. GitHubリポジトリへmainブランチ以外の任意のブランチでPush
  3. pull requestを作成し、mainにマージする。

TerraformコードのセキュリティチェックでCheckovをGitHub Actionsに組み込んでみた

はじめに

Terraformコードのセキュリティーチェックを行う必要があり、IaC用の静的コード解析ツールであるcheckovをGitHub ActionsのCIに組み込んでみた時のメモです。
最初にcheckovをローカルで実行する場合のやりかたを説明して、最後にActionsへの組み込み方法を説明します。

Checkovとは

Checkovは、Infrastracture as code(IaC)コード用の静的コード解析ツールです。Checkovを利用することで、セキュリティやコンプライアンスの問題につながる可能性のある箇所を摘出することができます。解析可能なIaCは以下の通りです(2022/5現在)

  • Terraform (for AWS, GCP, Azure and OCI)
  • CloudFormation (including AWS SAM)
  • Azure Resource Manager (ARM)
  • Serverless framework
  • Helm charts
  • Kubernetes
  • Docker

ローカルPCでの使い方

インストール

インストール方法は、checkovドキュメントのinstalling Checkovを参照ください。

たとえばpip3を利用してインストールする場合は以下のようになります。

pip3 install checkov

インストール後、下記のようにコマンドを打って実行可能なことを確認します。

checkov --version
2.0.1211

(留意事項)レベルアップ

なお、引数なしで実行すると下記のような画面で「追加機能を使えるようレベルアップするか?」という質問が出ます。このレベルアップをするためにyを選択すると、レベルアップに必須となるhttps://www.bridgecrew.cloud/gettingStartedのアカウント作成に必要な情報を対話形式で入力する流れになります。

この設定をすると、checkovの実行結果がbridgecrew.cloudのサイトにアップされることになるようなので、アップされるのを回避したい場合はnを選択して終了するようにしてください。

もしレベルアップを選択してbridgecrew.cloudのサイトにアップされるようになった後に解除する場合ですが、~/.bridgecrew/credentialsにサイトに接続するためのトークンがあるので、このファイルを削除してしまうことで接続されないようになります。

静的解析のやりかた

静的解析の実行方法

たとえばTerraformの実行ディレクトリが/data/HogeHogeSystem/envs/development/の場合、以下のコマンドで静的解析を実施することができます。

checkov --quiet -d /data/HogeHogeSystem/envs/development/
  • --quiet: FAILEDになったものだけ表示されます。(quietをつけない場合は、PASSした結果も表示されます)

実行結果の例です。

指摘事項のスキップ方法(Terraformのオブジェクト単位)

上記の例では、セキュリティーグループを作成しているけど使用されていないよというエラーになります。
ここで指摘内容が正しければコード修正しますが、問題ない場合は該当モジュールにcheckov:skip=check_id:suppression_commentの形式でコメントを追加すると、checkovでチェックをスキップすることができます。

# SG for EC2 instances
resource "aws_security_group" "ec2" {
   #checkov:skip=CKV2_AWS_5:There is no problem because it will be used in the EC2 instance to be added later.
   name        = "${var.vpcname}-ec2"
   description = "For EC2"
   vpc_id      = aws_vpc.this.id
   egress {
     description = "Allow all traffic."
     from_port   = 0
     to_port     = 0
     protocol    = "-1"
     cidr_blocks = ["0.0.0.0/0"]
   }
   tags = {
     Name = "${var.vpcname}-sg-ec2"
   }
}

詳しい内容は、Suppressing and Skipping Policies - checkovを参照ください。

指摘事項のスキップ方法(ルール単位)

また、--skip-checkで指定することで、チェック全体から特定のルールを除外することができます。
たとえばcheckovでは、LOWMEDIUMHIGHCRITICALの4つのseverity区分があります。またルールには、CKV_123のような番号が振られています。
severityがLOWのものと、CKV_123をチェックから除外する場合は、以下のように実行します。

checkov --quiet --skip-check LOW,CKV_123 -d /data/HogeHogeSystem/envs/development/

GitHub ActionsへのCheckovの組み込み

GitHub Actionsのワークフローに、checkovを組み込むことができます。詳細については以下のドキュメントを確認ください。

具体的にはGitHub Actionsに以下のような記載をすることで、Actionsでcheckovを実行することができます。この場合FAILEDになるルールが発生した場合はcheckovコマンドのリターン値が0ではないので、その時点でエラーでフローがアベンドします。
またcheckovの前提でpythonが必要になるため、 actions/setup-python@v4を利用しPythonをセットアップします。

      - name: Terraform Format
        run: terraform fmt -recursive -check -diff

      - name: Set up Python 3.9
        uses: actions/setup-python@v4
        with:
          python-version: 3.9
      - name: Test with Checkov
        id: checkov
        uses: bridgecrewio/checkov-action@master
        with:
          directory: .
          framework: terraform
          quiet: true
          skip_check: LOW

      - name: Terraform Init
        run: terraform init -no-color

エラーが発生した時のスキップ方法など運用方法は、ローカルPCの時と同じになります。
運用としては、このcheckovをmainブランチへのPull Request & Push時のフローに組み込み、セキュリティチェックが通過しないとmainブランチにマージできないようにするなどが考えられます。

テストでプロセスをハングアップさせる方法

テストケースでプロセスハングがあり、どうやろうかと調べたので簡単にメモします。

プロセスの一時停止

対象のプロセスにSIGSTOPを送るとプロセスが一時停止します。これでプロセスのハングアップ状況を作ります。
※ アプリ側でSIGSTOPを受け付けないよう実装されている場合は効かないです。

sudo kill -s SIGSTOP <対象プロセスのPID>

プロセスの再開

再開する場合は対象のプロセスにSIGCONTを送ります。ただ正常に再開してくれるかは該当アプリの実装によります。
なのでサービスであればsystemctl restart XXXXXで再起動してしまったほうが確実です。

sudo kill -s SIGCONT <対象プロセスのPID>

なんで調べたのか

kubernetes(EKS+ワーカーノードEC2構成)の障害テストで、kubeletのハング時を再現したくて調べたものです。
ちなみにテスト結果は以下のとおりです。

  • EC2インスタンスは正常稼働のママ(インスタンスやOSは正常なので、AutoScalingでは当然検知できない)
  • kubernetesで該当ノードはNotReady状態になる
  • 該当ノード上のPodは検証ではTerminatingになり、最終的に別のノードに再配置され復旧する
  • アプリ(ALB経由の簡易的なhttpdのPod)は一時的にエラー(50x)が発生する場合も

SSH接続型のCloud9をセットアップする手順

はじめに

AWS Cloud9は、ブラウザのみでコードを記述、実行、デバッグできるクラウドベースの統合開発環境 (IDE) です。IDEの画面はこんな感じです。
f:id:nopipi:20220413211500p:plain:w500


AWS Cloud9で、AWS Cloud9サービスからCloud9を動かすEC2インスタンス or VMへの接続方式は以下の3種類があります。

  1. ダイレクトアクセス
  2. Systems Manager経由
  3. SSH接続


この記事ではSSH接続方式の場合のCloud9のセットアップ手順を説明します。

結論としては面倒なので、EC2インスタンスタイプで特にこの方式を採用するメリットはないと思います。しいて言えばCloud9をEC2インスタンス以外で動作させたい場合はこの方式になるのではないでしょうか。

手順

インスタンス作成

前提
  • Public接続ができるVPCがあること(デフォルトVPCでOK)
  • CIDR0.0.0/0からSSH(TCP Port22)へ接続可能なセキュリティーグループがあること
インスタンス作成
#設定が必要なパラメータ
PROFILE="<対象アカウントのプロファイル(デフォルトプロファイル利用時はdefaultを指定)>"
REGION="<インスタンスを作成するリージョンのコード。東京リージョンならap-northeast-1>"
SUBNET_ID="<パブリックサブネットのID>"
SG_ID="<インバウンドでsshを許可しているセキュリティーグループのID>"
KEYNAME="<EC2インスタンスにSSHログインするためのキーペア名>"

#基本固定で良いパラメータ
INSTANCE_TYPE="t2.micro"

#AMI ID取得(ubuntuイメージ利用)
UBUNTU_AMI_ID=$(aws --profile ${PROFILE} --region ${REGION} --output text \
    ec2 describe-images \
        --filters 'Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-????????' \
                      'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' );
echo "
UBUNTU_AMI_ID = ${UBUNTU_AMI_ID}
"

#タグ設定
TAGJSON='
[
    {
        "ResourceType": "instance",
        "Tags": [
            {
                "Key": "Name",
                "Value": "Cloud9-SshType"
            }
        ]
    }
]'

# サーバの起動
aws --profile ${PROFILE} --region ${REGION} \
    ec2 run-instances \
        --image-id ${UBUNTU_AMI_ID} \
        --instance-type ${INSTANCE_TYPE} \
        --key-name ${KEYNAME} \
        --subnet-id ${SUBNET_ID} \
        --security-group-ids ${SG_ID} \
        --associate-public-ip-address \
        --tag-specifications "${TAGJSON}" ;

Cloud9用ユーザ準備

インスタンスへのログイン
#作成したインスタンスのPublic IP取得
PUBLIC_IP=$( aws --profile ${PROFILE} --region ${REGION} --output text \
    ec2 describe-instances \
        --filters \
            "Name=instance-state-name,Values=running" \
            "Name=tag:Name,Values=Cloud9-SshType" \
    --query 'Reservations[].Instances[].PublicIpAddress' );
echo "PUBLIC_IP = ${PUBLIC_IP}"

#インスタンスへのssh接続
ssh "ubuntu@${PUBLIC_IP}"
Cloud9用ユーザ追加
sudo adduser --quiet --gid 100 --disabled-password cloud9


Changing the user information for cloud9
Enter the new value, or press ENTER for the default
	Full Name []:
	Room Number []:
	Work Phone []:
	Home Phone []:
	Other []:
Is the information correct? [Y/n] Y

cloud9ユーザでパスワード無しでのsudoでのroot権限利用が必要なため、sudo設定を追加します。

sudo -i
echo 'cloud9 ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/99-cloud9-user
exit
cloud9ユーザのSSH公開鍵用設定

.sshフォルダを作成します。

sudo -i -u cloud9

mkdir ~/.ssh
chmod 700 ~/.ssh

ls -la |grep ssh
drwx------ 2 cloud9 users 4096 Apr 13 11:11 .ssh

exit

Cloud9の前提パッケージセットアップ

Python2.7

デフォルトではPython3しかインストールされていないので、追加でのインストールが必要なはずです。

#pythonの確認
python2 --version

#インストールされていない場合は下記コマンドでインストール
sudo apt update
sudo apt install -y python2
Node.js

恐らくインストールされていないので、Node.jsのインストールが必要なはずです。

#Node.jsの確認(多分コマンドがないはず)
node --version

#Node.jsのインストール
sudo apt update
sudo apt install -y nodejs
ホームディレクトリの権限確認

Cloud9の要件で、Cloud9実行ユーザのホームディレクトリはrwxr-xr-xに設定されている必要があります。
ubuntuでは多分変更不要と思いますが、念の為確認しあってなければ変更します。

#ホームディレクトリの確認
ls -la /home |grep cloud9

drwxr-xr-x  3 cloud9 users  4096 Apr 13 11:11 cloud9  <= drwxr-xr-xならOK

#仮に上記パーミッションでない場合は以下で設定
sudo chmod u=rwx,g=rx,o=rx /home/cloud9

Cloud9のセットアップ

セットアップに必要なパッケージのインストール

パッケージインストールでgccとmakeを利用するため、先にパッケージをインストールします。

sudo apt update
sudo apt install -y make gcc
Cloud9セットアップ

Cのビルドが走り完了までに数分かかります。

curl -L https://raw.githubusercontent.com/c9/install/master/install.sh | bash

(マネコン)Cloud9サービスでの設定

  • マネージメントコンソールでAWS Cloud9を表示します。(リージョンはどこでもOK)
  • Create environmentでCloud9環境作成のウィザードを開きます
  • 下図を参照に必要項目を入力し公開鍵をコピーします(Next stepは進まない)

f:id:nopipi:20220413204647p:plain:w600

(ubuntu)Cloud9サービスからのSSH接続設定

コピーした公開鍵を、ubuntsのcloud9ユーザに設定します。

sudo -i -u cloud9

echo "[コピーした公開鍵]" >>  ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
(マネコン)environment作成の続き
  • Next stepを実行します
  • ssh接続が成功すると確認画面が表示されるので、Create environmentを実行します。

Pythonで署名付きURLを作成し S3オブジェクトをGET/PUTしてみた

はじめに

この記事は、S3の署名付きURLの学習のために署名付きURLを利用してS3のオブジェクトをPUT/GETしたときの手順をまとめたものです。
ポイントは以下のとおりです。

  • S3の署名付きURLは、AWS CLISDK、マネージメントコンソールで生成可能
  • ただしAWS CLIでは、オブジェクトのPUT(アップロード)用の署名付きURLは不可
  • boto3でS3の署名付きURLを生成する場合は、generate_presigned_urlで生成可能(引数でPUT用/GET用を指定)

手順

環境準備

プロファイル設定
#プロファイルとリージョン設定
PROFILE="<利用したいプロファイル名:デフォルトの場合はdefaultを指定>"
REGION="<実行したいリージョン:ap-northeast-1など>"

aws --profile ${PROFILE} --region ${REGION} sts get-caller-identity
S3バケット作成
#S3バケット作成
GET_BUCKET="xxxxx-get-bucket"
PUT_BUCKET="xxxxx-put-bucket"
OBJECT="hello-world.txt"


aws --profile ${PROFILE} --region ${REGION} s3api create-bucket --bucket "${GET_BUCKET}" --create-bucket-configuration "LocationConstraint=${REGION}"
aws --profile ${PROFILE} --region ${REGION} s3api create-bucket --bucket "${PUT_BUCKET}" --create-bucket-configuration "LocationConstraint=${REGION}"

#GET用バケットへのオブジェクト配置
echo 'Hello World!!' > ${OBJECT}
aws s3 --profile ${PROFILE} --region ${REGION} cp "${OBJECT}" "s3://${GET_BUCKET}"
aws s3 --profile ${PROFILE} --region ${REGION} ls "s3://${GET_BUCKET}"
EC2用のインスタンスロール作成
#S3アクセス用のインスタンスロール作成
#IAMロール作成
POLICY='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}'
aws --profile ${PROFILE} \
    iam create-role \
        --role-name "EC2-PreSignS3Test" \
        --assume-role-policy-document "${POLICY}" \
        --max-session-duration 43200

# SSM用カスタマー管理ポリシーのアタッチ(SSMでインスタンスログインするためで検証自体としては不要)
aws --profile ${PROFILE} \
    iam attach-role-policy \
        --role-name "EC2-PreSignS3Test" \
        --policy-arn arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

#S3アクセス用のインラインポリシー追加
InlinePolicyDocument='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ForGetBucket",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetObject"
      ],
      "Resource": [
        "arn:aws:s3:::'"${GET_BUCKET}"'",
        "arn:aws:s3:::'"${GET_BUCKET}"'/*"
      ]
    },
    {
      "Sid": "ForPutBucket",
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:PutObject"
      ],
      "Resource": [
        "arn:aws:s3:::'"${PUT_BUCKET}"'",
        "arn:aws:s3:::'"${PUT_BUCKET}"'/*"
      ]
    }
  ]
}'
aws --profile ${PROFILE} \
    iam put-role-policy \
        --role-name "EC2-PreSignS3Test" \
        --policy-name "AllowS3Bucket" \
        --policy-document "${InlinePolicyDocument}"

#インスタンスプロファイルの作成
aws --profile ${PROFILE} \
    iam create-instance-profile \
        --instance-profile-name "EC2-PreSignS3Test";

aws --profile ${PROFILE} \
    iam add-role-to-instance-profile \
        --instance-profile-name "EC2-PreSignS3Test" \
        --role-name "EC2-PreSignS3Test" ;

|

EC2用インスタンス作成
PubSubnetID="<インスタンスを作成するパブリックサブネットのID: subnet-xxxx>"
SgId="<インスタンスに付与するサブネットID>"
KEYNAME="<SSH接続する場合はキーペア指定>"

#最新のAmazon Linux2のAMI IDを取得します。
AL2_AMIID=$(aws --profile ${PROFILE} --region ${REGION} --output text \
    ec2 describe-images \
        --owners amazon \
        --filters 'Name=name,Values=amzn2-ami-hvm-2.0.????????.?-x86_64-gp2' \
                  'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' ) ;
echo -e "PubSubnetID = ${PubSubnetID}\nSgId        = ${SgId}\nKEYNAME     = ${KEYNAME}\nAL2_AMIID   = ${AL2_AMIID}"

#インスタンスの作成
#インスタンスタイプ設定
INSTANCE_TYPE="t2.micro"

#タグ設定
TAGJSON='
[
    {
        "ResourceType": "instance",
        "Tags": [
            {
                "Key": "Name",
                "Value": "PreSignS3Test"
            }
        ]
    }
]'

# サーバの起動
aws --profile ${PROFILE} --region ${REGION} \
    ec2 run-instances \
        --image-id ${AL2_AMIID} \
        --instance-type "t2.micro" \
        --subnet-id ${PubSubnetID} \
        --security-group-ids ${SgId} \
        --associate-public-ip-address \
        --tag-specifications "${TAGJSON}" \
        --iam-instance-profile "Name=EC2-PreSignS3Test" \
        --key-name ${KEYNAME} ;  #SSH接続しない場合はこの行を削除する
インスタンス上でのAWS CLI設定
  • 作成したインスタンスにSSMまたはSSHで接続する
  • 下記コマンドを実行しAWS CLI環境をセットアップする
#AWS CLI環境の設定
REGION=$(TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \
              && curl -H "X-aws-ec2-metadata-token: $TOKEN" -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//')
aws configure set region ${REGION}
aws configure set output json

#接続確認
aws sts get-caller-identity

S3署名付きURLでのオブジェクト取得(GET)

AWS CLIでのオブジェクト取得確認
#S3バケット情報の設定
GET_BUCKET="xxxxx-get-bucket"
OBJECT="hello-world.txt"

#AWS CLIでのオブジェクト確認(ファイル一覧にオブジェクトが表示されること)
aws s3 ls "s3://${GET_BUCKET}"

#AWS CLIでのオブジェクト取得確認
aws s3 cp "s3://${GET_BUCKET}/${OBJECT}" .
署名付きURLでのオブジェクト取得用のpythonスクリプト配置
  • python3とboto3のセットアップ
sudo yum -y install python3
sudo pip3 install boto3
  • get.pyというファイル名で以下のファイルを格納
#!/usr/bin/env python3
import sys
import boto3

#引数からバケットとオブジェクト情報を取得
BUCKET_NAME = sys.argv[1]
OBJECT_KEY  = sys.argv[2]
EXPIRESIN   = sys.argv[3]

url = boto3.client('s3').generate_presigned_url(
    ClientMethod='get_object',
    Params={'Bucket':  BUCKET_NAME, 'Key': OBJECT_KEY },
    ExpiresIn=EXPIRESIN)

print(url)
  • 以下のコマンドを実行して署名付きURLを生成
python3 get.py ${GET_BUCKET} ${OBJECT} 60
署名付きURLでのオブジェクトアップロードのpythonスクリプト配置

|

  • put.pyというファイル名で以下のファイルを格納
#!/usr/bin/env python3
import sys
import boto3

#引数からバケットとオブジェクト情報を取得
BUCKET_NAME = sys.argv[1]
OBJECT_KEY  = sys.argv[2]
EXPIRESIN   = sys.argv[3]

url = boto3.client('s3').generate_presigned_url(
    ClientMethod='put_object',
    Params={'Bucket':  BUCKET_NAME, 'Key': OBJECT_KEY },
    ExpiresIn=EXPIRESIN)

print(url)
python3 put.py ${PUT_BUCKET} ${OBJECT} 60
curl -X PUT --upload-file <ファイルパス> "<取得したPre-Signed URL>"
  • 補足事項
    • 上記検証でAmazon S3 から 307 Temporary Redirect レスポンスが返される場合: 作成したS3バケットバケット名がAWSの全リージョンに伝搬し切れてない場合に返されます。バケット名が伝搬するまでしばらく待ってから再実行して下さい。*1

IAM Policy Simulatorの結果をJSON/CSV形式で出力するツール

作成したIAMポリシーやPermissions BoundaryのテストのためにIAM Policy Simulatorを利用すると便利ですが、テスト用のIAMアクションをGUIで作成しないといけないとか、結果をJSONCSV形式の簡易な一覧で取得できない点が悩みでしたので、Pythonで簡単なツールを作ってみました。

f:id:nopipi:20220322233007p:plain

使い方

コードは下記のGitHubレポジトリを参照下さい。セットアップ方法も以下のGitHubレポジトリを参照下さい。

評価対象のアクション一覧を準備

IAM Policy Simulatorで評価したいアクション一覧を以下の形式のJSONで準備します。

{
    "Action": [
        "iam:CreateServiceSpecificCredential",
        "iam:DeactivateMFADevice",
        <中略>
        "iam:DeletePolicyVersion",
        "iam:ListUserTags"
    ]
}
{{<
** IAM Policy Simulatorでの評価
ツールの実行
>|sh|
./simulate_iampolicy.py [-h] [-P PROFILE] [-c] -p POLICY_SOURCE_ARN -a ACTION_LIST_FILE
  • 必須オプション
    • -p POLICY_SOURCE_ARN : IAMユーザ/グループ/ロールのARNを指定
    • -a ACTION_LIST_FILE : 評価したいアクション一覧のJSONファイルパスを指定
  • 任意オプション
    • -P PROFILE : AWS CLI/boto3のプロファイルを明示的に指定する
    • -c: 結果をCSV形式で出力したい場合に指定。未指定時はJSON形式で結果を出力する
(実行例1) IAMロールに対して、IAMのアクション一覧を評価しJSON形式で出力する
./simulate_iampolicy.py -p 'arn:aws:iam::999999999999:role/RoleName' -a 'actions/iam_all_actions.json'
(実行例2) Hogeプロファイルのアカウントの特定IAMユーザ対し、S3のアクション一覧を評価しCSV形式で出力する
./simulate_iampolicy.py -P Hoge -c -p 'arn:aws:iam::999999999999:user/UserName' -a 'actions/s3_all_actions.json'

実行結果

[
  {
    "EvalActionName": "iam:CreateServiceSpecificCredential",
    "EvalDecision": "allowed"
  },
  {
    "EvalActionName": "iam:DeactivateMFADevice",
    "EvalDecision": "explicitDeny"
  },
]
EvalActionName,EvalDecision
iam:CreateServiceSpecificCredential,allowed
iam:DeactivateMFADevice,explicitDeny
  • 結果内容
    • EvalActionName : 評価対象のアクション名
    • EvalDecision : 評価結果
      • allowed : アクションを許可
      • explicitDeny : アクションを明示的に拒否
      • implicitDeny : アクションを暗黙的に拒否

Amazon Linux2にterraformをセットアップする

Googleで検索すれば記事は多数出てくるのですが、勉強と備忘録のために記録します。

Terraformのセットアップ

Cloud9(Amazon Linux2ベース)はデフォルトでterraformが入ってるので、セットアップはスキップできます。

  • Cloud9 on Amazon Linux2 : デフォルトでインストールされているのでセットアップ不要(2021/6現在)
  • Amazon Linux2: セットアップが必要

セットアップする場合は、Terraformのドキュメントにインストール手順が記載されているので、その内容を見ながらセットアップすればOKです。
learn.hashicorp.com

TerraformのYumレポジトリ の登録

Terraformのrpmパッケージは、Terraform提供元のHashiCorpのオフィシャルのyumレポジトリ から取得することができます。(Amazon LinuxではTerraformのパッケージは配布していない)
そのため、まずHashiCorpのオフィシャルのyumレポジトリをyumに登録する必要があります。

  • yum-config-managerコマンドのインストール
sudo yum install -y yum-utils
  • HashiCorpのオフィシャルのyumレポジトリの追加
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo

Terraformのインストール

sudo yum -y install terraform

Terraformのテスト

terraform -help

フォワードプロキシ(NLB+EC2(AutoScaling))を作成するCloudFormation

概要

NLB + EC2(Squid)構成のフォワードプロキシを作成するCloudFormationのテンプレートです。
ソースコードこちらのGitHubにあります。

f:id:nopipi:20220212224430p:plain:w600

  • クライアントはNLBに対してTCPの3128ポートにプロキシ接続してアクセスして利用します。
  • プロキシはSquidを利用
  • SquidのログはLogsに出力(CloudWatchエージェントを利用)

利用方法

前提条件

  • パブリックサブネットを有するVPCがあること
  • CloudFormationのデプロイ用にAdministratorAccess権限のある実行環境があること(CLIまたはマネージメントコンソール)
  • CLIを利用する場合はAWS CLIがセットアップされ、上記のAdministratorAccessで実行可能なプロファイルが設定済みであること
  • gitコマンドが利用可能なこと

(オプション)AWS CLIのための準備

手順でAWS CLIを利用する場合は事前に以下の設定を行う

export PROFILE="default"       #Specify a profile with AdministratorAccess privileges
export REGION="ap-northeast-1" #Specify Region

#動作テスト
aws --profile ${PROFILE} --region ${REGION} sts get-caller-identity

#以下のようにUserID/Account/Arnが表示されればOK
{
    "UserId": "AROAZKIQKBDEIBOE2MGEJ:botocore-session-xxxxxxxxxx",
    "Account": "999999999999",
    "Arn": "arn:aws:sts::999999999999:assumed-role/OrganizationAccountAccessRole/botocore-session-xxxxxxxxxx"
}

コードのダウンロード

git clone https://github.com/Noppy/CFn_ForwardProxy.git
cd CFn_ForwardProxy

設定情報の取得

CloudFormationのスタック作成時のパラメーターに設定する以下の情報を事前に控えておく

  • VPC構成情報
    • VPC ID
    • フォワードプロキシのEC2インスタンスを配置するパブリックサブネットのID(マルチAZ構成の場合は複数確認)
    • NLBを配置するプライベートまたはパブリックサブネットのID(マルチAZ構成の場合は複数確認)
  • Amazon Linux2(x86_64)のAMI ID
AMZ2_AMIID=$(aws --profile ${PROFILE} --region ${REGION} --output text \
    ec2 describe-images \
        --owners amazon \
        --filters 'Name=name,Values=amzn2-ami-kernel-5.10-hvm-2.0.????????.?-x86_64-gp2' \
                      'Name=state,Values=available' \
    --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' ) ;
echo "AMZ2_AMIID = ${AMZ2_AMIID}"

CloudFormationによるデプロイ

  • マネージメントコンソールの場合:
    • 具体的な操作手順は、CloudFormationのユーザーガイドを参照下さい。
    • テンプレートは、./src/forward_proxy.yaml
    • パラメータ: 上記の「設定情報の取得」で確認した情報を設定します。
  • CUIの場合:
# 「設定情報の取得」で確認した情報を設定
VPCID="<VPC ID>" 
PubSubIds="<PubSub1Id>,<PubSub2Id>,<Pubsub3Id>"
ElbSubIds="<ElbSub1Id>,<Elbsub2Id>,<ElbSub3Id>"
SrcCidr="<CIDR of VPC>"

# パラメータ用のJSON生成
CFN_STACK_PARAMETERS='
[
    {
        "ParameterKey": "VpcId",
        "ParameterValue": "'"${VPCID}"'"
    },
    {
        "ParameterKey": "PublicSubnets",
        "ParameterValue": "'"${PubSubIds}"'"
    },
    {
        "ParameterKey": "ElbSubnets",
        "ParameterValue": "'"${ElbSubIds}"'"
    },
    {
        "ParameterKey": "ProxyAmiId",
        "ParameterValue": "'"${AMZ2_AMIID}"'"
    },
    {
        "ParameterKey": "AllowCidrBlockForProxy",
        "ParameterValue": "'"${SrcCidr}"'"
    }
]'

# スタックの作成
aws --profile ${PROFILE} --region ${REGION} \
    cloudformation create-stack \
        --stack-name "ForwardProxy" \
        --template-body "file://./src/forward_proxy.yaml" \
--parameters "${CFN_STACK_PARAMETERS}" \
--capabilities CAPABILITY_IAM ;

NLBのDNS取得

  • マネージメントコンソールの場合:
    • 作成したスタックのOutputsで確認する
  • CUIの場合:
ProxyDns=$(aws --profile ${PROFILE} --region ${REGION} --output text \
    cloudformation describe-stacks \
        --stack-name "ForwardProxy" \
        --query 'Stacks[].Outputs[?OutputKey==`LoadBalancerDns`].[OutputValue]')
echo "ProxyDns = ${ProxyDns}"

テスト

Linuxサーバをクライアントとしてテストする場合の手順をいかに示します。

ProxyDns="<「NLBのDNS取得」で取得したDNS情報>"
export http_proxy="http://${ProxyDns}:3128"
export https_proxy="http://${ProxyDns}:3128"

# Test
# "https://www.google.co.jp" is allowed, "https://www.yahoo.co.jp" is denied.
curl https://www.google.co.jp
curl https://www.yahoo.co.jp

Amazon Linux2にCloudWatchエージェントを設定する手順(ユーザデータでの手順あり)

  • はじめに
  • CloudWatchエージェントとは*1
  • CloudWatchエージェント設定手順概要
  • CloudWatchエージェントの初期設定手順
    • (事前準備) IAMロール&EC2インスタンスの作成
    • CloudWatchエージェントのインストール
    • CloudWatch設定ファイル作成
    • CloudWatch設定ファイルのフェッチ
    • CloudWatchエージェントの起動*5
    • 補足
      • 設定フェッチと再起動を1コマンドで実行する
      • CloudWatchエージェントのログファイル
  • Systems Manager Parameter Storeで設定を配布する場合
    • Parameter Storeへの設定ファイル格納
    • CloudWatchエージェントを稼働させるAmazon Linux2サーバでの操作
  • ユーザデータでCloudWatchエージェントを設定する

はじめに

Amazon Linux2にCloudWatchエージェントをセットアップする手順をまとめました。ドキュメントではSSMから操作する手順をメインにしていますが、AutoScalingなどでUserDataにセットアップ手順を埋め込むことを考慮し全てコマンドラインでセットアップが完結するように手順を整理しています。

ポイントは以下の4点です。

  1. エージェントの操作はamazon-cloudwatch-agent-ctlを利用する
  2. Wizardや手動作成した設定ファイルはフェッチしないと有効化されない
  3. OS自動起動amazon-cloudwatch-agent-ctlからのエージェント起動時に自動設定される
  4. collectdを未使用時でも、Wizardではデフォルトcollectdを有効化するためcollectdパケージをインストールしておくほうが無難

なおCloudWatchエージェントの標準のカスタムメトリクスとログ取得の範囲で利用している場合は、collectdとStatsDは必要ありません。

CloudWatchエージェントとは*1

CloudWatchエージェントは、EC2やオンプレのLinux/Windowsで稼働し、OSやアプリケーションの情報をCloudWatchに送信するエージェントソフトウェアです。
具体的には以下の情報をCloudWatchに送信することができます。

  • OSの稼働情報(CPU/メモリ/ディスクなど)を取得し、カスタムメトリクスとしてCloudWatchへ送信する
  • (Option)CloudWatch エージェントで定義されていない稼働情報は、collectdプロトコルStatsDプロトコルを介して取得することでカスタムメトリクスとしてCloudWatchへ送信する事が可能
  • (Option)指定したログファイルをCloudWatch Logsへ送信する

f:id:nopipi:20220212130803p:plain:w600

なおcollectdStatsDの要否は以下の通りです。
CloudWatchエージェントの標準のカスタムメトリクスとログ取得の範囲で利用している場合は、どちらの機能も不要となります。

  • collectd: CloudWatchエージェントで準備されているメトリクス(CPU/メモリ/ディスク)以外に追加のメトリクスを独自取得したい場合に利用する
  • StatsD: ユーザアプリケーションから出力される稼働データをCloudWatchエージェントからカスタムメトリクスとして送信したい場合(ユーザアプリでStatsDプロトコルでの送信の実装が必要)

CloudWatchエージェント設定手順概要

CloudWatchエージェントは、Amazon Linux2の場合/opt/aws/amazon-cloudwatch-agentにインストールされ、操作用のコマンドは/opt/aws/amazon-cloudwatch-agent/binに配置されます。主に利用するコマンドは、(1)設定ファイル生成用ウィザードのamazon-cloudwatch-agent-config-wizardと、(2)操作用のamazon-cloudwatch-agent-ctl、の2つになります。

CloudWatchエージェントを利用するための手順概要は以下の通りです。

  1. CloudWatchエージェントのインストール
  2. 設定ファイルの作成(手動またはamazon-cloudwatch-agent-config-wizardによる作成)
  3. 設定ファイルのフェッチ
  4. CloudWatchエージェントの起動

f:id:nopipi:20220212135002p:plain:w600

続きを読む

AutoScaling Groupや起動テンプレートでEC2インスタンスの送信元/送信先チェックを変更する方法

EC2インスタンスの送信元/送信先チェック(source/destination checking)を、AutoScalingするインスタンスに設定しようとしても、Auto Scalingグループや起動テンプレートでは、送信元/送信先チェックの設定用のパラメータがないため設定できない。そのため、インスタンスのユーザデータでインスタンスIDとリージョン情報を取得して無理くり実行するしかない。

ということで起動サンプルが以下です。

設定

ユーザデータ

ユーザでは、EC2インスタンスメタデータからインスタンスIDとリージョン情報を取得して、AWS CLIを利用し自分自身のEC2インスタンスの設定変更を行います。
こちらでは、Instance Metadata Service Version 2 (IMDSv2)に対応した書き方になっています。

#!/bin/sh

# Disable Source/Destination Check 
yum -y install jq
INSTANCE_ID=$( TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \
    && curl -H "X-aws-ec2-metadata-token: $TOKEN" -v "http://169.254.169.254/latest/meta-data/instance-id");

REGION=$( TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` \
    && curl -H "X-aws-ec2-metadata-token: $TOKEN" -v "http://169.254.169.254/latest/dynamic/instance-identity/document" | \
    jq -r '.region' )

aws --region "${REGION}" ec2 modify-instance-attribute --instance-id "${INSTANCE_ID}" --source-dest-check '{"Value": false}'

EC2インスタンスロールに付与するIAMポリシー

実行するEC2インスタンスには、以下のIAMポリシーが付与されたEC2インスタンスロール が必要です。こちらの権限がないとAccessDenyでawsコマンド実行時にアベンドします。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "ec2:ModifyInstanceAttribute"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}

AWS CLIのAssumeRoleするプロファイル設定を使って、AssumeRoleのクレデンシャルを取得し環境変数に設定するシェル芸 ワンライナーを作ってみた

見やすいようにバックスラッシュで行を分けてますが、ワンライナーです。
terraformをローカルマシンで実行する時に、AWSプロバイダーの部分にローカルマシン固有のプロファイルとか埋め込みたくないけど、都度クレデンシャルを手で環境変数に設定するのは手間だということで作ってみたものです。

AWS_BASE_PROFILE="default"; \
AWS_ASSUME_PROFILE="<assume先のプロファイル>"; \
aws --profile ${AWS_BASE_PROFILE} \
    sts assume-role \
        --role-arn $(aws configure get ${AWS_ASSUME_PROFILE}.role_arn) \
        --role-session-name ${AWS_ASSUME_PROFILE}-session \
    --query 'Credentials.{
              AWS_ACCESS_KEY_ID:AccessKeyId,
              AWS_SECRET_ACCESS_KEY:SecretAccessKey,
              AWS_SESSION_TOKEN:SessionToken
    }' | \
jq -r 'to_entries | .[] | [.key + " " + .value]|@tsv' |
while read key value; do export $key=$value; done

1行にするとこんな感じ。

AWS_BASE_PROFILE="default"; AWS_ASSUME_PROFILE="<assume先のプロファイル>"; aws --profile ${AWS_BASE_PROFILE} sts assume-role --role-arn $(aws configure get ${AWS_ASSUME_PROFILE}.role_arn) --role-session-name ${AWS_ASSUME_PROFILE}-session --query 'Credentials.{AWS_ACCESS_KEY_ID:AccessKeyId,AWS_SECRET_ACCESS_KEY:SecretAccessKey,AWS_SESSION_TOKEN:SessionToken}' |jq -r 'to_entries | .[] | [.key + " " + .value]|@tsv' |while read key value; do export $key=$value; done

terraformでSlackチャンネルを作成するための手順

概要

terraformからpablovarela/slackプロバイダーを利用し、slackのchannelを作成するための手順です。

slackを外部のアプリケーションから操作するためには、slackでアプリケーション(App)を作成する必要があります。作成したAppに必要な権限を付与し、App操作のためのTOKENを利用しterrafomから実行します。今回利用したpablovarela/slackプロバイダーは、環境変数SLACK_TOKENでTOKENをterraformに渡せますのでその方式を利用します。(セキュリティーの面からコードへのTOKEN埋め込みは避けたいため)

あとはterraformでコーディングして実行して、Slackのチャンネルを作成します。

f:id:nopipi:20210921001409p:plain

手順

SlackのApp作成

terraformで操作を行うため、SlackのApp(ポット)を作成します。

f:id:nopipi:20210920224224p:plain:w400

  • Create an appFrom scratchを選択

f:id:nopipi:20210920224833p:plain:w400

  • Appの名前とワークスペースを選択する
    • App Name : 今回はexecute-terraformという名称を設定
    • Pick a workspace to develop your app in: Appをインストールしたいワークスペースを指定する

f:id:nopipi:20210920225258p:plain:w400

  • Appのボット用TOKENに権限を付与する
    • 左のバーからOAuth & Permissionsを選択する
    • Scopesで、 Bot Token ScopesAdd an OAuth Scopeから必要な権限を追加する
    • 権限は以下の6つを追加する
      • terraformのResourceslack_conversation用権限
        • channels:manage
        • channels:read
        • groups:write
        • groups:read
      • terraformのData sourceslack_user用権限
        • users:read
        • users:read.email

f:id:nopipi:20210920230936p:plain:w400

f:id:nopipi:20210920231856p:plain:w400

f:id:nopipi:20210920231950p:plain:w400

  • ワークスペースへのAppインストール
    • 同じ画面上部のOAuth Tokens for Your Workspaceに移動し、Install to Workspace>を実行
    • 選択先の画面で許可を行う
  • TOKENの取得
    • Bot User OAuth Tokenに表示されているトークンを取得する

f:id:nopipi:20210920233403p:plain

terrafomのコード

terrafomのセットアップは、こちらを参照ください。

SlackのTOKEN設定

terrafomでSlackを操作するSlack Providerには、環境変数を利用しTOKENを渡します。

export SLACK_TOKEN="<Appで生成したtoken>"

terrafomでのSlackチャンネル作成のサンプルコード

Slack チャンネル作成用のサンプルです。
terraformの実用的な実装を考慮しmodule構成にはしていますが、blogでの視認性の観点からファイル分割や環境毎のディレクトリ分割はしていません。

.
├── main.tf
└── modules
    └── slack_channel
        └── module_main.tf
  • ./main.tf: プロバイダー設定 & モジュールの呼び出し
#----------------------------
# slack provider
#----------------------------
terraform {
  required_providers {
    slack = {
      source  = "pablovarela/slack"
      version = "~> 1.0"
    }
  }
  required_version = ">= 0.13"
}

provider "slack" {}

#----------------------------
# main
#----------------------------
module "slack_channelt" {
  source = "./modules/slack_channel"

  name             = "sample-channel"
  topic            = "This is sample channel for terrafom demo."
  slack_users_mail = ["clausvalca813@gmail.com", "n.fujita74@gmail.com"]
}
  • ./modules/slack_channel/module_main.tf: 最小モジュールのサンプル
#---------------------------
# Variable設定
#---------------------------
variable "name" {
  type = string
}

variable "topic" {
  type = string
}

variable "slack_users_mail" {
  type = list(string)
}

#---------------------------
# Provider設定
#---------------------------
terraform {
  required_providers {
    slack = {
      source  = "pablovarela/slack"
      version = "~> 1.0"
    }
  }
  required_version = ">= 0.13"
}

#---------------------------
# channel設定
#---------------------------
# 追加するユーザのメール情報からユーザIDの配列を生成
data "slack_user" "users" {
  for_each = toset(var.slack_users_mail)

  email = each.key
}

locals {
  users_list = [for i in data.slack_user.users : i.id]
}


# Create a Slack channel
resource "slack_conversation" "individual_alert_channel" {
  name              = var.name
  topic             = var.topic
  permanent_members = local.users_list
  is_archived       = false
  is_private        = false
}

ポイントは以下の通りです。

  • provider設定
    • 今回利用したプロバイダーは、 required_providersを設定しないと動作しないので要設定
    • module側でも同じ設定が必要
    • 設定内容は、こちらのドキュメントを参照
  • チャンネルに参加させるユーザ
    • slack_userでメールアドレスからSlackユーザを特定し、localsでユーザIDを配列にする

terraformの実行

terraform init
terraform apply

実行時の注意点

  • terraform destroyなどでチャンネルを削除しても、チャンネルはアーカイブされるだけで削除はされない
    • そのため再度チャンネルを作成しようとすると、エラー(name_taken: A channel cannot be created with the given name)が発生する
    • 再作成する場合は、事前にチャンネル管理者によりアーカイブされた該当チャンネルを削除する必要がある