のぴぴのメモ

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

AWS CLIでVPCエンドポイントを作成する時のポリシー(JSON)の指定方法

CLIVPCエンドポイントを作成するときに、エンドポイントポリシーのJSONをどうやって渡せばいいのかを悩んだので、そのメモ書きです。
JSONの文字列をシェルの変数に投入してあげるという解決策です。

実行例

AWS_PROFILE=default

POLICY='
{
    "Statement": [
        {
            "Action": "*",
            "Effect": "Allow",
            "Resource": "*",
            "Principal": "*"
        }
    ]
}'

aws --profile ${AWS_PROFILE} ec2 create-vpc-endpoint \
    --vpc-endpoint-type Interface \
    --vpc-id vpc-f1dcfc96 \
    --subnet-ids subnet-cde80585 subnet-0bf40020 subnet-742e712f \
    --security-group-ids sg-beca3fc2 \
    --service-name com.amazonaws.ap-northeast-1.sqs \
    --policy-document "${POLICY}";

解説

ミソは、エンドポイントのポリシーをシェル変数(ここでは、POLICY)に登録して、--policy-document の引数に "${POLICY}"と入れること。
引数に入れる"${POLICY}"は、必ずダブルクォーテーションで括ること。(bashのコマンド文字列解析時に、JSONの途中のスペースを引数の区切りと誤認するのを回避するため)

Linuxでファイル名がSJIS(CP932)のものをUTF-8のファイル名に一括変換する

convmvをインストール

sudo yum -y install convmv

使い方

cd  <変換したいファイルがあるディレクトリ>
convmv -r -f cp932 -t utf-8 --notest *
  • -r 再帰的に変換を実行
  • -f cp932 : 変換元のコード指定。cp932は、windowsSJISコード名称
  • -t utf-8: 変換先のコード指定。utf-8に変換する
  • --notest: 変換を実行する(デフォルトではテストだけのため)

JSONの文字列変換(JSON.dumps)をワンライナーのコマンド実行で実現する

コマンドでの作業で、pythonのコードを書くまででない場合の方法。
変数"POLICY"に設定したJSONを、pythonで文字列に変換して、変数"POLICY_ESCAPE"に格納する。

JSON→文字列にエンコード

やること

このJSONを、

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1565607714000",
            "Effect": "Allow",
            "Action": [
                "iam:List*",
                "iam:PassRole"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

これに変換します。

"{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Sid\": \"Stmt1565607714000\", \"Effect\": \"Allow\", \"Action\": [ \"iam:List*\", \"iam:PassRole\" ], \"Resource\": [ \"*\" ] } ] }\n"

コマンド

POLICY='{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1565607714000",
            "Effect": "Allow",
            "Action": [
                "iam:List*",
                "iam:PassRole"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}'
POLICY_ESCAPE=$(echo ${POLICY} | python -c 'import json; import sys; print(json.dumps(sys.stdin.read()))')

文字列→JSONにデコード

上記の逆です。

POLICY_ESCAPE="{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Sid\": \"Stmt1565607714000\", \"Effect\": \"Allow\", \"Action\": [ \"iam:List*\", \"iam:PassRole\" ], \"Resource\": [ \"*\" ] } ] }\n"

POLICY=$(echo ${POLICY_ESCAPE} | python -c 'import json; import sys; str=sys.stdin.read(); dic=json.loads(str.replace("\\n","")); print("{}".format(json.dumps(dic,indent=4)))' )

実行結果

$ echo $POLICY
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "iam:List*", "iam:PassRole" ], "Resource": [ "*" ], "Effect": "Allow", "Sid": "Stmt1565607714000" } ] }

蛇足

もっといい方法があるような気がしますが、わかったらアップデートします。

EC2の有償OSの課金コード("billingProducts")

"billingProducts"の確認方法

課金コード(billingProduct)をどうやって取得するのかという話ですが、そのものズバリの記載が下記にあり、下記コマンドで取得できます。

curl http://169.254.169.254/latest/meta-data/product-codes

RHEL8での確認

OSに課金が発生するRHEL8の場合、"billingProducts"に"bp-6fa54006" というのが見えます。

$ curl http://169.254.169.254/latest/dynamic/instance-identity/document/ 
{
  "accountId" : "270025184181",
  "availabilityZone" : "ap-northeast-1a",
  "kernelId" : null,
  "ramdiskId" : null,
  "pendingTime" : "2019-06-02T14:03:10Z",
  "architecture" : "x86_64",
  "privateIp" : "172.31.32.137",
  "version" : "2017-09-30",
  "region" : "ap-northeast-1",
  "devpayProductCodes" : null,
  "marketplaceProductCodes" : null,
  "imageId" : "ami-03c6a4362c5fb8c61",
  "billingProducts" : [ "bp-6fa54006" ],
  "instanceId" : "i-0abf28d14a86f3c6c",
  "instanceType" : "m5a.large"
}

Amazon Linux2

無料のAmazon Linux2の場合、"billingProducts"がnullですね。

$ curl http://169.254.169.254/latest/dynamic/instance-identity/document/ 
{
  "devpayProductCodes" : null,
  "marketplaceProductCodes" : null,
  "accountId" : "270025184181",
  "availabilityZone" : "ap-northeast-1a",
  "kernelId" : null,
  "ramdiskId" : null,
  "pendingTime" : "2019-06-02T14:03:47Z",
  "architecture" : "x86_64",
  "privateIp" : "172.31.37.11",
  "version" : "2017-09-30",
  "region" : "ap-northeast-1",
  "imageId" : "ami-0f9ae750e8274075b",
  "billingProducts" : null,
  "instanceId" : "i-0d1ca8d3b54832823",
  "instanceType" : "m5a.2xlarge"
}

調べるに至った経緯

AMI作成手順のドキュメントにこんな記載があるのですが、「 billingProduct コード」をどうやったら確認できるのか書いてなかったので調べたものです。

Red Hat Enterprise Linux (RHEL) や SUSE Linux Enterprise Server (SLES) などの一部の Linux ディストリビューションは、AMI に関連付けられた Amazon EC2 の billingProduct コードを使用して、パッケージの更新に関するサブスクリプションのステータスを確認します

docs.aws.amazon.com

AWS CodeCommitでgit認証にAWS CLIの認証情報ヘルパーを使用する

CodeCommitに作成したgitレポジトリに接続するための認証に、AWS CLIを利用する方法です。gitの認証ヘルパーに"aws codecommit credential-helper"を利用するのがポイントです。

