シ〜らかんす

プログラミングとか、カメラとか。

ECRセキュリティスキャンの結果をslackに通知する

構成図

以下の通り、ECR セキュリティスキャン -> AWS Event Bridge -> SNS -> Chatbot -> slack という流れで通知ができる。

ECRセキュリティスキャンをEventBridgeのイベントとして使う

多くのAWSの各マネージドサービスが、自らEventBridgeにイベントを送信しており、その一覧は以下のページで見ることができる。

イベントブリッジのイベント一覧

イベント送信の信頼性には「Guaranteed」「Best Effort」の2種類がある。 Guaranteedであれば、少なくとも1回は送信が保証されるが、Best Effortの場合は稀にイベントが送信されないことがある。

ECRのセキュリティスキャンは、EventBridgeに送信されるイベントの一つであり、信頼性はBest Effortとなっている。

公式ドキュメント でも、ECRセキュリティスキャンがEventBridgeに送信するイベントの例として使われている。

Github ActionsからAWS SSMのRun Commandを使う

動作環境

やりたいこと

mainブランチへのマージをトリガーに、EC2上で git pull コマンドを実行して最新のコードを反映させたい

というのが今回のやりたいことです。

前提

  • EC2がRunning状態である
  • ec2-userでgitコマンドが使える状態にある
  • githubリポジトリのSecretsの設定にAWSのクレデンシャル情報を登録済みである

どうやるか

今回は、github actionsと、AWS Systems Managerを使用して、EC2インスタンス上でコマンドを実行させてみようと思います。

SSM Agentのセットアップ

まず最初のステップは、EC2にSSM Agentをインストールすることです。

インスタンスの種類によってはデフォルトでインストールされているようですが、今回利用するRed Hat Enterprise Linux 8のインスタンスには入っていないので、入れる必要があります。

公式ドキュメントのこちらのページRHELでのインストール方法が記されていますので、これの通りやります。

RHEL8系のインストール手順を見ると、まずは事前準備としてpython2か3が入っている必要があるようなので、入れます。

$ sudo yum install python3.9

続いて、SSMのインストーラーをダウンロードします。

$ sudo dnf install -y https://s3.ap-northeast-1.amazonaws.com/amazon-ssm-ap-northeast-1/latest/linux_amd64/amazon-ssm-agent.rpm

無事完了すると、SSM Agentのサービスプロセスが起動していることを確認できます。

$ sudo systemctl status amazon-ssm-agent
● amazon-ssm-agent.service - amazon-ssm-agent
   Loaded: loaded (/etc/systemd/system/amazon-ssm-agent.service; enabled; vendor preset: disabled)
   Active: active (running) since Sun 2021-08-15 23:41:54 JST; 42s ago
 Main PID: 20343 (amazon-ssm-agen)
    Tasks: 13 (limit: 4821)
   Memory: 27.3M
   CGroup: /system.slice/amazon-ssm-agent.service
           ├─20343 /usr/bin/amazon-ssm-agent
           └─20397 /usr/bin/ssm-agent-worker

IAM Roleの作成

続いて、EC2に付与するIAMロールの作成を行います。

management consoleからやります。

IAMのコンソールからロールの作成に行き、ユースケースとしてEC2を選択します。

f:id:sas-surfer0jonny:20210816000504p:plain

続いて、ポリシーを付与する画面で、「AmazonEC2RoleforSSM」と検索して、出てきたポリシーを付与します。

f:id:sas-surfer0jonny:20210816000617p:plain

あとは、お好きな名前とdescriptionを書いてRoleを作成して、EC2インスタンスに付与してください。

AWSコンソール上でRun Commandする

github actionsの設定に入る前に、SSM Run Commandが動くかどうかの確認と、github actions上で実行するコマンドを明らかにしておきましょう。

そのために、AWSコンソール上からRun Commandを実行し、git pull コマンドを実行させてみます。

AWS SSMのコンソールの左のナビゲーションの中に、「Run Command」があるので、そこから「コマンドの実行」へ移ります。 f:id:sas-surfer0jonny:20210816091911p:plain

f:id:sas-surfer0jonny:20210816092023p:plain

コマンドドキュメントとして「AWS-RunShellScript」を選びます。

f:id:sas-surfer0jonny:20210816092222p:plain

コマンドのパラメータの入力欄に、実行したいコマンドを書き込みます。

注意点として、SSMのRun Commandはrootユーザーで実行されてしまうので、ec2-userでgitの設定を行なっている場合はうまく動きません。

git pullコマンドを実行するときは、sudo -u ec2-user git pull とすることでec2-userとして実行させましょう。

