default eye-catch image.

VPC内にElastic Beanstalk + RDS の環境を構築して Laravel アプリをデプロイする [Laravel5アプリデプロイ編]

今後も何度か同じことを調べそうなので忘備録としてまとめておく。手順をstep by stepで記述するが、それぞれ分量が多いので以下の通り章立てを行ってエントリを分割する。 VPC構築編 VPCの作成 VPC、NetworkACL、SecurityGroupの確認、タグ付け VPCのDNSホスト名を有効にする サブネットの作成、タグ付け インターネットゲートウェイの作成、タグ付け、VPCにアタッチ パブリックサブネット用RoutingTableの作成 パブリックサブネット用RoutingTableにインターネットゲートウェイの関連付け 作成したサブネットをパブリックサブネット用RoutingTableに繋ぎかえる RDS構築編 MySQLの設定変更(DBパラメータグループの作成) DBサブネットグループを作成 RDS用セキュリティグループの作成 RDSインスタンスの作成 エンドポイントの確認、疎通確認 Elastic Beanstalk構築編 環境の構築 .ebextensionsによるカスタマイズ Laravel5アプリデプロイ編(本エントリ) gitリポジトリからclone .envの編集 アプリで使うDBのダンプファイルをRDSにコピー Laravelインストーラの準備 Laravel本体のインストール ビルトインサーバでの疎通確認 Elastic Beanstalkへのデプロイ gitリポジトリからclone 通常 Laravel5 にはデフォルトの .gitignore が作られており、Laravel本体は.gitignoreに登録されている。まず、アプリをcloneし、そのあとでLaravel本体をインストールする。 どこか、Laravel5の要件を満たすところでgitリポジトリからcloneする。 $ pwd /home/ikuty/www/ $ git clone ssh://servername/var/git/project.git project .envの編集 アプリケーションの設定を変更する。/config 内に各種設定ファイルが配置されているが、laravelでは.envを変更することで /config内のenv()関数に展開される仕組みになっている。だから、/config 内の各種ファイルを直接変更する必要はない。/config 内も git の構成管理に含めても良い。 cloneした先でenvを編集する。.env_example をリネームすると楽。 APP_KEY= *** 次項で示すようにキーを生成する DB_HOST : RDS編で設定したRDSエンドポイントを指定する DB_DATABASE, USERNAME, PASSWORD : RDS編で設定した各種設定を指定する APP_ENV=local APP_DEBUG=true APP_KEY=**** APP_URL=http://hoge.com/ DB_CONNECTION=mysql DB_HOST=rds-endpoint DB_PORT=3306 DB_DATABASE=hogedb DB_USERNAME=hoge DB_PASSWORD=fuga CACHE_DRIVER=file SESSION_DRIVER=file QUEUE_DRIVER=sync キーを生成する。以下のコマンドで .env の APP_KEY が差し替わる。 $ php artisan key:generate アプリで使うDBのダンプファイルをRDSにコピー 本来は Migration を使うべきだが...。Migration を利用しないでDBを作ってしまった場合は泥臭くやる...;;まず、ダンプ。 $ mysqldump -u hoge -p appdb > appdb.sql RDS側にDBを作成し権限を付与しておく。 $ mysql -h [rds-endpoint] -u hoge -p $ > CREATE DATABSE appdb; $ > GRANT ALL ON appdb.* to hoge@localhost; $ > FLUSH PRIVILEGES; $ > SHOW DATABSES; $ > ... RDS上のDBにダンプしたsqlを流し込む。 $ mysql -h [rds-endpoint] -u hoge -p -D appdb < appdb.sql Laravelインストーラの準備 composerを利用する。パスを通す。本エントリ作成時の最新は version1.3.3。 $ composer global require \"laravel/installer=~1.1\" $ echo \'export PATH=~/.composer/vendor/laravel/installer:$PATH\' >> ~/.bash_profile $ source .bash_profile $ laravel Laravel Installer version 1.3.3 Laravel本体のインストール composerが準備できたらcomposer installでLaravel本体をインストールする。 $ cd project $ composer install ビルトインサーバでの疎通確認 この状態でビルトインサーバを立ち上げ、疎通確認を行う。 $ cd project php artisan serve --host hoge.com Laravel development server started on http://hoge.com:8000/ Elastic Beanstalkへのデプロイ Elastic Beanstalk構築編にて、環境の準備が出来ているものとする。 eb deploy コマンドにより、カレントディレクトリ以下のファイルをデプロイする。また、.ebextension に DocumentRoot を設定する。カレントディレクトリと DocumentRoot の組み合わせを正しく設定する必要がある。 まず、DocumentRootを設定するため、.ebextensionディレクトリを作成し、設定ファイルを作成する。 $ pwd /home/ikuty/www/ $ mkdir .ebextensions $ cd .ebextensions $ vi 01_documentroot.config 01.documentroot_config は以下の通り。YAMLとして記述する必要がある。空白とタブの扱いがシビアなので、タブが入らないように注意する。ファイルの先頭の\"01\" は、ファイルが解釈される順番を表す。.ebextensions 内に、01,02,... のように設定を複数配置できる。 option_settings: - namespace: aws:elasticbeanstalk:container:php:phpini option_name: document_root value: /project/public DocumentRoot を /project/public と設定した場合に正しい構成になるように、カレントディレクトリを移動し、eb deployを実行する。~/www に移動して、/project 以下をデプロイすれば良い。 $ cd ~/www/ $ eb deploy Creating application version archive \"app-160630_130423\". Uploading: [##################################################] 100% Done... INFO: Environment update is starting. INFO: Deploying new version to instance(s). INFO: New application version was deployed to running EC2 instances. INFO: Environment update completed successfully. 設定が正しければ2分弱で完了する。

default eye-catch image.

VPC内にElastic Beanstalk + RDS の環境を構築して Laravel アプリをデプロイする [ElasticBeanstalk構築編]