1.手順: IAMユーザ作成とAWS CLIプロファイル設定

1.1 CodeCommit用のIAMユーザを作成

グループ"CodeCommitUserGrp"を作成し、AWSCodeCommitPowerUserポリシーを付与。
ユーザ"User01"を、"CodeCommitUserGrp"で作成する。

  • グループ設定
    • グループ名: CodeCommitUserGrp
    • アタッチするポリシ: AWSCodeCommitPowerUser
# 設定
aws iam create-group --group-name CodeCommitUserGrp
aws iam attach-group-policy --group-name CodeCommitUserGrp --policy-arn arn:aws:iam::aws:policy/AWSCodeCommitPowerUser
# 設定確認
aws iam get-group --group-name CodeCommitUserGrp
aws iam list-attached-group-policies --group-name CodeCommitUserGrp
  • ユーザ設定
    • ユーザ名: User01
    • 所属グループ: CodeCommitUserGrp
# 設定
aws iam create-user --user-name User01
aws iam add-user-to-group --user-name User01 --group-name CodeCommitUserGrp 
# 設定確認
aws iam get-user --user-name User01
aws iam list-groups-for-user --user-name User01

1.2 ユーザのアクセスキーとシークレットキーを生成

PCのAWS CLIに設定するアクセスキーとシークレットキーを生成します。AccessKeyIdとSecretAccessKeyを控えます。(漏洩しないよう注意!!)

aws iam create-access-key --user-name User01
{
    "AccessKey": {
        "UserName": "User01", 
        "Status": "Active", 
        "CreateDate": "2019-08-12T09:55:52Z", 
        "SecretAccessKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 
        "AccessKeyId": "YYYYYYYYYYYYYYYYYYYY"
    }
}

1.3 AWS CLIにプロファイルを作成

AWS CLIがセットアップされていない場合は、こちらの記事を参照してCLIをインストールして下さい。
AWS CLIをセットアップ後に、aws CLIにcodecommitというプロファイルを作成し、先ほど生成したアクセスキーとシークレットキーを登録します。

aws --profile codecommit configure
AWS Access Key ID [None]: YYYYYYYYYYYYYYYYYYYY
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: json

2.手順: gitの認証ヘルパー設定

gitコマンドがインストールされていない場合は、先にgitをインストールします。(インストール方法は記載しないので、別途検索して下さい)

2.1 gitの初期設定(ユーザ設定&pushモード設定)

(1)メールアドレス、(2)名前、(3)push時のモードを設定します。

git config --global user.email "xxxxxxx@gmail.com"
git config --global user.name "xxxxxxxx"
git config --global push.default simple

git config --global -l

2.2 gitの認証ヘルパーにAWSコマンドを設定

公式ドキュメントの情報を参考に、下記コマンドで認証ヘルパーとしてAWSコマンドを指定します。

git config --global credential.helper '!aws --profile codecommit codecommit credential-helper $@'
git config --global credential.UseHttpPath true

ただし、私の環境では上記コマンドで正しく反映されなかったため、"~/.gitconfig"のcredential句の部分を、下記の通り正しい内容に修正しました。(こちらの記事を参考にしました)

[credential]
   helper = "!aws --profile codecommit codecommit credential-helper $@"
   UseHttpPath = true

3.手順: CodeCommitのレポジトリからcloneのテスト

git clone <レポジトリhttpsのURL>

VPCのsubnetのCIDRの割り当てのメモ

VPCのCIDRを"10.1.0.0/16"とした時のCIDR例。
私は検証用途での利用でIPが枯渇するほどインスタンスを起動するわけではなので、IPアドレス範囲をキツキツには設計していない。

  • ポイント
    • Privateサブネットは少し大きめの19bitで切る。
    • Publicサブネットは、10.1.0.0/19の範囲をさらに24bitで切って利用する
    • AZ間は間違えないように離れた番号を利用するようにアドレスを割り当てる

サブネットのCIDR割り当て例

#大区分小区分項目CIDRIPアドレス範囲
1PublicAZ-1PublicSubnet110.1.0.0/2410.1.0.1-10.1.0.254
2未使用10.1.1.0/24

10.1.15.0/24
10.1.1.1-10.1.15.254
3AZ-2PublicSubnet210.1.16.0/2410.1.16.1-10.1.16.254
4未使用10.1.17.0/24

10.1.31.0/24
10.1.0.1-10.1.7.254
5PrivateAZ-1PrivateSubnet110.1.32.0/1910.1.32.1-10.1.63.254
6未使用10.1.64.0/1910.1.64.1-10.1.95.254
7未使用10.1.96.0/1910.1.96.1-10.1.127.254
8AZ-2PrivateSubnet210.1.128.0/1910.1.128.1-10.1.159.254
9未使用10.1.160.0/1910.1.160.1-10.1.191.254
10未使用10.1.192.0/1910.1.192.1-10.1.223.254
11未使用未使用未使用10.1.224.0/1910.1.224.1-10.1.255.254

参考

"10.1.0.0/16"の範囲を19bitで分割した時のCIDR一覧

CIDRIP範囲IPアドレス(2進数)
Subnet11111111 11111111 11100000 00000000
10.1.0.0/1910.1.0.1 - 10.1.31.25400001010 00000001 00000000 00000000
10.1.32.0/1910.1.32.1 - 10.1.63.25400001010 00000001 00100000 00000000
10.1.64.0/1910.1.64.1 - 10.1.95.25400001010 00000001 01000000 00000000
10.1.96.0/1910.1.96.0 - 10.1.127.25500001010 00000001 01100000 00000000
10.1.128.0/1910.1.128.0 - 10.1.159.25500001010 00000001 10000000 00000000
10.1.160.0/1910.1.160.0 - 10.1.191.25500001010 00000001 10100000 00000000
10.1.192.0/1910.1.192.0 - 10.1.223.25500001010 00000001 11000000 00000000
10.1.224.0/1910.1.224.0 - 10.1.255.25500001010 00000001 11100000 00000000

AWS CloudFormation 起動テンプレート+CloudFormationヘルパースクリプトを使ってFowardProxyインスタンスを複数作成する

やりたいこと

CloudFormationでsquidのFowardProxyインスタンスをCloudFormationヘルパースクリプトを利用してインスタンス起動時にセットアップするようにしています。このインスタンスをAutoscalingは使用せず複数個作成したいと思い、EC2の起動テンプレートを組み合わせることで、効率良く起動するようにしています。