f:id:sas-surfer0jonny:20210816094505p:plain

次に、ターゲットとして、コマンドを実行したいEC2を選びます。 タグやリソースグループでの指定も行えるようですが、今回はインスタンスを手動で選択します。

f:id:sas-surfer0jonny:20210816092917p:plain

SSMのRun Commandのコンソールでは、親切にもAWS CLIで同じRun Commandを実行する際のコマンドを自動で生成してくれていますので、控えておきましょう。

f:id:sas-surfer0jonny:20210816093327p:plain

実行し、しばらくすると成功か失敗かわかります。

github actions上からRun Commandを実行する

すでにSSM Run Commandを実行するコマンドはわかっているので、あとはGithub Actionsから実行してやるだけです。

.github/workflows/ 配下にyamlを作成し、以下のように書きます。

name: Deploy to Sandbox
on:
  push:
    branches:
      - main

env:
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-1

      - name: Execute SSM Run Command
        id: exec
        run: |
          export RESPONSE=$(aws ssm send-command --document-name "AWS-RunShellScript" --document-version "1" --targets '[{"Key":"InstanceIds","Values":["i-078b1fde3fc75d7fb"]}]' --parameters '{"workingDirectory":[""],"executionTimeout":["3600"],"commands":["cd /home/ec2-user/grpc-sample/", "sudo -u ec2-user git pull"]}' --timeout-seconds 600 --max-concurrency "50" --max-errors "0" --region ap-northeast-1)
          export COMMAND_ID=$(echo $RESPONSE | jq .Command.CommandId)
          echo "::set-output name=commandId::${COMMAND_ID}"

      - name: Check Run Command Result
        run: |
          bash -x ./.github/scripts/check_command_result.sh ${{ steps.exec.outputs.commandId }}

「Execute SSM Run Command」のステップでRun Commandを実行した後に、「Check Run Command Result」のステップで、Run Commandの成否をチェックしています。

シェルスクリプトで実行していて、そのコードは以下の通りです。

#!/bin/bash

function succeeded () {
  status=$(aws ssm get-command-invocation --command-id $1 --instance-id ご自身のEC2インスタンスIDに書き換えてください | jq .Status)
  if [ $status = "Success" ]; then
    echo true
  else
    echo false
  fi
}

commandId=$1
i=1

while [ $i -le 10 ];
do
  succeeded=$(succeeded $commandId)
  if [ $succeeded ]; then
    exit 0
  fi
  i=$($i + 1 )
  sleep 3
done

exit 1

Linux(RHEL8)でロケール設定を行う

動作環境

ロケールとは

ロケールとは、コンピュータにおける言語や地域の設定のことです。

たとえば、日本に設定した場合は、言語は「日本語」、通貨は「円」といった日本地域固有の情報を使ってユーザーとコンピュータがやりとりすることになります。

現在のロケール設定を確認する

ロケールの設定には localectl コマンドを使います。

EC2のRHEL8では、初期設定のロケールはUSになっています。

現在のロケール設定を確認するには、 localectl status を使います。

$ localectl status
   System Locale: LANG=en_US.UTF-8
       VC Keymap: us
      X11 Layout: us

日本のロケール設定が利用可能か調べる

$ localectl list-locales | grep ja

何も返ってきませんでした。 日本のロケールを利用可能にするために追加してやる必要があります。

日本のロケール設定を使えるようにする

yumで日本語関連のパッケージをインストールします。

$ sudo yum install glibc-langpack-ja

すると、日本語のロケールが利用可能になります

$ localectl list-locales | grep ja
ja_JP.eucjp
ja_JP.utf8

ロケールを日本語に設定する

$ sudo localectl set-locale LANG=ja_JP.utf8

確認すると、システムロケールがja_JP.utf8になっています。

$ localectl status
   System Locale: LANG=ja_JP.utf8
       VC Keymap: us
      X11 Layout: us

Linux(RHEL8)でtimezoneをjstにする

実行環境

  • AWS EC2
  • RHEL8

変更前の設定を確認

timedatectlコマンドで現状のtimezone設定を確認します。

$ timedatectl status
               Local time: Sun 2021-08-15 09:08:12 UTC
           Universal time: Sun 2021-08-15 09:08:12 UTC
                 RTC time: Sun 2021-08-15 09:08:12
                Time zone: UTC (UTC, +0000)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

UTCになっています。

JSTにtimezoneを変更する

$ sudo timedatectl set-timezone Asia/Tokyo

結果を確認

ちゃんとタイムゾーンが変わっているか確認します。