今後も何度か同じことを調べそうなので忘備録としてまとめておく。手順をstep by stepで記述するが、それぞれ分量が多いので以下の通り章立てを行ってエントリを分割する。 VPC構築編 VPCの作成 VPC、NetworkACL、SecurityGroupの確認、タグ付け VPCのDNSホスト名を有効にする サブネットの作成、タグ付け インターネットゲートウェイの作成、タグ付け、VPCにアタッチ パブリックサブネット用RoutingTableの作成 パブリックサブネット用RoutingTableにインターネットゲートウェイの関連付け 作成したサブネットをパブリックサブネット用RoutingTableに繋ぎかえる RDS構築編 MySQLの設定変更(DBパラメータグループの作成) DBサブネットグループを作成 RDS用セキュリティグループの作成 RDSインスタンスの作成 エンドポイントの確認、疎通確認 Elastic Beanstalk構築編(本エントリ) 環境の構築 .ebextensionsによるカスタマイズ Laravel5アプリデプロイ編 gitリポジトリからclone .envの編集 アプリで使うDBのダンプファイルをRDSにコピー Laravelインストーラの準備 Laravel本体のインストール ビルトインサーバでの疎通確認 Elastic Beanstalkへのデプロイ 環境の構築 Elastic Beanstalk の各インスタンスを収める「環境」を作成する。この「環境」の中に、これまで作ってきたVPCの要素を追加する。 $ eb create hoge-production --sample # アプリケーションのデプロイは後で行う。今はSampleアプリケーションをデプロイ --cname com-sample-production --instance_type t2.micro --region ap-northeast-1 --tier webserver --vpc.ec2subnets subnet-b71eeac1 #VPCに作成したパブリックサブネット --vpc.elbsubnets subnet-b71eeac1 #VPCに作成したパブリックサブネット(同じにする) --vpc.id vpc-cc2b11a9 --vpc.securitygroups sg-23fe8447 #デフォルトセキュリティグループ --vpc.publicip --vpc.elbpublic 3分から5分程度時間を要する。 作成した環境のURLを開く $eb open http://com-sample-production.ap-northeast-1.elasticbeanstalk.com/ .ebextensionsによるカスタマイズ Laravel5アプリケーションをデプロイするにあたって、環境をカスタマイズする。(qiitaを参考にした) $ pwd /home/ikuty/www $ mkdir .ebextensions $ cd .ebextensions # timezoneを変更する vi 01_settimezone.config 各ファイルは以下の通り。なお、各ファイルは yaml形式であり、空白とタブの扱いがシビア。タブでインデントすると簡単に謎のエラーに悩まされる。空文字はタブではなく半角スペース。 01_settimezone.config command: 01-set_timezone: command: cp /usr/share/zoneinfo/Japan /etc/localtime

default eye-catch image.

VPC内にElastic Beanstalk + RDS の環境を構築して Laravel アプリをデプロイする [RDS構築編]