構成

f:id:nopipi:20190427234531p:plain
CloudFormation:起動テンプレートとCfnヘルパーの組み合わせで複数のProxyを作成する

ポイント

  • Cfnヘルパースクリプト用の定義を、EC2起動テンプレートのリソースのMetadataとして定義
  • Cfnヘルパースクリプトのオプション
    • 起動テンプレートのUserDataでcfn_initコマンドでcfnヘルパーを実行するようにする
    • その時Cloudformationの指定リソースを、”--resource ProxyLunchTemplate ”と起動テンプレートのリソースを指定する

CloudFormationテンプレート例

AWSTemplateFormatVersion: '2010-09-09'
Description: Outbound from the On-premises Environment
#----------------------------------------------
Parameters:
  Environment:
    Type: String
    Default: Dev
    AllowedPattern: "[a-zA-Z0-9=-@+?[?]?<?>._]*"
    ConstraintDescription: Can contain only ASCII characters.
  #------------------
  VpcId:
    Type: AWS::EC2::VPC::Id
  Subnet1Id:
    Type: AWS::EC2::Subnet::Id
    Description: Specify 1st Subnet to run Proxy instances.
  Subnet2Id:
    Type: AWS::EC2::Subnet::Id
    Description: Specify 2nd Subnet to run Proxy instances.
  #------------------
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: (required) Name of an existing EC2 key pair
  ProxyInstanceType: 
    Type: String 
    Default: m5.large 
    ConstraintDescription : Must be a valid EC2 instance type
    AllowedValues: 
      - t2.micro 
      - t2.small 
      - t2.medium 
      - t2.large 
      - t2.xlarge 
      - t2.2xlarge 
      - m5.large 
      - m5.xlarge 
      - m5.2xlarge 
      - m5.4xlarge 
      - m5.12xlarge 
      - m5.24xlarge 
  ProxyAmiId:
    Type: AWS::EC2::Image::Id
    Description: (required) AMI ID
    Default: ami-0f9ae750e8274075b  #for Tokyo-region
    #Default: ami-0b419c3a4b01d1859  #for singapore-region
  ProxyAutoRecoveryMinutes:
    Type: Number
    Description: Auto Recovery Time(Minutes)
    Default: 1
    MinValue: 1
    MaxValue: 120
  #------------------
Metadata: 
  AWS::CloudFormation::Interface: 
    ParameterGroups: 
      -
        Label:
          default: "General"
        Parameters:
          - Environment
      -
        Label:
          default: "Network"
        Parameters:
          - VpcId
          - Subnet1Id
          - Subnet2Id
      -
        Label:
          default: "Forward Proxy"
        Parameters:
          - KeyName
          - ProxyInstanceType
          - ProxyAmiId
          - ProxyAutoRecoveryMinutes
#----------------------------------------------
Resources:
  ProxySG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${Environment}-ProxySecurityGroup
      GroupDescription: Allow Proxy inbound access
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        -
          IpProtocol: tcp
          FromPort: 3128
          ToPort: 3128
          CidrIp: 0.0.0.0/0
        -
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags: 
        - Key: Name
          Value: !Sub ${Environment}-ProxySecurityGroup
  #------------------
  ProxyLunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: FowardProxyLunchTemplate
      LaunchTemplateData:
        ImageId: !Ref ProxyAmiId
        InstanceType: !Ref ProxyInstanceType
        KeyName: !Ref KeyName
        SecurityGroupIds:
          - !Ref ProxySG
        Monitoring:
          Enabled: true
        TagSpecifications:
          -
            ResourceType: instance
            Tags:
              - Key: Name
                Value: !Sub ${Environment}-ProxyInstance
        UserData:
          Fn::Base64: !Sub | 
            #!/bin/bash -xe
            yum update -y 
            yum install -y aws-cfn-bootstrap cloud-init aws-cli 
            /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ProxyLunchTemplate --region ${AWS::Region} --configsets proxy_setup
            /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ProxyLunchTemplate --region ${AWS::Region}
    Metadata:
      AWS::CloudFormation::Init: 
        configSets: 
          proxy_setup:
            - install_squid 
            - setup_squid 
        install_squid: 
          packages: 
            yum: 
              squid: [] 
        setup_squid: 
          files: 
            "/etc/squid/squid.conf": 
              content: !Sub | 
                # define ip address 
                acl localnet src 127.0.0.0/8 
                acl localnet src ::1/128 
 
                acl SSL_ports port 443 
                acl Safe_ports port 443
                acl CONNECT method CONNECT 
                http_access deny !Safe_ports 
 
                # Deny CONNECT to other than secure SSL ports 
                http_access deny CONNECT !SSL_ports 
 
                # Only allow cachemgr access from localhost 
                http_access allow localhost manager 
                http_access deny manager 
 
                # from where browsing should be allowed 
                http_access allow localnet 
 
                # include url white list 
                acl whitelist dstdomain "/etc/squid/whitelist" 
                http_access allow whitelist 
 
                # And finally deny all other access to this proxy 
                http_access deny all 
                #------------------------------------------ 
                http_port 3128

                # Leave coredumps in the first cache dir
                coredump_dir /var/spool/squid
 
                # anonymouse host name
                visible_hostname unknown
              mode: '000640'
              owner: root
              group: squid
            "/etc/squid/whitelist":
              content: !Sub |
                .google.co.jp
                github.com
                gist.github.com
              mode: '000640'
              owner: root
              group: squid
          services:
            sysvinit:
              squid:
                enabled: true
                ensureRunning: true
                files:
                  - "/etc/squid/squid.conf"
                  - "/etc/squid/whitelist.conf"
  #------------------
  ProxyInstance1: 
    Type: AWS::EC2::Instance 
    Properties:
      LaunchTemplate:
        LaunchTemplateName: FowardProxyLunchTemplate
        Version: !GetAtt ProxyLunchTemplate.LatestVersionNumber
      SubnetId: !Ref Subnet1Id
      SourceDestCheck: false
  ProxyInstance2: 
    Type: AWS::EC2::Instance 
    Properties:
      LaunchTemplate:
        LaunchTemplateName: FowardProxyLunchTemplate
        Version: !GetAtt ProxyLunchTemplate.LatestVersionNumber
      SubnetId: !Ref Subnet2Id
      SourceDestCheck: false
  #------------------
