boto3でリトライの設定を変更する

python 3.7
boto3 1.4.4

AWS でのエラーの再試行とエクスポネンシャルバックオフ

各 AWS SDK は、自動再試行ロジックを実装しています

設定ファイルで変更するか、コードで変更するかのどちらか
設定ファイルの場合、AWS CLIでも有効になる

設定ファイル

~/.aws/models/_retry.jsonを用意する。書き方はbotocoreパッケージの_retry.jsonと同じ。
http://botocore.readthedocs.io/en/latest/reference/loaders.html

clientのリトライハンドラーの持つ値を直接変更する

boto2.num_retries equivalent in retryhandler.py or _retry.json #882

import boto3

client = boto3.client('stepfunctions')
client.meta.events._unique_id_handlers['retry-config-states']['handler']._checker.__dict__['_max_attempts'] = 1

retry-config-statesstates部分はサービスごとの値。上の例だと、stepfunctionsでの再試行回数を1に設定している。


現在、セッションごとに設定値を指定できるようなPRがマージを待ってる
Add possibility of modifying retry_config #891

続きを読む

AnsibleでRoute53のレコード情報をいい感じにエクスポートする(全件対応版)

はじめに

前回、こちらでRoute53のレコード一覧をAnsibleでエクスポートする事例をご紹介しましたが、Ansibleのroute53_factsモジュールの仕様上、ゾーンあたり100件までしかレコードを取得できないことが分かりました。
なんとか100件以上のレコードをエクスポートしたいと思い色々調べていたところ、Ansibleのモジュールだと難しいですが、botoに同梱されているroute53コマンドが使えることが分かりました。

というわけで、route53コマンドをAnsibleから実行することで全件エクスポートを実現しようと思います。

前提

  • AWS関連のモジュール実行にはbotoが必要です。
  • route53コマンドが必要です(botoに同梱)。
  • credential情報については、環境変数AWS_DEFAULT_PROFILEで適切なprofileが選択されていることとします。

sample

やること

取得したroute53のレコードセット情報を、ゾーンごとにworkディレクトリに保存する

ディレクトリ構成

ディレクトリ構成
site.yml
roles/
|--route53_export/
|  |--tasks/
|  |  |--main.yml
|  |--templates/
|  |  |--save_record_sets.j2
hosts/aws                  #inventory
work/                      #ゾーン単位でレコード情報が出力される
|--testdomain.com.txt        #出力されたレコード情報

inventory

AWSリソース関連モジュールはすべてlocalhostで実行するので、下記のようなインベントリファイルを用意します。

hosts/aws
[aws]
localhost

Role

  • route53コマンドはAWS CLIと違い、環境変数AWS_DEFAULT_PROFILEでセットしたプロファイルを読んでくれません。そのため、環境変数AWS_PROFILEにセットする必要があります。
roles/route53_export/tasks/main.yml
---
- name: Create directory
  file:
    path: "{{ inventory_dir | dirname }}/work/route53_export"
    state: directory
    mode: '0775'
    group: wheel
  delegate_to: localhost
  check_mode: no
  changed_when: no
  become: yes

- name: Get all hosted zones
  route53_facts:
    query: hosted_zone
  register: hosted_zones
  check_mode: no

- name: Get record sets in a given hosted zone
  shell: >-
    export AWS_PROFILE=`echo $AWS_DEFAULT_PROFILE` && \
    route53 get {{ item.Id | regex_replace('/hostedzone/','') }}
  check_mode: no
  changed_when: no
  register: record_sets
  with_items: "{{ hosted_zones.HostedZones }}"

- name: Save record sets
  template:
    src: save_record_sets.j2
    dest: >-
      {{ inventory_dir | dirname }}/work/route53_export/{{ item.item.Name | regex_replace('(^.*)\.$','\1') }}.txt
    mode: '0664'
    group: wheel
  delegate_to: localhost
  changed_when: no
  become: yes
  with_items: "{{ record_sets.results }}"
  when: not ansible_check_mode

template

roles/route53_export/templates/save_record_sets.j2
{{ item.stdout }}

site.yml

site.yml
---
- name: route53_export
  hosts: localhost
  connection: local
  roles:
    - role: route53_export

実行

Command
$ ansible-playbook -i hosts/aws -l localhost site.yml

出力イメージ