$ timedatectl status
               Local time: Sun 2021-08-15 20:38:53 JST
           Universal time: Sun 2021-08-15 11:38:53 UTC
                 RTC time: Sun 2021-08-15 11:38:53
                Time zone: Asia/Tokyo (JST, +0900)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

JSTになりました!!

ghq & hubを使って、organizationの全リポジトリをcloneする方法

最初に結論から

hubとghqを利用すると、orgs配下の全てのリポジトリをcloneすることができます。 転職したときなど、新しいorgに参加した際の初期セットアップの際に便利です。

導入の動機は?

転職した会社のリポジトリをとりあえず全部cloneしようと思ったけど、100以上リポジトリがあった。 100回以上git clone唱えるのが嫌だったので、一括でできる方法を探した。

手順をご紹介

まずは、事前準備

参考までに、~/.gitconfig 内にこういう風に書くと、ghqのrootディレクトリを ~/ghqに設定できます。

[ghq]
    root = ~/ghq

orgsの全リポジトリを実際にcloneするコマンドを順に紹介

  1. まずは、リポジトリのリストをhub apiコマンドで取得する
hub api orgs/{あなたのorgの名前}/repos | jq --raw-output '.[].full_name' > repolist.txt

ページングを利用する場合(100件ずつ)

hub api orgs/{あなたのorgの名前}/repos\?per_page=100\&page=1 | jq --raw-output '.[].full_name' > repolist.txt
hub api orgs/{あなたのorgの名前}/repos\?per_page=100\&page=2 | jq --raw-output '.[].full_name' >> repolist.txt
  1. repolist.txtから、全件リポジトリをcloneする
cat repolist.txt | ghq get --parallel -p

結果

ghqのrootディレクトリが仮に ~/ghq/ に設定されているとすると、 ~/ghq/github.com/{あなたのorgの名前}/ 配下に、全てのリポジトリがcloneされているはずです!

【Python】argparseで任意引数を受け取る方法

pythonで引数を受け取る方法

argparseというライブラリを使うと、簡単に引数を足すことができます。

import argparse

def main(start_at, end_at):
    do_something

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("start_at", help="対象年月日のはじめ")
    parser.add_argument("end_at", help="対象年月日の終わり")
    args = parser.parse_args()

    main(args.start_at, args.end_at)

sysを使って引数を受け取ることもできますが、引数を扱う上で便利な機能がargparseの方が充実している印象です。

  • add_argumentメソッドのhelp引数に、説明を加えておくことができ、ドキュメンテーションラク
  • add_argumentメソッドのaction引数に、store_truestore_false など入れて、型や値を指定できる etc...

任意の引数を受け取るには?

上記のコードでは、start_atとend_atという引数は必ずなくてはならず、存在しないとエラーになってしまいます。

引数の付与は任意、としたい場合は、下記のように書けます。

import argparse

def main(start_at, end_at):
    do_something

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--start_at", help="対象年月日のはじめ")
    parser.add_argument("--end_at", help="対象年月日の終わり")
    args = parser.parse_args()

    main(args.start_at, args.end_at)

add_argumentメソッドで引数の名前を指定する時に、名前の直前に -- (ハイフン二つ)を足しています。

こうすることで、start_atとend_atはあってもなくてもいい、任意の引数として設定されました。

任意引数を与えたいときのコマンドの書き方

任意引数は、コマンドを書く時に以下のように「引数ありますよー」ということを明記してやる必要があるので注意してください。

python sample.py --start_at=2019-03-19 --end_at=2019-03-20

これがもし必須の引数の場合は、以下のような書き方ができます。

pythono sample.py 2019-03-19 2019-03-20

参考サイト

【Django】モデルの複数カラムをセットでユニークに設定する方法

いつもやり方忘れてしまうので、メモ。

単一カラムに対するユニーク設定

djangoでモデルのあるカラムをユニークにするには、 以下のようにモデル定義のオプション引数として unique=True を渡してやればOKです。

from django.db import models


class SomeModel(models.Model):
    column1 = models.CharField(max_length=255, unique=True)

複数カラムセットでユニークにする設定

カラム一つに対してユニーク設定をかけたい場合は上記で問題ありませんが、「複数カラムセットでユニーク」な設定をかけたい場合は、以下のように書く必要があります。

from django.db import models


class SomeModel(models.Model):
    column1 = models.CharField(max_length=255)
    column2 = models.CharField(max_length=255)

    class Meta:
        unique_together = ("column1", "column2",)

参考にしたサイト

https://docs.djangoproject.com/en/2.1/ref/models/fields/#django.db.models.Field.unique

https://stackoverflow.com/questions/2201598/how-to-define-two-fields-unique-as-couple