Outputs:
  ProxyInstance1PrivateIp:
    Description: 'Foward Proxy #1 Private IP'
    Value: !GetAtt ProxyInstance1.PrivateIp
    Export:
      Name: !Sub "${AWS::StackName}-ProxyInstance1PrivateIp"
  ProxyInstance2PrivateIp:
    Description: 'Foward Proxy #2 Private IP'
    Value: !GetAtt ProxyInstance2.PrivateIp
    Export:
      Name: !Sub "${AWS::StackName}-ProxyInstance2PrivateIp"

(AWS) UserDataとCloudFormationヘルパースクリプトの定義情報取得や実行契機の違い

定義箇所とインスタンス内からの定義情報取得方法の違い

UserDataはEC2インスタンスの機能で、CloudFormationヘルパースクリプトはCloudFormationの機能です。
UserDataはインスタンスメタデータ*1から情報を取得するため、実質VPC設定やIAMロールを考慮する必要がありません。一方CloudFormationヘルパースクリプトは、CloudFormationのパブリックにあるエンドポイントから取得するためInternet GatewayやCloudFormationのVPC Endpointを用意してパブリックのエンドポイントにアクセスできるようにする必要があります。

機能 機能を提供しているサービス 定義 インスタンスからの取得方法 通信要件
UserData EC2 EC2インスタンス起動オプション インスタンスメタデータから取得 不要
Cfnヘルパー CloudFormation CloudFormationのテンプレート(ResourceのMetadata定義) CloudFormationのエンドポイントから取得 IGW経由またはVPCEndpoint経由でCloudFormationのパブリックエンドポイントにアクセス可能なこと

絵に描くとこんな感じでしょうか。(逆に判りずらいかも)

f:id:nopipi:20190427235711p:plain

実行モジュールと実行契機の違い

AWSが提供するAMIを利用した場合UserDataは、cloud-initというツールの中で実行されておりOS起動時に自動実行されます。一方CloudFormationヘルパースクリプトは自動実行されないため明示的に実行する必要があり、通常はUserDataにコマンドを記載して、UserDataから実行させます。

機能 パッケージ 起動契機 インスタンス無いでの設定ファイル
UserData cloud-init*2 OS起動時に自動実行(systemdからキックされる) /etc/cloud/配下
Cfnヘルパー aws-cfn-bootstrap*3 明示的に実行(UserDataに実行コマンドを埋め込む) なし(CfnのスタックのMetadataに定義)

VSCodeでAWS CloudFormation(YAML)を作成する環境を整備する

VSCodeAWS CloudFormationをYAMLで作成する環境の設定手順です。

やること

  1. VSCodeのセットアップ
  2. YAMLの構文チェック用にエクステンション"YAML Language Support by Red Hat"をVSCodeに追加
  3. CloudFormationの構文チェック用のカスタム設定をVSCode設定に追加

VSCodeのセットアップ

MacへのVSCodeインストール(とgit連携)はこちらを参照
nopipi.hatenablog.com

YAMLエクステンションの追加

VSCoderedhatYAMLエクステンションを追加します。ここではCUIGUIの両方の手順を記載します。

CUIでインストールする場合

下記コマンドでYAMLエクステンションを追加します。

code --install-extension redhat.vscode-yaml

GUIでインストールする場合

  1. 左のバーから"Extentions"をクリック
  2. 検索ボックスでYAMLでエクステンションを検索し、"YAML Language Support by Red Hat"を選択
  3. "Install"でエクステンションを追加する

(1)左バーからExtentionボタンをクリックし、(2)キーワードYAMLで検索、(3)redhat YAMLをインストール
GUI操作でのYAMLエクステンションインストール

CloudFormationの構文チェック用のカスタム設定追加

下記設置を追加します

  1. CloudFormation仕様の追加
  2. CloudFormationのカスタムタグ定義の追加

設定(1) CloudFormation仕様の追加

VSCodeの設定ファイル(Macの場合: $HOME/Library/Application Support/Code/User/settings.json)*1に、CloudFormationのリソース仕様を追加します。この設定でをすると、拡張子がcf.yamlのファイル、cfn/、cloudformation配下のYAMLファイルがCloudFormation用のYAMLとして指定したソース仕様にしたがってチェックされます。

  1. コマンドパレット(⇧⌘P)を開いて >settings (JSON) と打ち込みsettings.jsonを開く。
  2. 下記定義を追加する
    "yaml.schemas": {
        "https://d33vqc0rt9ld30.cloudfront.net/latest/gzip/CloudFormationResourceSpecification.json": [
            "*.cf.yaml",
            "*.cf.yml",
            "cfn/*.yaml",
            "cfn/*.yml",
            "cloudformation/*.yaml",
            "cloudformation/*.yml"
        ]
    },

設定(2) CloudFormationのカスタムタグ定義の追加

デフォルトのままだと!Sub, !RefなどのCloudFormation固有のスキーマYAMLの構文エラーとなるため、これら固有スキーマを追加します。内容はこちらのIssueAndyJPhillipsさんのコメント内容を流用しています。

    "yaml.customTags": [
        "!And sequence",
        "!Equals sequence",
        "!If sequence",
        "!Not sequence",
        "!Or sequence",
        "!Base64",
        "!Cidr sequence",
        "!FindInMap sequence",
        "!GetAtt",
        "!GetAZs",
        "!ImportValue",
        "!Join sequence",
        "!Select sequence",
        "!Split sequence",
        "!Sub",
        "!Ref"
    ]

(参考)setting.json全体の内容

設定(1)&(2)をすると、既存の設定に、下記設定が追加された形になります。

{
    <既存の設定>
    "yaml.schemas": {
        "https://d33vqc0rt9ld30.cloudfront.net/latest/gzip/CloudFormationResourceSpecification.json": [
            "*.cf.yaml",
            "*.cf.yml",
            "cfn/*.yaml",
            "cfn/*.yml",
            "cloudformation/*.yaml",
            "cloudformation/*.yml"
        ]
    },
    "yaml.customTags": [
        "!And sequence",
        "!Equals sequence",
        "!If sequence",
        "!Not sequence",
        "!Or sequence",
        "!Base64",
        "!Cidr sequence",
        "!FindInMap sequence",
        "!GetAtt",
        "!GetAZs",
        "!ImportValue",
        "!Join sequence",
        "!Select sequence",
        "!Split sequence",
        "!Sub",
        "!Ref"
    ]
}

mac版PowerPoint消えなくなった自動回復ファイルを手動削除する手順