work/testdomain.com.txt
Name                                     Type  TTL                  Value(s)
testdomain.com.                          NS    172800               ns-174.awsdns-21.com.,ns-1037.awsdns-01.org.,ns-1745.awsdns-26.co.uk.,ns-613.awsdns-12.net.
testdomain.com.                          SOA   900                  ns-174.awsdns-21.com. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
cname.testdomain.com.                    CNAME 300                  www.example.com
text.testdomain.com.                     TXT   300                  "var"
www.testdomain.com.                      A     7200                 10.1.1.1

まとめ

出力イメージについては、route53コマンドの出力結果がそこそこ見やすいのでそのままファイルに出力しています。
必要に応じてJSONに整形すればYAML形式に変換するのも簡単です。

参考

AnsibleでAWSリソースを管理するシリーズ

続きを読む

AWS Greengrassのチュートリアルをやってみた

Greengrassのユーザガイドにあるチュートリアルをやってみました。
http://docs.aws.amazon.com/greengrass/latest/userguide/gg-deploy-simple-lambda.html

事前に用意するもの

  • AWSアカウント
  • Raspberry Pi3

作業内容

1. GreengrassグループとGreengrassコアを作成する

マネジメントコンソールにログイン後、「AWS IoT」-「Greengrass」-「今すぐ始める」を選択します。次に「Greengrassグループを設定する」画面になったら、「簡単な作成の使用」を選択します。

01_welcome.png

グループ名とコア名を指定後、「グループとコアの作成」をクリックします。

02_group.png

03_core.png

「コアデバイスへの接続」 の画面になったら、証明書、パブリックキー、プライベートキーとGreengrassコアソフトウェアをダウンロードします。

05_key.png

2. Raspberry Piを設定する

Raspberry Piへログインし、下記の設定をします。

  • ユーザー・グループの作成
  • カーネル4.9へアップデート
  • sqlite3のインストール
  • セキュリティ設定

$ sudo adduser --system ggc_user
$ sudo addgroup --system ggc_group

$ sudo apt-get install rpi-update
$ sudo rpi-update

$ sudo apt-get install sqlite3

$ sudo vi /etc/sysctl.d/98-rpi.conf

$ sudo reboot
/etc/sysctl.d/98-rpi.conf
・・・
fs.protected_hardlinks = 1
fs.protected_symlinks = 1

3. Raspberry PiにGreengrassソフトウェアパッケージを展開する

ダウンロードしたソフトウェアパッケージをscp等でRaspberry Piにコピー後、展開します。

$ sudo tar -zxf greengrass-linux-armv7l-1.0.0.tar.gz -C /

4. Raspberry Piに証明書をインストールする

シマンテックのサイトからルート証明書をダウンロードします。
http://docs.aws.amazon.com/ja_jp/greengrass/latest/userguide/install-core-certs.html

ダウンロードした証明書、プライベートキーとルート証明書をscp等でRaspberry Piに転送後、配置します。

$ sudo mv xxxxxxxxxx-certificate.pem.crt /greengrass/configuration/certs/cloud.pem.crt

$ sudo mv xxxxxxxxxx-private.pem.key /greengrass/configuration/certs/cloud.pem.key

$ sudo mv root-ca.pem /greengrass/configuration/certs/

5. Greengrassコアを設定する

「コア」-「詳細」を選択して、ARNを表示します。

06_core.png

07_core_detail.png

aws cliでIoTのエンドポイントを取得します。

$ aws iot describe-endpoint
{
    "endpointAddress": "xxxxxxxxxxxx.iot.us-east-1.amazonaws.com"
}

上記で取得した値を元に、config.jsonファイルを設定します。

  • ルート証明書
  • 証明書
  • プライベートキー
  • ARN
  • IoTエンドポイント
  • systemdの利用有無
/greengrass/configuration/config.json
{
    "coreThing": {
        "caPath": "root-ca.pem",
        "certPath": "cloud.pem.crt",
        "keyPath": "cloud.pem.key",
        "thingArn": "arn:aws:iot:us-east-1:yyyyyyyyyyyy:thing/MyFirstGroup_Core",
        "iotHost": "xxxxxxxxxxxx.iot.us-east-1.amazonaws.com",
        "ggHost": "greengrass.iot.us-east-1.amazonaws.com"
    },
    "runtime": {
        "cgroup": {
            "useSystemd": "yes"
        }
    }
}