今後も何度か同じことを調べそうなので忘備録としてまとめておく。手順をstep by stepで記述するが、それぞれ分量が多いので以下の通り章立てを行ってエントリを分割する。 VPC構築編 VPCの作成 VPC、NetworkACL、SecurityGroupの確認、タグ付け VPCのDNSホスト名を有効にする サブネットの作成、タグ付け インターネットゲートウェイの作成、タグ付け、VPCにアタッチ パブリックサブネット用RoutingTableの作成 パブリックサブネット用RoutingTableにインターネットゲートウェイの関連付け 作成したサブネットをパブリックサブネット用RoutingTableに繋ぎかえる RDS構築編(本エントリ) MySQLの設定変更(DBパラメータグループの作成) DBサブネットグループを作成 RDS用セキュリティグループの作成 RDSインスタンスの作成 エンドポイントの確認、疎通確認 Elastic Beanstalk構築編 環境の構築 .ebextensionsによるカスタマイズ Laravel5アプリデプロイ編 gitリポジトリからclone .envの編集 アプリで使うDBのダンプファイルをRDSにコピー Laravelインストーラの準備 Laravel本体のインストール ビルトインサーバでの疎通確認 Elastic Beanstalkへのデプロイ MySQLの設定変更 my.cnf等のコンフィグファイルに記述していた内容をRDS風に記述する。RDSでは「DBパラメータ」に設定を記述していく。まず、DBパラメータを作成する。 $ aws rds create-db-parameter-group --db-parameter-group-name mydbparamgroup --db-parameter-group-family mysql5.6 --description \"public rds\" { \"DBParameterGroup\": { \"DBParameterGroupName\": \"mydbparamgroup\", \"DBParameterGroupFamily\": \"mysql5.6\", \"Description\": \"public rds\" } } 文字コード関連の設定 $ aws rds modify-db-parameter-group --db-parameter-group-name --parameters ParameterName=character_set_client,ParameterValue=utf8mb4,ApplyMethod=immediate ParameterName=character_set_connection,ParameterValue=utf8mb4,ApplyMethod=immediate ParameterName=character_set_database,ParameterValue=utf8mb4,ApplyMethod=immediate ParameterName=character_set_results,ParameterValue=utf8mb4,ApplyMethod=immediate ParameterName=character_set_server,ParameterValue=utf8mb4,ApplyMethod=immediate ParameterName=collation_connection,ParameterValue=utf8mb4_general_ci,ApplyMethod=immediate ParameterName=collation_server,ParameterValue=utf8mb4_general_ci,ApplyMethod=immediate ParameterName=skip-character-set-client-handshake,ParameterValue=0,ApplyMethod=pending-reboot init_connectパラメータの設定。デフォルトの設定をファイルに落とし、ファイルを変更する。 $ aws rds modify-db-parameter-group --generate-cli-skeleton > init_connect.json # 変更後 $ cat init_connect.json { \"DBParameterGroupName\": \"\", \"Parameters\": [ { \"ParameterName\": \"\", \"ParameterValue\": \"\", \"Description\": \"\", \"Source\": \"\", \"ApplyType\": \"\", \"DataType\": \"\", \"AllowedValues\": \"\", \"IsModifiable\": true, \"MinimumEngineVersion\": \"\", \"ApplyMethod\": \"\" } ] } # 出力された設定ファイルを変更する $ vi init_connect.json # 変更内容は以下の通り $ cat init_connect.json { \"DBParameterGroupName\": \"mydbparamgroup\", \"Parameters\": [ { \"ParameterName\": \"init_connect\", \"ParameterValue\": \"SET SESSION time_zone = CASE WHEN POSITION(\'rds\' IN CURRENT_USER()) = 1 THEN \'UTC\' ELSE \'Asia/Tokyo\' END;\", \"Description\": \"\", \"Source\": \"\", \"ApplyType\": \"\", \"DataType\": \"\", \"AllowedValues\": \"\", \"IsModifiable\": true, \"MinimumEngineVersion\": \"\", \"ApplyMethod\": \"immediate\" } ] } # 設定ファイルを読み込む $ aws rds modify-db-parameter-group --cli-input-json file://init_connect.json { \"DBParameterGroupName\": \"mydbparamgroup\" } DBサブネットグループを作成 DB構築編にてAvailabilityZoneが異なるDB用サブネットを2個作成した。それぞれID/tagは\"subnet-131feb65\"/\"subnet public db1\"、\"subnet-d6d2d38f\"/\"subnet public db2\"であった。 DB サブネットグループmydbsubnetgroupを作成する。 aws rds create-db-subnet-group --db-subnet-group-name mydbsubnetgroup --db-subnet-group-description \"DB SubnetGroup for RDS Instance\" --subnet-ids subnet-131feb65 subnet-d6d2d38f { \"DBSubnetGroup\": { \"Subnets\": [ { \"SubnetStatus\": \"Active\", \"SubnetIdentifier\": \"subnet-131feb65\", \"SubnetAvailabilityZone\": { \"Name\": \"ap-northeast-1a\" } }, { \"SubnetStatus\": \"Active\", \"SubnetIdentifier\": \"subnet-d6d2d38f\", \"SubnetAvailabilityZone\": { \"Name\": \"ap-northeast-1c\" } } ], \"DBSubnetGroupName\": \"mydbsubnetgroup\", \"VpcId\": \"vpc-cc2b11a9\", \"DBSubnetGroupDescription\": \"DB SubnetGroup for RDS Instance\", \"SubnetGroupStatus\": \"Complete\" } } RDS用セキュリティグループの作成 RDS用セキュリティグループ myrds を作成する。 $ aws ec2 create-security-group --group-name myrds --description \"RDS security group\" --vpc-id vpc-cc2b11a9 { \"GroupId\": \"sg-b50742d1\" } #タグ付け $ aws ec2 create-tags --resources sg-b50742d1 --tags Key=Name,Value=\"sg rds\" RDS MySQLにVPCから接続するために、RDS用セキュリティグループに3306からのInbound許可設定を行う。(作成済のVPCセキュリティグループのIDはsg-73490c17。) $ aws ec2 authorize-security-group-ingress --group-id sg-b50742d1 --protocol tcp --port 3306 --source-group sg-73490c17 [参考] 下記のようにCIDR標記のアドレスを指定するとVPCの外部からも接続可能になる。 $ aws ec2 authorize-security-group-ingress --group-id sg-b50742d1 --protocol tcp --port 3306 --cidr 0.0.0.0/0 RDSインスタンスの作成 DBパラメータグループ、デフォルトセキュリティグループ、RDS用セキュリティグループを指定する。 外部からの接続を許可する場合 -- publicly-accessible を指定する必要がある。 $ aws rds create-db-instance --db-instance-identifier hogedb --allocated-storage 5 --db-instance-class db.t2.micro --engine MySQL --engine-version 5.6.22 --master-username hoge --master-user-password fuga --db-name hogedb --db-parameter-group-name mydbparamgroup --db-subnet-group-name mydbsubnetgroup --vpc-security-group-ids sg-23fe8447 sg-b50742d1 --storage-type standard --availability-zone ap-northeast-1a --no-multi-az --region ap-northeast-1 --publicly-accessible --no-auto-minor-version-upgrade 疎通確認 エンドポイントを確認する。予めjqコマンドを利用可能にしておくこと。 # jqがない場合 $ sudo yum install jq $ aws rds describe-db-instances --db-instance-identifier hogedb | jq \'.DBInstances[].Endpoint\' { \"Address\": \"hogedb.c3w6*******5990.ap-northeast-1.rds.amazonaws.com\", \"Port\": 3306 } PublicIp、PublicDnsName を確認する。 $ aws ec2 describe-network-interfaces --filters \"Name=description,Values=RDSNetworkInterface\" { .. \"Description\": \"RDSNetworkInterface\", \"Association\": { \"PublicIp\": \"54.95.100.249\", \"PublicDnsName\": \"ec2-54-92-100-249.ap-northeast-1.compute.amazonaws.com\", \"IpOwnerId\": \"amazon-rds\" }, .. } mysql コマンドでエンドポイントに接続してみる。 $ mysql -h 54.95.100.249 -P 3306 -u hoge -p -D hogedb Enter password: fuga Welcome to the MySQL monitor. Commands end with ; or g. Your MySQL connection id is 9 Server version: 5.6.22-log MySQL Community Server (GPL) Copyright (c) 2000, 2016, Oracle and/or its affiliates. All rights reserved. ...

default eye-catch image.

VPC内にElastic Beanstalk + RDS の環境を構築して Laravel アプリをデプロイする [VPC作成編]