Mac版のPowerPointを利用していて、PowerPointを起動するたびに古い自動回復ファイルが開いて対処するのが面倒でした。なんとか古い自動回復ファイルを消せないかなと調べた内容を備忘録で残します。

手順

MacPowerPointの自動回復ファイルの保存先フォルダパス

~/Library/Containers/com.microsoft.Word/Data/Library/Preferences/AutoRecovery/

削除手順

  • (1)PowerPointを停止して
  • (2)コンソールを開いて下記コマンドで自動回復ファイルを削除する
cd ~/Library/Containers/com.microsoft.Word/Data/Library/Preferences/AutoRecovery/
rm -i *

Amazon RDSのIOPS&容量変更をCLIで実行するサンプル

RDS プロビジョンド IOPSで、IOPSを変更して、DBがAvailableになるまでWaitするシェル(+AWS CLI)のサンプルです。
AWS CLIで”aws rds modify-db-instance”で変更をします。
RDS&プロビジョンド IOPSで、IOPS設定によるBLOBファイルインサートのパフォーマンスの影響を確認するための検証ツール作成に利用したものです。

コマンド

DBIdentifier="dbname"
RDS_SIZE=1024  #1TB
RDS_IOPS=4096

#Modify RDS storage(size and iops)
echo "$(date '+%Y/%m/%d %H:%M:%S'): Modify RDS storage"
aws rds modify-db-instance \
        --db-instance-identifier ${DBIdentifier} \
        --allocated-storage ${RDS_SIZE} \
        --iops ${RDS_IOPS} \
        --apply-immediately;

# Wait until the DBInstanceStatus is "Available"
sleep 30
while true
do
     STAT=$(aws --output text rds describe-db-instances \
          --db-instance-identifier ${DBIdentifier} \
          --query 'DBInstances[].DBInstanceStatus')
     if [ "A${STAT}" == "Aavailable" ]; then
          break
     else
          echo "$(date '+%Y/%m/%d %H:%M:%S'): DBInstanceStatus= ${STAT}"
          sleep 15
     fi
     done
echo "$(date '+%Y/%m/%d %H:%M:%S'): Done to modifying RDS"

注意事項

  • ストレージの設定変更後、6時間はストレージの再変更ができません。実行すると以下のような感じのエラーが出ます。
An error occurred (InvalidParameterCombination) when calling the ModifyDBInstance operation: You can't currently modify the storage of this DB instance. Try again after approximately 3 hours.

CloudTrailの「組織の証跡」でOrganizationsメンバアカウントに一括CloudTrailを設定する手順 (ロギング専用アカウント+KMS暗号化)

はじめに

AWS Oraganizationsを利用したマルチアカウント構成で、Oraganizationsの配下のAWSアカウントに共通のCloudTrail設定を行うことができる「組織の証跡」が CloudTrailにあります。ドキュメントは以下になりますが、logging専用のAWSアカウントのS3バケットを利用してかつKMSによる暗号化と組み合わせたやり方の情報がなかったので試行錯誤の結果をまとめました。
docs.aws.amazon.com

構成の説明

f:id:nopipi:20190308003130p:plain
組織の証跡の設定概要

  • 設定のポイント
    • CloudTrailのログは、ロギングアカウントのS3バケットに保存します。
    • またS3保管時にKMSによる暗号化を行います。
    • KMSの鍵はロギングアカウントで管理します。
  • アカウントとOrganizations構成
    • マスターアカウント:111111111111
    • リリースアカウント:222222222222
    • ロギングアカウント:333333333333
    • OrganizationsのID: o-yyyyyyyyyy
  • S3バケットとKMSか
  • 設定時の権限
    • 今回は、AdministratorAccessポリシーが付与されている環境で作業しています

設定手順

ロギングアカウント(S3とKMSの作成)

S3バケットの準備

S3バケットを作成します。CloudTrail設定で最も重要なバケットポリシーには、以下の設定を行います。

  • 1つめ: CloudTrailサービスからのバケットACL変更を許可する
  • 2つめ:マスターアカウントの証跡書き込みを許可する
  • 3つめ: 「組織の証跡」を許可する
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AWSCloudTrailAclCheck20150319",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": "arn:aws:s3:::trailbucket"
        },
        {
            "Sid": "AWSCloudTrailWrite20150319",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::trailbucket/AWSLogs/111111111111/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        },
        {
            "Sid": "AWSCloudTrailWrite20150319",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::trailbucket/AWSLogs/o-yyyyyyyyyy/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control"
                }
            }
        }
    ]
}

ドキュメントでは設定は以下に説明があります。
docs.aws.amazon.com

KMSの準備

CloudTrailがS3にPUTするときの暗号化に利用するKMSのカスタマーマスターキー(CMK) を作成します。
カスタマーポリシーは、今回はCloudTrailの書き込みに必要な最低限のポリシーを付与しています。

{
    "Version": "2012-10-17",
    "Id": "Key policy created by CloudTrail",
    "Statement": [
        {
            "Sid": "Enable IAM User Permissions",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "arn:aws:sts::333333333333:assumed-role/OrganizationAccountAccessRole/admin",
                    "arn:aws:iam::333333333333:root"
                ]
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "Allow CloudTrail to encrypt logs",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "kms:GenerateDataKey*",
            "Resource": "*",
            "Condition": {
                "StringLike": {
                    "kms:EncryptionContext:aws:cloudtrail:arn": "arn:aws:cloudtrail:*:111111111111:trail/*"
                }
            }
        },
        {
            "Sid": "Allow CloudTrail to describe key",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "kms:DescribeKey",
            "Resource": "*"
        }
    ]
}

マスターアカウント(CloudTrailの設定)

マスターアカウントのCloudTrailに移動し、「監査情報」から「証跡の作成」を実行します。

  • ポイント
    • 「組織に証跡を適用」を「はい」とすると、「組織の証跡」が作成されます
    • 「証跡情報を全てのリージョンに適用」を「はい」にして全リージョンに設定を適用します

f:id:nopipi:20190308010258p:plain:w550

  • 出力先S3バケット設定
    • バケット名:「新しいバケットを作成する」を「いいえ」にし、バケット名を入力する
    • KMS暗号化
      • 「SSE-KMS を使用してログファイルを暗号化」を「はい」にし、
      • 「新規の KMS キーの作成」を「いいえ」にして、
      • ロギングアカウントで作成したCMKのARNを入力する