6. Raspberry Pi上のGreengrassコアをAWS IoTに接続する

greengrassデーモンを起動します。

$ cd /greengrass
$ sudo ./greengrassd start

7. サンプルのLambdaを作成する

マネジメントコンソールで「Lambda」-「greengrass-hello-world」を選択し、Lambdaを作成します。
08_lambda.png

作成後、Lambdaに対し新しいバージョンを発行します。

12_lambda.png

8. 作成したLambdaをグループの定義に追加・設定する

「最初のLambdaを追加する」を選択します。

14_lambda_add.png

「既存のLambdaを使用」を選択し、Lambda名とバージョンを指定します。

15_lambda_add.png

16_lambda_add.png

17_lambda_add.png

Lambdaを追加したら、ライフサイクルを「存在時間が長く無制限に稼働する関数にする」に変更します。

18_lambda_modify.png

19_lambda_modify.png

9. subscriptionをグループの定義に追加する

「最初のサブスクリプションの追加」を選択します。

20_subscription.png

以下のように指定して、「完了」を選択します。

25_subscription.png

10. グループをデプロイする

「デプロイ」を選択します。

26_deploy.png

「自動検出」を選択します。

27_deploy.png

「権限の付与」を選択します。

28_deploy.png

「デプロイメントが正常に完了しました」のメッセージが表示されればデプロイ成功です。

30_deploy.png

動作確認

トピックのサブスクリプションを指定後、「トピックへのサブスクライブ」を選択します。

31_test.png

Greengrassコアからのメッセージが表示されればOKです。

32_test.png

雑感

Lambdaに続き、AWSは何か破壊的なサービスを出してきたように思えます。
Greengrassが今後どのように発展していくのか、とても楽しみです。

続きを読む

AWSからS3のセキュリティについて警告メールが来た時の対処方法

AWSからS3の警告メールが来たら

S3バケットのポリシーに関するTIPSです。
SORACOMの松井さんに教えていただいて大変助かったので情報共有として記事にします。

いきなりメールが来る

AWSからある日こんなメールが届きました。どうも特定の条件の人に一斉にメール送ってるようです。

Subject: Securing Amazon S3 Buckets [AWS Account: ******]

Hello,

We’re writing to remind you that one or more of your Amazon S3 bucket access control lists (ACLs) are currently configured to allow access from any user on the Internet. The list of buckets with this configuration is below.

By default, S3 bucket ACLs allow only the account owner to read contents from the bucket; however, these ACLs can be configured to permit world access. While there are reasons to configure buckets with world read access, including public websites or publicly downloadable content, recently, there have been public disclosures by third parties of S3 bucket contents that were inadvertently configured to allow world read access but were not intended to be publicly available.