今後も何度か同じことを調べそうなので忘備録としてまとめておく。手順をstep by stepで記述するが、それぞれ分量が多いので以下の通り章立てを行ってエントリを分割する。 VPC構築編(本エントリ) VPCの作成 VPC、NetworkACL、SecurityGroupの確認、タグ付け VPCのDNSホスト名を有効にする サブネットの作成、タグ付け インターネットゲートウェイの作成、タグ付け、VPCにアタッチ パブリックサブネット用RoutingTableの作成 パブリックサブネット用RoutingTableにインターネットゲートウェイの関連付け 作成したサブネットをパブリックサブネット用RoutingTableに繋ぎかえる RDS構築編 MySQLの設定変更(DBパラメータグループの作成) DBサブネットグループを作成 RDS用セキュリティグループの作成 RDSインスタンスの作成 エンドポイントの確認、疎通確認 Elastic Beanstalk構築編 環境の構築 .ebextensionsによるカスタマイズ Laravel5アプリデプロイ編 gitリポジトリからclone .envの編集 アプリで使うDBのダンプファイルをRDSにコピー Laravelインストーラの準備 Laravel本体のインストール ビルトインサーバでの疎通確認 Elastic Beanstalkへのデプロイ 前提 aws cli, eb-cli をインストールしておくこと。 $ pwd /home/ikuty $ aws --version aws-cli/1.10.37 Python/2.6.6 Linux/2.6.32-573.22.1.el6.x86_64 botocore/1.4.27 $ eb --version EB CLI 3.7.6 (Python 2.7.9) Administrator AccessポリシーをもつユーザをIAMで作成し、aws-cli から該当ユーザの権限で操作できるようにしておくこと。 $ aws configure AWS Access Key ID [****************] AWS Secret Key [***********************] Default region name [ap-northeast-1] Default output format [json] VPCの環境構築 VPCを作成する。アドレスの範囲をCIDR標記で記述する。ネットワーク部を16bit取る。 vpc-cc2b11a9というIDのVPCが作成されたという報告がjsonで戻る。 $ aws ec2 create-vpc --cidr-block 10.0.0.0/16 { \"Vpc\": { \"VpcId\": \"vpc-cc2b11a9\", \"InstanceTenancy\": \"default\", \"State\": \"pending\", \"DhcpOptionsId\": \"dopt-fec65c9b\", \"CidrBlock\": \"10.0.0.0/16\", \"IsDefault\": false } } vpc-cc2b11a9というVPCにNameタグを付与する。 $ aws ec2 create-tags --resources vpc-cc2b11a9 --tags Key=Name,Value=\"vpc\" VPCとは、クラウド上に閉じたネットワークを構築する機能であり、VPC作成時に、閉じたネットワークを構成する要素が同時に生成される。構成要素は以下の通り。 Routing Table Subnet Network ACL Security group なお、Network ACL、Security group について AWS のドキュメントでは以下の通り説明している。 Amazon VPCのネットワークACL(アクセスコントロールリスト)は、サブネット内外のトラフィックを制御するファイアウォールとして任意のセキュリティを提供します。セキュリティグループの設定と同じようにACLのルールを適応することによって、VPCに追加のセキュリティ層を提供します。EC2はサブネット指定ができませんのでネットワークACLを利用することはできません。 なんのこっちゃ、だが、図を見ると一目瞭然である。NetworkACLを使ってサブネット毎にファイアウォールを定義できる! NetworkACL、SecurityGroupいずれもファイアウォールのように見えるが、以下のような違いがある。 セキュリティグループ ネットワーク ACL インスタンスレベルで動作します(第 1 保護レイヤー) サブネットレベルで動作します(第 2 保護レイヤー) ルールの許可のみがサポートされます ルールの許可と拒否がサポートされます ステートフル: ルールに関係なく、返されたトラフィックが自動的に許可されます ステートレス: 返されたトラフィックがルールによって明示的に許可されます トラフィックを許可するかどうかを決める前に、すべてのルールを評価します トラフィックを許可するかどうかを決めるときに、順番にルールを処理します インスタンスの起動時に誰かがセキュリティグループを指定した場合、または後でセキュリティグループをインスタンスに関連付けた場合にのみ、インスタンスに適用されます。 関連付けられたサブネット内のすべてのインスタンスに自動的に適用されます(バックアップの保護レイヤーなので、セキュリティグループを指定する人物に依存する必要はありません) Routing tableにタグをつける vpc-cc2b11a9というIDを持ったVPCのRouting tableを確認する。rtb-964da5f2というIDを持ったRouting tableを確認できる。 $ aws ec2 describe-route-tables --filters \"Name=vpc-id,Values=vpc-cc2b11a9\" { \"RouteTables\": [ { \"Associations\": [ { \"RouteTableAssociationId\": \"rtbassoc-b2fd42d6\", \"Main\": true, \"RouteTableId\": \"rtb-964da5f2\" } ], \"RouteTableId\": \"rtb-964da5f2\", \"VpcId\": \"vpc-cc2b11a9\", \"PropagatingVgws\": [], \"Tags\": [], \"Routes\": [ { \"GatewayId\": \"local\", \"DestinationCidrBlock\": \"10.0.0.0/16\", \"State\": \"active\", \"Origin\": \"CreateRouteTable\" } ] } ] } $ aws ec2 create-tags --resources rtb-964da5f2 --tags Key=Name,Value=\"rtb main\" NetworkACLにタグをつける $ aws ec2 describe-network-acls --filters \"Name=vpc-id,Values=vpc-cc2b11a9\" { \"NetworkAcls\": [ { \"Associations\": [], \"NetworkAclId\": \"acl-25fb1a41\", \"VpcId\": \"vpc-cc2b11a9\", \"Tags\": [], \"Entries\": [ { \"CidrBlock\": \"0.0.0.0/0\", \"RuleNumber\": 100, \"Protocol\": \"-1\", \"Egress\": true, \"RuleAction\": \"allow\" }, { \"CidrBlock\": \"0.0.0.0/0\", \"RuleNumber\": 32767, \"Protocol\": \"-1\", \"Egress\": true, \"RuleAction\": \"deny\" }, { \"CidrBlock\": \"0.0.0.0/0\", \"RuleNumber\": 100, \"Protocol\": \"-1\", \"Egress\": false, \"RuleAction\": \"allow\" }, { \"CidrBlock\": \"0.0.0.0/0\", \"RuleNumber\": 32767, \"Protocol\": \"-1\", \"Egress\": false, \"RuleAction\": \"deny\" } ], \"IsDefault\": true } ] } $ aws ec2 create-tags --resources acl-25fb1a41 --tags Key=Name,Value=\"acl main\" SecurityGroupにタグをつける SecurityGroupを確認する。ID=sg-23fe8447というSecurityGroupが存在することがわかる。 $ aws ec2 describe-security-groups --filters \"Name=vpc-id,Values=vpc-cc2b11a9\" { \"SecurityGroups\": [ { \"IpPermissionsEgress\": [ { \"IpProtocol\": \"-1\", \"IpRanges\": [ { \"CidrIp\": \"0.0.0.0/0\" } ], \"UserIdGroupPairs\": [], \"PrefixListIds\": [] } ], \"Description\": \"default VPC security group\", \"IpPermissions\": [ { \"IpProtocol\": \"-1\", \"IpRanges\": [], \"UserIdGroupPairs\": [ { \"UserId\": \"281631559249\", \"GroupId\": \"sg-23fe8447\" } ], \"PrefixListIds\": [] } ], \"GroupName\": \"default\", \"VpcId\": \"vpc-cc2b11a9\", \"OwnerId\": \"281631559249\", \"GroupId\": \"sg-23fe8447\" } ] } $ aws ec2 create-tags --resources sg-23fe8447 --tags Key=Name,Value=\"sg main\" VPCのDNSホスト名を有効にする VPC内のRDSに外部から接続できるようにDNSホスト名を有効にする。 #有効にする $ aws ec2 modify-vpc-attribute --vpc-id vpc-cc2b11a9 --enable-dns-hostnames #確認する $ aws ec2 describe-vpc-attribute --vpc-id vpc-cc2b11a9 --attribute enableDnsHostnames { \"VpcId\": \"vpc-cc2b11a9\", \"EnableDnsHostnames\": { \"Value\": true } } サブネットを作成する 最初のVPC作成時に、16ビットをネットワーク部として使う意図で、CIDR標記で/16を設定した。さらに、8ビットをサブネットとして利用するため、CIDR標記で/24を設定する。8ビット分(第3オクテット分)がサブネットとして利用でき、そのうち .0、.1、.2 のサブネットを作成する。第4オクテットがホスト部となるが、8ビット全て利用することはできず、利用できる最大数がそれぞれ\"AvailableIpAddressCount\"に書かれてる。なお、各サブネットにそれぞれIDが振られる。 RDSをMultiAZで運用しない場合でもDBサブネットグループを作成する際に2つのAvailabilityZoneが必要なので異なるAvailabilityZoneに対応するサブネットを作成する。 $ aws ec2 create-subnet --vpc-id vpc-cc2b11a9 --cidr-block 10.0.0.0/24 --availability-zone ap-northeast-1a { \"Subnet\": { \"VpcId\": \"vpc-cc2b11a9\", \"CidrBlock\": \"10.0.0.0/24\", \"State\": \"pending\", \"AvailabilityZone\": \"ap-northeast-1a\", \"SubnetId\": \"subnet-b71eeac1\", \"AvailableIpAddressCount\": 251 } } # ap-northeast-1a AvailabilityZone $ aws ec2 create-subnet --vpc-id vpc-cc2b11a9 --cidr-block 10.0.1.0/24 --availability-zone ap-northeast-1a { \"Subnet\": { \"VpcId\": \"vpc-cc2b11a9\", \"CidrBlock\": \"10.0.1.0/24\", \"State\": \"pending\", \"AvailabilityZone\": \"ap-northeast-1a\", \"SubnetId\": \"subnet-131feb65\", \"AvailableIpAddressCount\": 251 } } # ap-northeast-1c AvailabitliyZone $ aws ec2 create-subnet --vpc-id vpc-cc2b11a9 --cidr-block 10.0.2.0/24 --availability-zone ap-northeast-1c { \"Subnet\": { \"VpcId\": \"vpc-cc2b11a9\", \"CidrBlock\": \"10.0.2.0/24\", \"State\": \"pending\", \"AvailabilityZone\": \"ap-northeast-1c\", \"SubnetId\": \"subnet-d6d2d38f\", \"AvailableIpAddressCount\": 251 } } サブネットにタグを付与する 作成したサブネットには、それぞれ、subnet-b71eeac1、subnet-131feb65、subnet-d6d2d38f というIDが振られている。それぞれにタグを付与する。 $ aws ec2 create-tags --resources subnet-b71eeac1 --tags Key=Name,Value=\"subnet public web\" $ aws ec2 create-tags --resources subnet-131feb65 --tags Key=Name,Value=\"subnet public db1\" $ aws ec2 create-tags --resources subnet-d6d2d38f --tags Key=Name,Value=\"subnet public db2\" インターネットゲートウェイの作成 インターネットゲートウェイを作成し、タグを付与する。 $ aws ec2 create-internet-gateway { \"InternetGateway\": { \"Tags\": [], \"InternetGatewayId\": \"igw-5bb8203e\", \"Attachments\": [] } } $ aws ec2 create-tags --resources igw-5bb8203e --tags Key=Name,Value=\"igw main\" インターネットゲートウェイをVPCにアタッチする。 #アタッチ $ aws ec2 attach-internet-gateway --internet-gateway-id igw-5bb8203e --vpc-id vpc-cc2b11a9 #確認 $ aws ec2 describe-internet-gateways --internet-gateway-id igw-5bb8203e { \"InternetGateways\": [ { \"Tags\": [ { \"Value\": \"igw main\", \"Key\": \"Name\" } ], \"InternetGatewayId\": \"igw-5bb8203e\", \"Attachments\": [ { \"State\": \"available\", \"VpcId\": \"vpc-cc2b11a9\" } ] } ] } パブリックサブネット用のRoutingTableを作成する VPC作成時に\"rtb-964da5f2\"というIDを持つRoutingTableが作成されていることを確認した。\"rtb-964da5f2\"にはVPC作成時に自動生成されたデフォルトサブネットが紐付いている。 今回、オリジナルのサブネット、インターネットゲートウェイを新たに作成するのに合わせて、RoutingTableを新規作成する。 # RoutingTable の作成 $ aws ec2 create-route-table --vpc-id vpc-cc2b11a9 { \"RouteTable\": { \"Associations\": [], \"RouteTableId\": \"rtb-fe5cb49a\", \"VpcId\": \"vpc-cc2b11a9\", \"PropagatingVgws\": [], \"Tags\": [], \"Routes\": [ { \"GatewayId\": \"local\", \"DestinationCidrBlock\": \"10.0.0.0/16\", \"State\": \"active\", \"Origin\": \"CreateRouteTable\" } ] } } # タグ付け $ aws ec2 create-tags --resources rtb-fe5cb49a --tags Key=Name,Value=\"rtb public\" パブリックサブネット用RoutingTableにインターネットゲートウェイを関連付ける $ aws ec2 create-route --route-table-id rtb-fe5cb49a --destination-cidr-block 0.0.0.0/0 --gateway-id igw-5bb8203e { \"Return\": true } 新たに作成したサブネットをパブリックサブネット用RoutingTableに繋ぎかえる $ aws ec2 associate-route-table --route-table-id rtb-fe5cb49a --subnet-id subnet-b71eeac1 { \"AssociationId\": \"rtbassoc-0ce55a68\" } $ aws ec2 associate-route-table --route-table-id rtb-fe5cb49a --subnet-id subnet-131feb65 { \"AssociationId\": \"rtbassoc-c1e45ba5\" } $ aws ec2 associate-route-table --route-table-id rtb-fe5cb49a --subnet-id subnet-d6d2d38f { \"AssociationId\": \"rtbassoc-76c97612\" }

default eye-catch image.

OAuth2 implicit grant flow で FitbitAPI の認証をパスするサンプル

これまで、OAuth2 Authenticate code grant flow で FitbitAPI の認証をパスしてデータを取得する事例を書いてきた。implicit grant flow では認証をパスできないのか調べてみた。 Fitbit API の認証を implicit grant flowでパスする際の特徴は以下の通り。 implicit grant flow でFitbitAPI の認証をパスするには、アプリケーションタイプを Client として登録する必要がある。 access tokenの消費期限はユーザが決定する。 refresh tokenを使ってaccess tokenを更新する場合、ユーザの承認が必要となる。 本エントリにて、implicit grant flow で FitbitAPI の認証する方法を step by step で追ってみる。 Authenticate code grant flow と implicit grant flow の違いについて、↓が参考になりました。What is the difference between the 2 workflows? When to use Authorization Code flow? Authenticate code grant flow Authenticate code grant flow は以下の流れだった。 ClientID, ClientSecret から認可コードを取得する 認可コードと accessToken を交換する accessTokenを取得するためには ClientID, ClientSecret を保持しておく必要があった。 本家から図を転載する。 implicit grant flow 対して、implicit grant flow は以下のような流れとなる。 まず、本家からの図の転載。 まとめると次の通り。 ClientIDを使って accessToken を取得する https://www.fitbit.com/oauth2/authorize?state=fx2D6zGcbNVltC15sQlxh4wP8U8HH53%2B &scope=activity+heartrate+location+profile+settings+sleep+social+weight &response_type=token &client_id=xxxxxx &redirect_uri=http%3A%2F%2Fhoge.com%3A8002%2Ftest.php ブラウザ上で認証画面が表示される。この画面はブラウザである必要があり埋め込んでスルーしたりすることはできない。 許可すると、指定したコールバックURLがパラメータ付きで呼び出される。(当然伏字です) http://hoge.com:8002/test.php#scope=weight+location+social+settings+heartrate+sleep+activity+profile &state=fx2D6zGcbNVltC15sQlxh4wP8U8HH53%252B &user_id=xxxxx &token_type=Bearer &expires_in=69483 &access_token=eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0NjY5MzMyMzcsInNjb3BlcyI6InJ3ZWkgc nBybyByaHIgcmxvYyByc2xlIHJzZXQgcmFjdCByc29jIiwic3ViIjoiM1FaVllYIiwiYXVkIjoiMjI3V EZKIiwiaXNzIjoiRml0Yml0IiwidHlwIjoiYWNjZXNzX3Rva2VuIiwiaWF0IjoxNDY2ODYzNzU0fQ.oy 0Px-mEauh5Jgw1yS8PF94U37tUW2Q35fbCHKcDFHU ここで得られたaccess_tokenを使って、データアクセスAPIを呼び出す。その際、クエリに含まれるaccess_tokenをAPIにアクセスする度に利用する。クエリに付けるのではなくBASIC認証のヘッダとして付与する。 URLのクエリからaccess_tokenを取得しBASIC認証で利用する。 GET https://api.fitbit.com/1/user/-/profile.json Authorization: Bearer eyJhbGciOiJIUz******************************* ******BybyByaHIgcmxvYyByc2xlIHJzZXQgcmFjdCByc29jIiwic3ViIjoiM1FaVll YIiwiYXVkIjoiMjI3VEZKIiwiaXNzIjoiRml0Yml0IiwidHlwIjoiYWNjZXNzX3Rva2 V**************************************eLtCF0V6IISPinTxy_ZgCLQl1tB0 rEMeqVk4 access_token を使いまわしていると、いずれ access_token の消費期限に到達する。 消費期限に到達した access_token を使ってデータ取得を行うと、以下のようなレスポンスが返ってくる。 { \"errors\": [ { \"errorType\": \"expired_token\", \"message\": \"Access token expired: eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MzAzNDM3MzUsInNjb3BlcyI6Indwcm8gd2xvYyB3bnV0IHdzbGUgd3NldCB3aHIgd3dlaSB3YWN0IHdzb2MiLCJzdWIiOiJBQkNERUYiLCJhdWQiOiJJSktMTU4iLCJpc3MiOiJGaXRiaXQiLCJ0eXAiOiJhY2Nlc3NfdG9rZW4iLCJpYXQiOjE0MzAzNDAxMzV9.z0VHrIEzjsBnjiNMBey6wtu26yHTnSWz_qlqoEpUlpc\" } ] } ここで、Authenticate code grant flow と同様に refresh_token を使って access_token を更新する必要があるのだが、ドキュメントに記述があるように、implicit grant flow では refresh_token を取得するために client_id を使って再度認証する必要がある。 client_id はアプリケーションにストアすべきデータではなく、ユーザによる操作が必要。 Unlike the Authorization Code Grant Flow, the refresh tokens are not issued with the Implicit Grant flow. Refreshing a token requires use of the client secret, which cannot safely be stored in distributed application code. When the access token expires, users will need to re-authorize your app. また、以下に記述があるように、implicit grant flow における access_token の消費期限は Authenticate code grant flow のそれよりも長めに設定される。アプリケーション側で予め消費期限を設定できるが、最終的には認証画面においてユーザが消費機嫌を決めること値が決まる。 Access tokens from the Implicit Grant Flow are longer lived than tokens from the Authorization Code Grant flow. Users may specify the lifetime of the access token from the authorization page when an application uses the Implicit Grant flow. The access token lifetime options are 1 day, 1 week, and 30 days. Applications can pre-select a token lifetime option, but the user ultimately decides.

default eye-catch image.

OAuth2 Implicit Grant Flow とセキュリティ

Implicit Grant Flow を「認証」のための方法として使ってはならない、というが、ちょっと不勉強で理解が曖昧だったので、少し深く理解してみることにした。 参考にしたソースは下記記事 The problem with OAuth for Authentication. 単なる OAuth 2.0 を認証に使うと、車が通れるほどのどでかいセキュリティー・ホールができる OAuth2 implicit grant flow 自分が所有する情報に対してアクセスを認めることを認可と呼ぶ。 自分が所有する情報と自分の間に第三者が入らない場合 その鍵を自分が管理することに問題はない アクセスするための鍵を自分自身が管理し、安全が保障されている通信の中で直接鍵を利用できる。 自分が所有する情報と自分の間に第三者(Webシステムやアプリ)が入る場合 Webシステムやアプリに対して自分自身の情報に対するアクセス権を移譲する。 鍵をWebシステムやアプリに渡してしまうと、Webシステムやアプリの脆弱性により鍵そのものが危険にさらされてしまう。 代わりに合鍵(accessToken)を作成し、Webシステムやアプリは合鍵を使って自分自身の情報にアクセスするようにする。 以後、第三者は大元のアクセス権に触れずに、accessToken/refreshTokenを使う。 第三者システムを仮に実装してみると、accessToken/refreshTokenを該当システムのIDに紐付けて保存することで、そのシステム上のログインユーザにリソースオーナーへのアクセス権を付与できる implicit grant flow を認証に使う implicit grant flow を認証に使う、というのは、第三者システムが、リソースオーナーから取得したIDをそのまま第三者システムのIDとして使うことを指す。例えば、GoogleやYahooに保存されたID(emailなど)に対してアクセスを許可するだけで、第三者システムのログインそのものを許可してしまうようなものを指す。 だいたいどんなサービスを作るにしても、最初は知名度が低くて、ユーザ登録などしてくれないことがほとんど。 サービス提供者が考えるのは、GoogleやYahooなどのログインを自システムへのログインに代替できないか、ということ。 自分自身の情報へのアクセスを認めただけなのに、勝手に自分自身のログインであることの証明に使われてしまう、というのが問題。 問題は 第三者システムに悪意はなくて、ただ借りパクしたいだけなら被害はない。 第三者システムが悪い奴で、取得したaccessToken/refreshTokenを使って、本人になりすまして、別の第三者システムにログインしてしまったら...。 別の第三者システム的には、本人からのアクセスなのか、本人になりすましたシステムからのアクセスなのか、区別することができないから、CSRF対策を行って防げる攻撃ではない。

default eye-catch image.

RESTFul API 設計の掟

はじめに どうすれば、開発者に喜ばれるAPIを設計できるのか、RESTFulAPI設計のバイブル「Web API Design - Crafting Interfaces that developers love」をまとめてみます。 URLの設計方針 URLはリソースを表す URLはあくまでリソースを表す識別子とし名詞により表現すること リソースを複数形で表現すること。 リソースに対する操作を、動詞を使ったURLで表現しないこと 抽象的な名詞より具体的な名詞が良い より多くの意味を含もうとすると items といったボンヤリした単語になる。 items では、APIの利用者(開発者)にとって意味が分かりづらい。 例えば、以下のようなURLは適切。 /dogs/123 /dogs/123/age /dogs/123/color リソースに対する操作をHTTPメソッドで表現する リソースに対する操作をCRUDの4種類に分類すること CRUDを4種類のHTTPメソッドに対応させること。 操作HTTPメソッド CreatePOST ReadGET UpdatePUT DeleteDELETE リソースとHTTPメソッドの組み合わせは以下のような意味となる。 リソースPOSTcreateGETreadPUTupdateDELETEdelete /dogsdogを新規作成dog一覧を取得全てのdogを一括更新全てのdogを削除 /dogs/1234未定義ID=1234のdogを取得もしID=1234のdogがあれば更新もしID=1234のdogがあれば削除 リソース間の関連を簡潔に表現する 原理主義的に全てのリソースをURLに含めるとURLの階層が深くなりがちである。 /owners/954/dogs/123/red/runninng/park ... リソースとパラメータを分類することでリソースを表すURLの階層を浅く保つ パラメータはリスエストパラメータとして表現する /dogs?owner=954&color=&red&state=running&location=park エラーハンドリングをしっかりする API利用者にとってはAPIはブラックボックスとなる。API開発者は利用者に対してブラックボックス内部で発生した出来事を伝える義務がある。 さらにAPI開発者に対しても以下のようなメリットが生まれる TestFirstな開発を進めやすくなる プロダクトが世に出回ったとき発生したトラブルを追跡しやすくなる 以下の2つでエラーを表現する。 HTTPステータスコード HTTPレスポンス文字列 まず、以下の3つのステータスコードで済ませられないか検討する。 200 - OK 400 - Bad Request 500 - Internal Srver Error 必要であれば、以下から選んで上記に加える。 201 - Created 304 - Not Modified 404 - Not Found 401 - Unauthorized 403 - Forbidden 開発者向けメッセージ、利用者向けメッセージなどを含めるなど、レスポンス文字列を可能な限り充実させること。 バージョニング APIの更新による後方互換性を考慮し、URLにバージョンを含めること。例えば以下の通り。 api/v1/hoge/123 api/2016-06-12/hoge/123 api/hoge/123?v=1.0 旧APIをいつまで保守すべきかについて、以下のような方針を取ること。 少なくとも1個前は保守が必要 保守を停止することに対して開発者の反応を\"1サイクル\"見ること。 ここでいう\"1サイクル\"とは、開発対象による。モバイルアプリなら短いだろうしWebアプリなら長くなる。 リソース範囲を限定する方法 開発者はいつも全てのデータを必要としている訳ではない。以下の二つの戦略で取得したい範囲を限定できるべきである。 Partial Response戦略 あるデータについて常に全フィールドを返すのではなく、対応するデータのみ取得できる手段を提供すること。 リクエストパラメータに取得したいフィールドを付与することで実現すること。 Pagenation戦略 1回あたりの取得量を制限できる手段を提供すること。 例えば 100件のデータのうち、1回あたり20件を表示できるようにしたとき、page=3,limit=20 等をリクエストパラメータに付与できること。 複数のフォーマットをサポートする json、xml、csvなど複数のフォーマットをサポートする 以下のようなURL上の表現方法がある クエリパラメータに付与 : /api/v1/dogs/123?alt=json 拡張子 : /api/v1/dogs/123.json jsonフォーマットが最も普及しているためjsonをデフォルトとする jsonフォーマットを採用する場合、属性はJavaScriptの書式(CamelCase,オブジェクトタイプによる大文字小文字制御など)に従うこと 例外的な扱い HTTP Status Code を利用できない環境 HTTP Status Codeとして常に200=OKを返す レスポンスに StatusCode を表す属性を付与する PUT、DELETE等のHTTPメソッドが利用できない環境 全てをGETリクエストとする 動詞部分をクエリパラメータとする GET /api/v1/dogs?method=put&location=park など 認証 RESTfulAPIの認証で一般的なOAuth2.0を採用すること OAuth2.0と似て非なる認証を採用するのはN.G.なぜなら セキュリティ的に危険だから 開発者が慣れていないから 何度も呼び出さないといけないAPIにしない 開発者がどのようにAPIを使うか想像してAPIを設計すること 呼出回数を軽減することを心がけるkとお 例えば、ほとんどのケースである条件で絞り込んだ結果が求められるのに、必ずAPI経由で絞込みを行わせるなどは避ける 使われ方を想定したデフォルトを設定する

default eye-catch image.

Authenticate code grant flow で FitbitAPI からデータを取得する例

Fitbit APIからデータを取得するためには OAuth2 をパスする必要がある。FitbitAPI は以下の2つのflowをサポートしている。 Authenticate grant flow implicit grant flow ドキュメントでは、サーバサイドコードからFitbitAPIにアクセスする場合、Authenticate grant flow が推奨されている。 このエントリでは、djchen/oauth2 というoauth2クライアントを利用して、Authenticate grant flow をパスするサンプルを作成してみる。 composerによるoauth2クライアント(djchen/oauth2)の導入手順 dev.fitbit.comへのサンプルアプリ登録 FitbitのOAuth2認証をパスするためのサンプル実装 Fitbitからサンプルデータの取得 composerによるoauth2クライアントの導入手順 phpスクリプトをブラウザから叩ける環境を用意する。VagrantでもVPSでも何でも。 ~/wwwが DocumentRoot となるように httpd を構成する。 $ pwd /home/ikuty/www 次に、composerを使ってOAuth2クライアントをインストールする。 $ composer require league/oauth2-client djchen/oauth2-fitbit dev.fitbit.comへのサンプルアプリ登録 これから作るサンプルアプリを dev.fitbit.com に登録する必要がある。 例えば以下のような登録を行う。 Application Name: Test Application Description: This is my first test. Application Web Site: http://hoge.com:8001 Organization: Personal Organization Web Site: https://ikuty.com/ OAuth2.0 Application Type: Personal Callback URL: http://hoge.com:8001/test.php Default Data Access: readonly すると、以下を発行してもらえる。 OAUth 2.0 Client ID Client Secret OAuth 2.0: Authorization URI OAUth 2.0: Access/Refresh Token Request URI FitbitのOAuth2認証をパスするためのサンプル実装 サンプル実装、といっても本家のサンプルをそのまま流用しただけだが…。 以下の Authenticate grant flow の流れの通りとなっている。 ClientID,ClientSecretを使用して認証コードを取得する 認証コード を accessToken に変換する accessToken の有効期限に達したら refreshToken を使って accessToken を更新する $ pwd /home/ikuty/www $ vi test.php 中身は以下 <?php require_once \'./vendor/autoload.php\'; use djchenOAuth2ClientProviderFitbit; use LeagueOAuth2ClientTokenAccessToken; $provider = new Fitbit([ \'clientId\' => \'{client_id}\', // 登録時に取得したclientId \'clientSecret\' => \'{clientSecret}\', //登録時に取得したclientSecret \'redirectUri\' => \'http://hoge.com:8002/test.php\' ]); session_start(); if(!isset($_GET[\'code\'])){ $authorizationUrl = $provider->getAuthorizationUrl(); $_SESSION[\'oauth2state\'] = $provider->getState(); header(\'Location: \'.$authorizationUrl); exit; } elseif ( empty($_GET[\'state\']) || ($_GET[\'state\'] != $_SESSION[\'oauth2state\'])){ unset($_SESSION[\'oauth2state\']); exit(\'Invalid state\'); } else { try { $forceToAuth = false; $needToRewrite = false; $lines = file(\'token.txt\',FILE_IGNORE_NEW_LINES); if (($lines == false) || (count($lines) == 0) || $forceToAuth) { echo \'authorization_code->\'; //ここで 認証コード と accessToken を交換する $accessToken = $provider->getAccessToken(\'authorization_code\',[\'code\'=>$_GET[\'code\']]); $needToRewrite = true; } else { echo \'existing AccessToken->\'; $_accessToken = $lines[0]; $_refreshToken = $lines[1]; $_expiredToken = $lines[2]; $accessToken = new AccessToken([\'access_token\'=>$_accessToken, \'refresh_token\'=>$_refreshToken, \'expires_in\'=>$_expiredToken]); // accessToken の有効期限に達したら refreshToken を使って新しい accessTokenを要求する if ($accessToken->hasExpired()) { echo \'refresh AccessToken->\'; $refreshToken = $accessToken->getRefreshToken(); $accessToken = $provider->getAccessToken(\'refresh_token\',[\'refresh_token\'=>$refreshToken]); $needToRewrite = true; } } if ($needToRewrite) { $file = fopen(\"token.txt\",\"wb\"); fputs($file, $accessToken->getToken()); fputs($file, \"n\"); fputs($file, $accessToken->getRefreshToken()); fputs($file, \"n\"); fputs($file, $accessToken->getExpires()); fputs($file, \"n\"); fputs($file, $accessToken->getResourceOwnerId()); fputs($file, \"n\"); fclose($file); } } catch (Exception $e){ } } 最初、「$accessToken->getToken()を保存しておいて次回利用時に使いまわす」具体的な方法が分からなかった。oauth2-client の AccessTokenクラスの実装を見ると、コンストラクタにアクセストークン等を渡してあげればインスタンス化できることがわかった。 アクセストークンの有効期限に達すると、hasExpired()メソッドがtrueを返すようになる。その場合、リフレッシュトークンを使って新しいアクセストークンを要求する。DBに保存しておいたアクセストークンを新しい値で上書きする。 Fitbitからサンプルデータの取得 RESTfulなAPIを指定することでデータが得られる。例えばユーザのプロフィールを取得するには以下の通りとする。厳密にAPIにパラメータを全て埋め込むタイプではなくログイン情報等のセッション情報も用いられるタイプ。以下では、user-idとして\"-\"を渡すとセッションにあるユーザIDが使われるようだ。 $request = $provider->getAuthenticatedRequest( \'GET\', Fitbit::BASE_FITBIT_API_URL . \'/1/user/-/profile.json\', $accessToken, [\'headers\' => [\'Accept-Language\' => \'ja_JP\'],[\'Accept-Locale\' => \'ja_JP\']] ); $response = $provider->getResponse($request); var_dump($response);