f:id:nopipi:20190308011020p:plain:w550

  • メモ
    • 作成した証跡はメンバーアカウントでは削除できません
    • マスターアカウントで証跡を削除するとメンバーアカウントの証跡も削除されます
    • 証跡作成時に、S3バケットポリシーの権限が不足していてもマネコン上では「KMSの権限不足」と表示されるようです(私はそれでハマりました)

完了

作成に成功してしばらく経つと、各メンバーアカウント状に同じ設定のCloudTrail設定が作成されます。
今回の例の場合、指定したS3バケットには以下のような構成でCloudTrailのログが出力されます。

trailbucket
└ AWSLogs
  ├ 111111111111
  │ ├ CloudTrail-Digest
  │ └ CloudTrail
  └ o-yyyyyyyyyy
    ├ 111111111111
    │ ├ CloudTrail-Digest
    │ └ CloudTrail
    ├ 222222222222
    │ ├ CloudTrail-Digest
    │ └ CloudTrail
    └ 333333333333
      ├ CloudTrail-Digest
      └ CloudTrail

Lambdaの実行ロールのクレデンシャルを確認してみた

はじめに

Lambdaの関数からAWS APIを実行する場合、Lambdaに付与した実行ロール(Execution role)の権限が利用されます。
docs.aws.amazon.com
というところまではすぐにわかったのですが、この実行ロールのクレデンシャルをLambda上でどう取得しているかがパッとわからなかったので、実機で確認しました。確認はPythonで実施しています。

結論

結論を述べると、Lambdaを実行する環境の「環境変数」から実行ロールのクレデンシャルを取得することができます。

Lambda検証コード(python)

import os
import json
import boto3

def lambda_handler(event, context):
    
    print('AWS_ACCESS_KEY_ID={}'.format(os.environ['AWS_ACCESS_KEY_ID']))
    print('AWS_SECRET_ACCESS_KEY={}'.format(os.environ['AWS_SECRET_ACCESS_KEY']))
    print('AWS_SESSION_TOKEN={}'.format(os.environ['AWS_SESSION_TOKEN']))
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

検証コードの実行結果

START RequestId: 6b84d91a-2cb7-49ec-83d1-0844247f2188 Version: $LATEST
AWS_ACCESS_KEY_ID=ASIAT5XxxxxxxxxxxS4G
AWS_SECRET_ACCESS_KEY=UJ7JT9M7kxxxxxxxxxxxxxxxxxRbujmy7DwdyrK+
AWS_SESSION_TOKEN=FQoGZXIvYXdzEEcaFlCTJtylI<中略>IBTtPdOoott/04wU=
END RequestId: 1b54a3fa-4793-XXXX-XXXX-e6a1f102c0ef
REPORT RequestId: 1b54a3fa-4793-XXXX-XXXX-e6a1f102c0ef	Duration: 124.07 ms	Billed Duration: 200 ms 	Memory Size: 128 MB	Max Memory Used: 76 MB

クレデンシャルはマスクしています。
ドキュメントを確認すると、Lambdaの環境変数一覧にクレデンシャルがありました。
docs.aws.amazon.com

調査履歴

Lambdaの実行ロールを利用したAWS API実行

Lambda上でAWS APIを実行する場合は通常、EC2やオンプレ環境と同様にAWS SDKを利用してAPIを実行します。例えばS3のバケット一覧取得をLambda上でpythonで実行する場合は、以下のようなコードを書きます。

import json
import boto3

def lambda_handler(event, context):
    
    s3 = boto3.resource('s3')
    for bucket in s3.buckets.all():
        print('{}'.format(bucket.name))
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

このコード例を見ると、Lambda固有の書き方をする部分もありますが、AWS APIを実行するためのセッション取得の部分は” s3 = boto3.resource('s3')”とオンプレやEC2から実行する場合と変わらないです。またAWS SDKもLambda専用のSDKという記載はドキュメントには(私が調べた限り)ありませんでした。

ということから、セッション取得のためのクレデンシャル情報の取得はオンプレやEC2インスタンスと同様の挙動で取得しているのではないかということが考えられます。

AWS SDK(boto3)におけるクレデンシャル情報の取得

次にAWS SDKがどのようにクレデンシャル情報を取得しているかということを、ここでおさらいします。
boto3のクレデンシャルの取得手順は、公式ドキュメントでは下記に記載があります。