We encourage you to promptly review your S3 buckets and their contents to ensure that you are not inadvertently making objects available to users that you don’t intend. Bucket ACLs can be reviewed in the AWS Management Console (http://console.aws.amazon.com ), or using the AWS CLI tools. ACLs permitting access to either “All Users” or “Any Authenticated AWS User” (which includes any AWS account) are effectively granting world access to the related content.

For more information on configuring your bucket ACLs, please visit: https://docs.aws.amazon.com/AmazonS3/latest/dev/S3_ACLs_UsingACLs.html

For additional assistance reviewing your bucket ACLs, please visit http://aws.amazon.com/support to create a case with AWS Developer Support.

Your list of buckets configured to allow access from anyone on the Internet are:

バケットの名前

要約すると「おめー、S3のバケットを世界中から見られるように設定してるけどまずいんじゃね?ちゃんと制限しろよ」ということのようです。

とはいえ

指摘されたバケットはいずれもstatic web hostingで使っているバケットです。その性質上、世界中から見えないとあまり意味がないです。
公式ドキュメントでも以下のように「該当バケット以下のすべてのオブジェクトに対するGetObjectメソッドを、全てのアクセスに対して許可しろ」と書いてあります。

http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/WebsiteAccessPermissionsReqd.html

これは困った・・。

そこに救いの手が

Facebookで相談したところ、元・中の人の松井さんが教えてくれました。

Bucket policy でアクセス元IPアドレスを 0.0.0.0/1 と 128.0.0.0/1 からのみ許可するというライフハック

これはどういうことかというと

  • 0.0.0.0/0を開放するとこれは全開放になっちゃうのでこれまでと変わらないから多分また警告が来る
  • ネットマスクを最小値の1として0.0.0.0/1とするとこれは0.0.0.1~127.255.255.255.254のレンジになる
  • 追加で128.0.0.0/1を許可すると、これが128.0.0.1~255.255.255.254になる
  • 結局、2つ合わせると0.0.0.0/0とするのとほぼ等価になる(厳密にいうとブロードキャストアドレスの分があるけど、そんなアドレスからのアクセスはないだろう)

ということになります。ネットマスクの計算については割愛します。

というわけで

以下のように書き換えたら無事終了。

{
    "Version": "2008-10-17",
    "Statement": [
        {
            "Sid": "PublicReadForGetBucketObjects",
            "Effect": "Allow",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:GetObject",
            "Resource": "バケットのARN/*",
            "Condition": {
                "IpAddress": {
                    "aws:SourceIp": [
                        "0.0.0.0/1",
                        "128.0.0.0/1"
                    ]
                }
            }
        }
    ]
}

将来的なことを考えるとv6アドレスも足すべきでしょうけどとりあえずはこれでよさそうです。

しかし、そもそもstatic web hostingがONになってる人は対象から外すとかしてくれればいいんですけどねぇ・・。

あと、これで本当に警告が来なくなるのかは未検証です(しばらく待ってみないとわからないので)のでご了承ください。
また届いたら記事をアップデートします。

続きを読む

AWS STSの勉強メモ

STSを利用すると、一時的なトークンを取得できる。
使用頻度が低い等でIAMアカウントを作成すること無く、IDフェデレーションで一時的にCredentialsを渡すようなことができる。
例えば、LDAP認証などのブローカーサーバーを用意し、そちらで認証に通ったユーザーに対して権限を割り当てる等。

ここでは下記の2つのSTSのAPIについて試してみた。

  • AssumeRole
  • GetFederationToken

AssumeRoleについて

既存のIAMユーザーの認証情報を⽤いて、IAM Roleのtemporary security credentialsを取得するためのアクション。

aws cliを使う例。下記の投稿がわかりやすかった。

AWS CLIでAssumeRoleを使う小さなサンプル

NOTE: IAMロールについて、信頼関係のJSONはPermissionのタブではなく、Trust relationshipsの方に登録する

assume-role APIを実行すると、新規にアクセスキー、シークレットアクセスキー、そしてセッショントークンが送られてくるので、これを環境変数にセットすることでその権限が使用できる。

$ export AWS_ACCESS_KEY_ID = XXXXXXXXXXXX
$ export AWS_SECRET_ACCESS_KEY= XXXXXXXXXXXX
$ export AWS_SESSION_TOKEN= XXXXXXXXXXXX

もし、マネジメントコンソールで利用する場合、コンソール上部の”Switch Role”メニューで設定すれば切り替えられる。

EC2_Management_Console.png

これをboto3でやるとこんな感じ。

import boto3
from boto3.session import Session

profile = 'assume-test-user'
session = Session(profile_name=profile)

sts = session.client('sts')
result = sts.assume_role(RoleArn='arn:aws:iam::XXXXXXXXXXXX:role/assume-role-test',
                         RoleSessionName='test')
credentials = result['Credentials']
ec2_resource = boto3.resource('ec2',
                              aws_access_key_id=credentials['AccessKeyId'],
                              aws_secret_access_key=credentials['SecretAccessKey'],
                              aws_session_token=credentials['SessionToken'])

for instance in ec2_resource.instances.all():
    print(instance)

有効期間ついては下記。(ドキュメントより)

一時的なセキュリティ認証情報の有効期間を指定する期間。最短は 15 分(900 秒)、最長(デフォルト)は 1 時間(3600 秒)です。一時的な認証情報を 1 時間が経過する前に無効にする場合にのみ、この値を渡す必要があります。

GetFederationTokenについて

認証を受けたFederatedユーザーのtemporary security credentialsを取得するためのアクション。

下記の投稿が分かりやすかった。

[JAWS-UG CLI] STS:#2 GetFederationToken

有効期間ついては下記。(ドキュメントより)

この API は、デフォルトの有効期限が大幅に長い(1 時間までではなく 36 時間まで)という点で AssumeRole とは異なります。より長い有効期限により、何度も新しい認証情報を取得する必要がなくなるので、AWS の呼び出しの回数を減らすことができます。

それぞれのAPIの違い

AssumeRoleやGetFederationTokenなどのAPIの使い分けはどうすればよいのだろうか。
下記を見ると、与える権限の範囲やクロスアカウント、MFAの利用で違いがある。

僕はスピンサーブが打てない:Temporary-Security-Credentials-のまとめ

また、Identity and Access Management (IAM)のp.58に概要説明があるので、それを参考に。

続きを読む

AnsibleでRoute53のレコード情報をいい感じにエクスポートする

はじめに

Route53でDNSを運用している方は多いと思いますが、ゾーンごとのレコード一覧とかサクっと調べたいことがあるかと思います。
マネコンだとエクスポートできないし、AWS CLIでやるにしてもjqなどで適切にパースしないと見づらいです。

今回は、Ansibleを利用してレコード一覧をYAML形式で見やすくファイル出力してみます。

前提

  • AWS関連のモジュール実行にはbotoが必要です。
  • credential情報は環境変数かaws configureでセットしてある必要があります。

sample

やること

取得したレコードセット情報を、ゾーンごとにworkディレクトリにYAML形式で保存する

ディレクトリ構成

ディレクトリ構成
site.yml
roles/
|--route53_export/
|  |--tasks/
|  |  |--main.yml
|  |--templates/
|  |  |--save_record_sets.j2
hosts/aws                  #inventory
work/                      #ゾーン単位でレコード情報が出力される
|--testdomain.com.txt        #出力されたレコード情報

inventory

AWSリソース関連モジュールはすべてlocalhostで実行するので、下記のようなインベントリファイルを用意します。

hosts/aws
[aws]
localhost

Role

roles/route53_export/tasks/main.yml
---
- name: Create directory
  file:
    path: "{{ inventory_dir | dirname }}/work/route53_export"
    state: directory
    mode: '0775'
    group: wheel
  delegate_to: localhost
  check_mode: no
  changed_when: no
  become: yes

- name: Get all hosted zones
  route53_facts:
    query: hosted_zone
  register: hosted_zones
  check_mode: no

- name: Get record sets in a given hosted zone
  route53_facts:
    profile: "{{ lookup('env', 'AWS_DEFAULT_PROFILE') }}"
    query: record_sets
    hosted_zone_id: "{{ item.Id | regex_replace('/hostedzone/','') }}"
  check_mode: no
  register: record_sets
  with_items: "{{ hosted_zones.HostedZones }}"

- name: Save record sets
  template:
    src: save_record_sets.j2
    dest: >-
      {{ inventory_dir | dirname }}/work/route53_export/{{ item.item.Name | regex_replace('(^.*).$','1') }}.txt
    mode: '0664'
    group: wheel
  delegate_to: localhost
  changed_when: no
  become: yes
  with_items: "{{ record_sets.results }}"
  when: not ansible_check_mode

template

roles/route53_export/templates/save_record_sets.j2
{{ item.ResourceRecordSets | to_nice_yaml(indent=2) }}

site.yml

site.yml
---
- name: route53_export
  hosts: localhost
  connection: local
  roles:
    - role: route53_export

実行

Command
$ ansible-playbook -i hosts/aws -l localhost site.yml

出力イメージ

work/testdomain.com.txt
- Name: cname.testdomain.com.
  ResourceRecords:
  - Value: www.example.com
  TTL: 300
  Type: CNAME
- AliasTarget:
    DNSName: testelb-xxxxxxxxx.ap-northeast-1.elb.amazonaws.com.
    EvaluateTargetHealth: false
    HostedZoneId: Z14GRHDCWA56QT
  Name: elb.testdomain.com.
  Type: A
- Name: text.testdomain.com.
  ResourceRecords:
  - Value: '"var"'
  TTL: 300
  Type: TXT
- Name: www.testdomain.com.
  ResourceRecords:
  - Value: 10.1.1.1
  TTL: 7200
  Type: A

まとめ

いかがでしょうか。結構見やすいと思います。
このファイルをgit管理するのもアリかと。

参考

AnsibleでAWSリソースを管理するシリーズ

続きを読む