こちらの内容によると、boto3でのクレデンシャル取得は、以下の優先順で取得するとあります。

  1. boto.client() 実行時に、パラメータとしてクレデンシャル情報を明示的に引き渡す
  2. セッションオブジェクト作成時に、パラメータとしてクレデンシャル情報を明示的に引き渡す
  3. 環境変数
  4. AWS config file (~/.aws/config)
  5. Assume Role provider
  6. Boto2のconfigファイル(/etc/boto.cfg and ~/.boto)
  7. EC2インスタンスロール(EC2メタデータ: http://169.254.169.254/latest/meta-data/iam/security-credentials/ロール名称)

クレデンシャル情報取得方法の絞り込み

上記のboto3のクレデンシャル情報取得方法の優先順位とLambdaのサンプルコードからLambdaの実行ロールのクレデンシャル取得で可能性のない方式を除外します。

  • boto.client()実行時にコード上でクレデンシャル情報を明示的にしていない・・・該当しない(コードでクレデンシャルの指定なし)
  • セッションオブジェクト作成時にクレデンシャル情報を指定する ・・・・・・該当しない(セッション取得の関数は利用していない)
  • Assume Role provid・・・該当しない(コード上に該当する箇所がない。また後述するがconfigに該当箇所がない)

絞り込んで残った候補は以下の4つです。

  1. 環境変数
  2. AWS config file (~/.aws/config)
  3. Boto2のconfigファイル(/etc/boto.cfg and ~/.boto)
  4. EC2インスタンスロール(EC2メタデータ: http://169.254.169.254/latest/meta-data/iam/security-credentials/ロール名称)

configtと boto.cfgの確認 > なし

雑なコードですが下記コードでconfig, boto.cfg, .botoファイルの有無を確認します。(file=のファイル名にそれぞれのファイルのパスを設定して実行)
結果として、3つのファイルとも存在していないことが確認できました。

import json
import boto3

def lambda_handler(event, context):

    file='~/.aws/config'
    fp = open(file, "r")
    for line in fp:
        print('{}'.format(line))
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

EC2インスタンスロールの確認

EC2インスタンスロールと同様にインスタンスメタデータ(http://169.254.169.254/latest/meta-data)から取得できるか確認します。

検証コード

この検証コードではLambdaが実行されているインスタンスの、インスタンスメターデータの取得を試みています。

import urllib.request
import json
import boto3

def lambda_handler(event, context):
    
    url = 'http://169.254.169.254/latest/meta-data/'
    
    try:
        req = urllib.request.Request(url)
        with urllib.request.urlopen(req) as res:
            print(res.read())
    except urllib.error.URLError as e:
        print(e.reason)
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }
結果 > 取得失敗

実行の結果、”Connection refused”とエラー応答があり、メタデータである'http://169.254.169.254’からクレデンシャル取得を行うことは出来ませんでした。

START RequestId: df6064d2-ee6e-4c73-9dd5-d99f44de5ea3 Version: $LATEST
[Errno 111] Connection refused
END RequestId: df6064d2-ee6e-4c73-9dd5-d99f44de5ea3
REPORT RequestId: df6064d2-ee6e-4c73-9dd5-d99f44de5ea3  Duration: 1057.08 ms	Billed Duration: 1100 ms 	Memory Size: 128 MB	Max Memory Used: 77 MB	

環境変数の確認

下記環境を準備し、Lambdaの実行環境の環境変数を確認します。

import os
import json
import boto3

def lambda_handler(event, context):
    for key in os.environ:
        print('{:30s}= {}'.format(key, os.environ[key]))
    
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

確認すると、Lambdaの実行環境内にAWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY、下記のクレデンシャルコードが存在していることがわかりました。

Amazon SNSからプッシュ通知をPublishする時のメモ

モバイルプッシュ関連のblog記事

マネージメントコンソールからJSON形式でメッセージを送る例

{
"APNS_SANDBOX":"{\"aps\": {\"alert\": {\"title\":\"テスト\",\"body\":\"これはテストメッセージです\"}, \"sound\": \"default\", \"badge\":1}}"
}

こちらを参考
qiita.com

Python+SDK(boto3)でPublishする例

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import json
import boto3

PLATFORM   = 'APNS_SANDBOX'
TARGET_ARN = u'arn:aws:sns:ap-northeast-1:270025184181:endpoint/APNS_SANDBOX/SNSTestAPNs/b93b1ab2-4be5-3db1-bfd6-af6d5adb62c7'


def main():

    dict = {
             'aps': {
               'alert': {
                 'title': 'テスト',
                 'body':  'これはテストメッセージです'
               },
               'sound': 'default',
               'badge': 1
               }
           }
    message = {PLATFORM: json.dumps(dict)}
    messageJSON = json.dumps(message)

    # Get session
    client = boto3.client('sns')

    request = {
        'TargetArn': TARGET_ARN,
        'Message': messageJSON,
        'MessageStructure': 'json'
    }
    response = client.publish(**request)

if __name__ == "__main__":
    sys.exit(main())

Amazon SNSとiOSでモバイルプッシュを試してみる

検証環境のモバイルプッシュ通知の流れ

Appleのプッシュ通知サービス

Apple製品のプッシュ通知は、Apple Push Notification Service(APNs)と呼ばるAppleが提供するプッシュ通知サービスを利用して各デバイスにプッシュ通知を送ります。*1

プッシュ通知をする時は、Provider(要はサーバ)からAPNsに要求を送り、要求を受けたAPNsが指定されたデバイスにプッシュ通知を送付します。ProviderからAPNsへの要求では、通知先のデバイスの情報(デバイストークンという、デバイス+アプリの組み合わせで一意に生成されるトークン)とプッシュ通知の内容を送ります。

f:id:nopipi:20190216233223p:plain:w400

Amazon SNSモバイルプッシュとは

Amazon Simple Notification Service(SNS)の機能の一つで、様々な種類のモバイルデバイスのアプリケーションにプッシュ通知メッセージを送付するための抽象化されたサービスです。*2

先ほどのAppleのプッシュ通知の概要図のProvider部分を担うサービスになります。

f:id:nopipi:20190216234056p:plain:w400

プッシュ通知の流れ

プッシュ通知は大きく、(1)デバイストークンの取得と、(2)プッシュ通知の実行、に区分されます。ここではこの記事で作成する検証環境でのプッシュ通知の流れを説明します。

f:id:nopipi:20190206042218p:plain:w500

  1. バイストークンの取得(下図の緑吹き出し)
    1. iOSで検証用アプリケーションでAPNsからデバイストークンを取得
      1. バイストークンは、デバイスとアプリの組み合わせで一意に生成されるトークンです
      2. バイストークンはAppleのドキュメントでは、アプリ起動毎に取得することが推奨されてます*3
    2. 取得したデバイストークンをSNSに登録
  2. プッシュ通知(下図のオレンジ吹き出し)
    1. アプリケーションからSNSに通知の実行を指示(publish)
    2. SNSは、APNsにプッシュ要求を送付
    3. 要求を受けたAPNsは、指定されたデバイスにプッシュを通知

開発環境

この記事でのアプリケーション開発環境は以下の通りです

  • 開発言語: Swift(Swift4.2)
  • Xcode: Version 10.1 (10B61)
  • OS: macOS Mojave 10.14.3

クライアントアプリケーションの開発と実行

APNs設定

クライアントアプリケーションの開発前に、APNsへの設定が必要になります(APNsへの登録には、Apple Developer Programへの登録(有償)が必要です)。下記の記事を参考にAPNsの設定を行い、開発用プロビショニングプロファイルと、NotificationsのSSL証明書作成と登録、を準備してください。
nopipi.hatenablog.com

簡易クライアントアプリ作成

ここでは、プッシュ通知に必要となるDeviceTokenをAPNsから取得するのみの簡易的なアプリを作成します。

取得したDeviceTokenは、この検証環境ではXcodeのデバイスコンソールにログ出力させた上で手動でSNSに登録させます。(本来はサーバを立ててそのサーバに通知させてSNS登録させます)

Xcodeでプロジェクトを作成しセッティングする

まずはXcodeでSwift開発のプロジェクトを作成して、必要なセッティングをします。

  • (1) プロジェクトの作成
    • Xcodeを起動し"Create a new Xcode project"を選択します。

f:id:nopipi:20190216191753p:plain:w450

  • (2) テンプレートの選択
    • シンプルな”Single View App”を選択します。

f:id:nopipi:20190216191806p:plain:w450

  • (3) プロジェクトの設定
    • プロジェクトの設定を行います。注意しなければならないのは、以下の3点です。
      • Organization Name: APNs登録で作成した「iOSアプリ開発用証明書」を選択
      • Organization Identifier: APNs登録で作成したApp IDの「Bundle ID」を指定する(異なる指定を指定するとBuildでエラーになる)
      • Language: Swiftを選択

f:id:nopipi:20190216220918p:plain:w450

  • (4) 開発用プロビショニングプロファイルの設定
    • ProjectNavigator(左のバー)のトップ(図のSnsTest部分)をクリック
    • 「General」タブを選択
    • 「Automatically manage signing」無効化する
    • 下記2箇所に事前に作成した開発用プロビショニングプロファイルを指定します。
      • Signing(debug)
      • Signing(Release)
    • プロビジョニングファイルは、事前にダウンロードしたもののインポートと、XcodeがDeveloperからダウンロードする、のいずれかの方法で設定します。

f:id:nopipi:20190217002555p:plain:w450

  • (5) Notificationの有効化
  • プロジェクトのターゲット設定画面のタグで「Capabilities」を選択
  • 「Push Notifications」をONにする

f:id:nopipi:20190217002612p:plain:w450]

Device Token取得コードの追加

アプリケーション起動時にデバイストークンを取得するコードを追加します。

  • 「AppDelegate.swift」を開く
  • 下記の図の部分に、デバイストークン取得とコンソールへのデバイストークン出力用のコードを追加します。

f:id:nopipi:20190217011255p:plain

//
//  AppDelegate.swift
//  SnsTest
//
//  Created by NF on 2019/02/16.
//  Copyright © 2019 NopipiDevTeam. All rights reserved.
//

import UIKit
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        // デバイストークンの要求
        if #available(iOS 10.0, *){
            /** iOS10以上 **/
            let center = UNUserNotificationCenter.current()
            center.requestAuthorization(options: [.alert, .badge, .sound]) {granted, error in
                if error != nil {
                    // エラー時の処理
                    return
                }
                if granted {
                    // デバイストークンの要求
                    print("Get a device token.")
                    UIApplication.shared.registerForRemoteNotifications()
                    print("Done to get a device token.")
                }
            }
        } else {
            /** iOS8以上iOS10未満 **/
            //通知のタイプを設定したsettingを用意
            let setting = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
            //通知のタイプを設定
            application.registerUserNotificationSettings(setting)
            //DevoceTokenを要求
            application.registerForRemoteNotifications()
        }
        
        return true
    }

    // デバイストークン取得が成功した場合呼び出される関数
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        
        let token = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
        print("DeviceToken: " + token)
    }

    // デバイストークン取得が失敗した場合呼び出される関数
    func application(_ application: UIApplication,
                     didFailToRegisterForRemoteNotificationsWithError error: Error) {
        // Try again later.
        print("Can not get Device token")
    }

   以下略
  • バイストークン取得で重要なコードは以下の部分です
    • "import UserNotifications" ・・・UserNotificationsライブラリのインポート
    • UIApplication.shared.registerForRemoteNotifications() ・・デバイストークン取得を実行するメソッド
    • func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) ・・・デバイストークン取得が成功した場合に良い出される関数
      • let token = deviceToken.map { String(format: "%.2hhx", $0) }.joined() ・・・デバイストークン情報を16進数の文字列に変換する
      • print("Done to get a device token.")のコンソール出力

アプリケーションの実行とデバイストークン確認

  • (1) 実行用デバイス(iPhone)の準備
    • テストに利用するiPhoneXcodeを動かしているMacにUSB接続します
    • iPhoneの画面ロックがかかっている状態の場合はロック解除します
    • 右上の停止ボタン右側をクリックします
    • ”No devices connected to 'My Mac'を選択し、_UBS接続したデバイスを登録&選択します(既にデバイスが表示されている場合はそれを選択)
    • USB接続したデバイスをアプリケーションの実行環境として選択します

f:id:nopipi:20190217014514p:plain:w400

  • (2)Active Consoleの表示
    • 「View」->「Debug Area」->「Active Console」で)Active Consoleを開きます(Shift+Command+Cでも可)

f:id:nopipi:20190217014708p:plain:w400

  • (3)アプリケーションの実行
    • 左上の再生ボタンを押してアプリケーションを実行します
    • バイストークン取得が成功すると右下のコンソールにデバイス情報が出力されます

f:id:nopipi:20190217015729p:plain:w500

Amazon SNSへの登録とプッシュ通知の実行

Amazon SNSのアプリケーションプラットホーム作成

  • マネージメントコンソールにログインし、Amazon SNSの画面に移動します
  • 左のナビゲーションバーから「アプリケーション」を選択し
  • 「プラットホームアプリケーショの作成」を選択
  • 必要な情報を入力します
    • アプリケーション名:わかりやすい名称を指定
    • プッシュ通知プラットホーム:今回はAPNsのDevelopmentなので「Apple Development」を選択
    • プッシュ証明書タイプ:「iOSプッシュ証明書」を選択
    • P12ファイル:
      • ファイル選択こちらの最後で作成した、APNs用個人情報交換ファイル(.p12) を指定(ファイル名が日本語だとエラーになるようです)
      • パスワードの入力:P12ファイル生成時に指定したパスワードを指定します
      • 「認証情報」をファイルから読み込みを実行し、証明書・プライベートキーに表示されることを確認

f:id:nopipi:20190217020649p:plain:w500

バイストークン情報の登録

  • (1)プラットフォームエンドポイントの作成
    • 作成したアプリケーションプラットホームに移動し、「プラットフォームエンドポイントの作成」をクリックする
    • アプリケーションで取得したデバイストークンを登録する
    • 「エンドポイントの追加」をする

f:id:nopipi:20190217021508p:plain:w500

  • (2)プッシュ通知の実行
  • アプリケーションプラットフォームで、送信したいデバイスを選択します

f:id:nopipi:20190217023437p:plain:w500

f:id:nopipi:20190217023452p:plain:w500

  • ペイローの内容は以下の通りです。
{
"APNS_SANDBOX":"{\"aps\": {\"alert\": {\"title\":\"テスト\",\"body\":\"これはテストメッセージです\"}, \"sound\": \"default\", \"badge\":1}}"
}
  • こんな感じに送れます。

f:id:nopipi:20190217023907p:plain:w300

プッシュ通知のペイロードの詳細についてはこちらを参照
nopipi.hatenablog.com