20200929のAWSに関する記事は15件です。

2回目でなんとか受かったAWS SAA-C02の思い出

SAA-C02とは

AWS 認定 ソリューションアーキテクト – アソシエイトのことであります。
15,000 円(税別)というなかなかお値段する試験を2回目でなんとか合格できたので、まとめておこうと思います。

元々のAWS力と学習期間

  • もともと触ったことあったサービス

    • IAM : ロールとかポリシーとかどんなもんか知ってたぐらい
    • EC2 : インスタンス立てて, sshぐらい
    • RDS : Lambdaとつないで、RDSプロキシとか触ってた
    • Lambda : 音声ファイル文字起こしとかしてた
    • S3 : 普通にデータ保存しとく用で使ってた
    • SNS : なんかSMS飛ばして遊んだりしてた
    • VPC : インターネットゲートウェイとかNATゲートウェイとか設定したりしたことあった
    • それ以外は多分触ったことなかった...
  • 1回目の学習期間 : 3週間(最初2週間は1日2,3h。最後の1週間はガッツリ1日10hぐらい)

  • 2回目の学習期間 : 1回目落ちてから4週間(1日2,3h)

スコア

  • 1回目:680点
  • 2回目:779点
    720点で合格です。1回目落ちた時に次回絶対オーバーキルしようと思って挑んだにも関わらず、大したスコアでもなかったので、上記のようなタイトルになってます。

やったこと

1回目受験までにやったこと

Udemy

他の記事でも紹介されてますが、私も以下の2つをやりました。

1回目はこの2つ、主に2番目の方ばっかりやって受験しました。

2回目受験までにやったこと

Udemy

AWS WEB問題集で学習しよう

  • 2回目はこちらをメインで学習して、用意されている136回分(1回分あたり7問)のうち、80~136回までを3周解きました。(ゴールドプラン以上に登録する必要あり)
  • 私はこの問題集のおかげで受かったという感じでした。1回目の受験時からやっておけば受かったんじゃないかなと後悔してます。
  • こちらの問題集は本番に酷似した問題を提供してくれているので、Udemyよりおすすめですし、解説もすぐに出てきて、参考リンクがついてるので復習しやすいです。

後悔してること・気をつけた方がいいこと

1回目受験の後悔

  • 見直しをしなかったことです。どうせ受かんないなぁという感覚だったので、1周解いて終わってしまいましたが、意外と680とちょっと惜しかったので、見直ししとけばワンチャンあったかもと思ってます。
  • それでも不合格は不合格で、悪かったこととしてはUdemyの問題丸暗記状態になってしまっていたことです。「なんでこの回答になるのか、他の選択肢がなぜ違うのか」ちゃんと理解しきれていなかったと思います。

2回目受験の後悔

  • AWS WEB問題集で学習しようでの学習においても、「なんでこの回答になるのか、他の選択肢がなぜ違うのか」をより意識すべきだったと思ってます。試験本番でも2択に絞れるけどどっちかわからないみたいなことが何問もありました。

終わりに

上に書きましたが、ただ問題を解くだけでなく、回答になる理由ならない理由をしっかり理解しながら学習を進めることが非常にこの試験では重要だと思います。例えば私の試験には出ませんでしたが、SNSとSQSどっち使うかとかAmazon ElastiCacheのMemcachedとRedisのどちら使うかなど割と深いところまで知っといた方が良いと思います。
今後は一回AWS周りの勉強はおやすみして、直近のセキスペに向けて勉強します。GCPもなんか取りたいなぁ。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS ECRでAWS CLIの認証で弾かれた問題

なにこれ

AWSのECRへ認証するコマンドを叩いた時に、下記のエラーが発生したのでその対策

エラー文

auth error An error occurred (UnrecognizedClientException) when calling the GetAuthorizationToken operation: The security token included in the request is invalid. Error: Cannot perform an interactive login from a non TTY device

訳)auth error GetAuthorizationToken操作の呼び出し中にエラーが発生しました(UnrecognizedClientException):要求に含まれているセキュリティトークンが無効です。エラー:TTY以外のデバイスからインタラクティブログインを実行できません

はい、訳を読んでも意味が分からないです。
セキュリティートークンが無効だから、認証系の問題かな?と予想。
とりあえず、エラー文をはっつけて検索する初歩的な方法をしました。

原因

AWSのACCESS_KEYとSECRET_KEYの情報が一致しないため(?)

原因を調べる

aws configure listコマンドを叩いて、アクセスキーとシークレットキーが何と設定されてるか確認します。

~ $ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************1234             env    
secret_key     ****************1234             env    
    region           ap-northeast-1      config-file    ~/.aws/config

ここで本来あって欲しいアクセスキーとシークレットキーになっていないことに気づく。

なので、下記のコマンドで変更する

export AWS_ACCESS_KEY_ID=****5678
export AWS_SECRET_ACCESS_KEY=****5678

このコマンドで、~/.zshrc

再度aws configure listを叩いてキー2つを確認する。

~ $ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************5678             env    
secret_key     ****************5678             env    
    region           ap-northeast-1      config-file    ~/.aws/config

ターミナルを再起動後、もう1回ECRへ認証するコマンドを叩いたら、成功しました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cognitoで学ぶ認証・認可 in AWS

この記事について

Webアプリのアクセス制御を行いたい!となったときに学ぶべきなのは認証・認可の仕組みです。
AWSにはAmazon Cognitoというユーザー管理を行うための仕組みが存在し、これを利用すれば「実装するだけなら」簡単にアプリのアクセス制御を行うことができます。

この記事では「Cognitoが実際に何をやってくれているのか?」というところまで掘り下げながら、簡単なReactアプリを作っていきます。

アジェンダ

  1. Cognitoのユーザープールを作って触ってみる
  2. Reactアプリに認証の仕組みを入れてみる
  3. Cognitoで認証済みの人だけが叩けるAPIをLambda + API Gatewayで作る
  4. CognitoのIDプールを作り、AWSでの認可の仕組みを学ぶ
  5. Cognito IDプールで認可された人だけが叩けるAPIをLambda + API Gatewayで作る

使用する環境・バージョン

  • React: 16.13.1
  • aws-amplify: 3.3.1
  • aws-amplify-react: 4.2.5
  • @aws-amplify/ui-react: 0.2.21

読者に要求する前提知識

  • Reactについての知識(本記事ではReactそのものの説明についてはしません)
  • REST APIが何かがわかること
  • (Lambda + API GatewayでのREST APIの作り方がわかること)
  • IAMロールが何かがわかり、自分で作成できること

Cognitoユーザープールの作成

まずは、AWSウェブコンソールからユーザープールを作成してみましょう。

ユーザープール作成

スクリーンショット 2020-09-19 19.48.19.png
任意のプール名をつけたあと、設定に進みます。今回は「ステップにしたがって設定」を選択します。
スクリーンショット 2020-09-19 19.56.21.png
認証(SignIn)時に要求する情報を「(ユーザーが決めた)ユーザー名」にするか「メールアドレスor電話番号」にするかの選択です。
今回は、メールアドレスのみを使用するようにしました。
スクリーンショット 2020-09-19 19.57.11.png
ユーザー作成(SignUp)時に、ユーザーが登録する必須項目を選択します。
今回の場合、メールアドレスの他に「name」を登録してもらうことにしました。
スクリーンショット 2020-09-19 20.00.14.png
パスワード関連の設定を行います。

  • パスワードの強度を設定: 数字・特殊文字・大文字小文字を要求するか・長さを決めます。
  • 自己サインアップ: ユーザー作成をAWSの管理画面からのみにするか、一般ユーザーもアプリ画面等から可能にするかを決めます。
  • パスワード有効期限: Sign Up時に発行される、初回ログインのための仮パスワードの有効期限を設定します。

スクリーンショット 2020-09-19 20.06.59.png
ここでは以下の設定を行います。

  • MFA(多要素認証)の設定: offにするかonにするかを選択します。今回は簡略化のためなし。
  • パスワード忘れの時の回復手段: ユーザーがパスワードを忘れたときには、登録済みのメールアドレスから回復させるようにした。
  • ロールの提供: Sign Up時やパスワード忘れからの復旧のときに、Cognitoがメールを送るためのロールを付与する。

他の設定は全てデフォルトのままにして、プールを作成します。
スクリーンショット 2020-09-19 21.39.12.png
ここで確認できるプールIDは控えておきましょう。

参考:[新機能] Amazon Cognito に待望のユーザー認証基盤「User Pools」が追加されました!

アプリクライアントの作成

ユーザープールを使う方のアプリの登録・ID払い出しを行います。
アプリを作る際に「クライアントシークレットを作る」のチェックを外しておきましょう。

スクリーンショット 2020-09-19 23.11.34.png

  • 有効なIDプロバイダ: すべて選択をチェック
  • コールバックURL: httpsかlocalhostを選択。ここでは暫定的にamazonを指定しておく。
  • 許可されているOAuthフロー: Authorization code grantとImplicit grantを選択
  • 許可されているOAuthスコープ: 全て選択

ここで作ったアプリクライアントIDもいずれ使うので控えておきます。

Amazon Cognitoドメインを設定

アプリの統合→ドメイン名のタブを開くと、以下のような画面になる。
スクリーンショット 2020-09-19 22.52.51.png
Amazon Cognitoドメインは、1つのリージョンの中で一意なドメインを設定する必要があります。
まだ使われていなさそうなドメインを「ドメインのプレフィックス」に入力・設定を保存します。

動かせるか確認

まずは、「ユーザーとグループ」のタブから、ユーザーを一つ作成しておきます。

OAuthフロー"Implicit Grant"の使用

アプリクライアントの作成のところで、"Implicit Grant"を許可しているので、まずはそれを試してみます。
以下のURLにアクセスします。

https://[amazon cognitoドメイン]/login?response_type=token&client_id=[アプリクライアントID]&redirect_uri=[コールバックURL]

すると以下のように、Cognitoが提供するサインインフォームが表示されます。
スクリーンショット 2020-09-25 23.53.39.png
ここで、先ほど作ったユーザーのメールアドレスとパスワードを入力してSign Inすると、コールバックURLにリダイレクトします。
この時のリダイレクト先URLハッシュの中に「IDトークン」「アクセストークン」が格納されています。

https://[コールバックURL]#id_token={IDトークン}&access_token={アクセストークン}&expires_in=3600&token_type=Bearer

この後の流れとしては、このURLハッシュで得られたIDトークンとアクセストークンをフロントエンド側で取得・保存し、アプリ内で利用することになります。

参考:【AWS】これだけ見れば理解できるCognito〜認証機能つきサーバレスアーキテクチャの作成〜

OAuthフロー"Authorization Code Grant"の使用

もう一つのOAuthフロー"Authorization Code Grant"も試してみます。
以下のURLにアクセスします。

https://[amazon cognitoドメイン]/login?response_type=code&client_id=[アプリクライアントID]&redirect_uri=[コールバックURL]

先ほどの"Implicit Grant"の違いはresponse_typeが"code"か"token"かというところです。

アクセスしたら、先ほど同様にCognitoが用意したログインフォームがあるので、同様に作ったユーザーでログインします。
すると、以下のURLにリダイレクトされます。

https://[コールバックURL]?code={認可トークン}

これも、リダイレクトURLで得られた認可トークンを保存して使用していくことになります。

今は何をしていたのか

今試してみた「Cognitoが用意したUIを使う」方法は、以下のAWS Blackbelt Seminor資料における「Cognito Auth API と Hosted UI を利用」に該当します。
スクリーンショット 2020-09-29 0.11.34.png
画像出典・資料:[AWS Black Belt Online Seminar] Amazon Cognito

「ログイン・サインインフォームのUIも自分で作りたい!」という場合や、「リダイレクトURLから得たトークンや認可コードを自分で保存・管理しておくのがめんどくさい!」という場合は、もう一方の「Cognito Identity Provider API を利用」を選択することになります。

Reactでフロントエンドを作り、Cognito認証と連携させる

「Cognito Identity Provider API を利用」した方法がこちらです。

ここからは、Cognitoでユーザー認証をしたユーザーだけがアクセス・中身を見ることができるウェブページをReactで作成していきます。

Reactアプリの枠組みを作成

create-react-appで簡単にアプリフレームが作れるので実行します。

$ npx create-react-app [appname]

これで、[appname]という名前のディレクトリができて、そこにReactアプリのフレームが出来上がっています。

パッケージのインストール

Cognitoでの認証・認可をコードベースで扱うためのパッケージをインストールします。
かつては"Amazon Cognito Identity SDK for JavaScript"等のSDKを使用していましたが、今は"AWS Amplify"の方のパッケージにCognito周りのパッケージが統合され、そちらがかなり便利なので今回はこれを使います。
公式Doc: Amplify Framework Documentation

$ npm install aws-amplify aws-amplify-react @aws-amplify/ui-react

コード作成

まずはindex.jsを以下のように修正します。

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
+ import { Amplify } from 'aws-amplify';
+ 
+ Amplify.configure({
+   Auth: {
+       region: "Cognitoユーザープールを作ったリージョン",
+       userPoolId: "作成したユーザープールID",
+       userPoolWebClientId: "作成したアプリクライアントID"
+   }
+ });

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

Amplify.configure()メソッドを使って、ReactアプリでどのCognitoプールを利用するかを指定しています。
参考:公式Doc: Amplify Doc AUTHENTICATION Create or re-use existing backend

次に、App.js を修正します。

App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
+ import { withAuthenticator } from '@aws-amplify/ui-react';

function App() {
  // (略)
}

- export default App;
+ export default withAuthenticator(App);

withAuthenticator()関数はhigher-order component (HoC)という、Reactコンポーネントを受け取って別のReactコンポーネントを返すものです。
withAuthenticator(引数)で得られるコンポーネントを利用することで、引数で渡したコンポーネントの閲覧にCognitoユーザー認証を要求するようにできます。

今回の場合はAppコンポーネントを引数に渡しているので、アプリのどこの画面を見るにしてもCognito認証が必要です。
参考:公式Doc: Amplify Doc AUTHENTICATION Authenticator

アプリの起動

npm run starthttp://localhost:3000が立ち上がり、アプリの挙動が確かめられるようになります。

http://localhost:3000にブラウザでアクセスしてみると、ReactのHello, Worldは表れず、以下のような認証画面が表れます。withAuthenticator(App)の機能通り、ここでユーザー認証が行われないと画面を閲覧することはできないようになっています。

スクリーンショット 2020-09-28 16.24.10.png

ここで、先ほどCognitoユーザープール管理画面で作ったユーザー情報を入力します。
すると、Appコンポーネントの中身である、ReactのHello, World!画面が無事に表れます。

スクリーンショット 2020-09-28 16.24.51.png

画面カスタマイズ

Sign IN/Up周りのUIをデフォルトからカスタマイズする方法を一部紹介します。

サインアウトボタンをつける

デフォルトではアプリ画面にログアウトボタンが存在しません。SAOみたいな狂気の世界にするつもりはない(はず)なので、ログアウトボタンをつけましょう。

AmplifyのAmplifySignOutコンポーネントが、ログアウトボタン機能を実装しているので、それを追加します。

App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
- import { withAuthenticator } from '@aws-amplify/ui-react';
+ import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        // (略)
+    <AmplifySignOut />
      </header>
    </div>
  );
}

export default withAuthenticator(App);

認証フォームをカスタマイズする

デフォルトでは、Sign Upフォームは「メールアドレス」「パスワード」「電話番号」です。しかし例えば、電話番号をフォームから抜きたいというときはどうしたらいいでしょうか。

認証フォームをwithAuthenticator()メソッドで導入した場合は、楽な代わりに細かいカスタマイズが難しいという側面があります。
実は、withAuthenticator()というのは、AmplifyAuthenticatorコンポーネントをラップしたものです。なので、withAuthenticator(App)とするのは以下のように書くのと同じです。

App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
- import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react';
+ import { AmplifyAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react';

function App() {
  return (
    <div className="App">
+      <AmplifyAuthenticator>
        <header className="App-header">
          // (略)
       <AmplifySignOut />
        </header>
+      </AmplifyAuthenticator>
    </div>
  );
}

- export default withAuthenticator(App);
+ export default App;

このとき、AmplifyAuthenticatorコンポーネントのブロックの中で、カスタマイズした認証フォームコンポーネントを設置すれば、デフォルト表示からフォームを変えることができます。

例えば、SignUpフォームを「メールアドレス」「パスワード」だけにするときは、Sign Upフォームを作るAmplifySignUpコンポーネントに適切なPropsを追加してやります。

App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';
- import { withAuthenticator, AmplifySignOut } from '@aws-amplify/ui-react';
+ import { AmplifyAuthenticator, AmplifySignOut, AmplifySignUp } from '@aws-amplify/ui-react';

function App() {
  return (
    <div className="App">
      <AmplifyAuthenticator>
+      <AmplifySignUp
+       slot="sign-up"
+       usernameAlias="email"
+       formFields={[
+         {
+           type: "email",
+           required: true,
+         },
+         {
+           type: "password",
+           required: false,
+         },
+       ]} 
+      />
        <header className="App-header">
          // (略)
       <AmplifySignOut />
        </header>
      </AmplifyAuthenticator>
    </div>
  );
}

export default App;

こうすることで、AmplifyAuthenticatorコンポーネントのデフォルト表示が上書きされて、自分で書いたAmplifySignUpコンポーネントが使われます。

どんなコンポーネントが存在して、そんなPropsがあるのかについては、以下の公式ドキュメントを参照ください。
参考:公式Doc:Amplify Doc AUTHENTICATION Authenticator #Component

ユーザープールで得られる実態を確認

ここで、ブラウザのローカルストレージを確認してみます。
スクリーンショット 2020-09-28 16.38.32.png

  • idToken: ユーザープールに格納されている情報を確認するのに必要なJWTトークン
  • accessToken: ユーザープールの情報の更新のために必要なJWTトークン
  • refreshToken: idTokenやaccessTokenの有効期限が切れた時に、新しいものを取得するために必要なトークン
  • LastAuthUser: 現在ログインしている(=最後にログインした)ユーザーの、ユーザープール上でのID
  • userData: ユーザープールに登録されている情報

参考:Cognitoのサインイン時に取得できる、IDトークン・アクセストークン・更新トークンを理解する

Amplifyのコードを使っての認証に成功した暁には、これらの重要データが自動でローカルストレージに格納されています。Cognitoが用意したUIを使用した場合とは違い、自力でのトークン保存処理の実装が必要ありません。便利です。

また、idTokenやaccessTokenの有効期限が切れた場合(デフォルトで1時間)、refreshTokenというものを使ってトークンの更新が必要なのですが、それもAmplifyを使っている場合、開発者が意識しなくても自動で更新が行われています。これもAmplifyを使用するメリットの一つです。

これらの「Amplifyが裏でうまいことやってくれている仕組み」については、以下の記事で非常に詳しく説明されていますので、気になる方はご覧ください。
参考:AmplifyでCognitoのHosted UIを利用した認証を最低限の実装で動かしてみて動作を理解する

Cognitoで認証した人だけが叩けるAPIを作って連携する

Cognito認証をするアプリのバックエンドで使うAPIは、認証済みの人だけが叩けるようにしないと困ります。
そのため、「Cognitoでのユーザー認証が適切になされている人のみが叩けるAPI」を作ります。

Lambda関数を作る

API GatewayからGETリクエストを受け取ったら、Secret API!というbodyを含んだjsonを返す関数をLambdaで作ります。

package main

import (
    "encoding/json"

    "github.com/aws/aws-lambda-go/events"
    "github.com/aws/aws-lambda-go/lambda"
)

type Response struct {
    Body string `body`
}

func handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
    res := Response{Body: "Secret API!"}
    jsonBytes, _ := json.Marshal(res)

    return events.APIGatewayProxyResponse{
        Body:       string(jsonBytes),
        Headers:    map[string]string{"Access-Control-Allow-Origin": "*"},
        StatusCode: 200,
    }, nil
}

func main() {
    lambda.Start(handler)
}

コードについての解説は、以下の過去記事をご覧ください。
AWS Lambda+API Gateway+DynamoDBでCRUD APIを作るのをGolangでやってみた

注:Headersの{"Access-Control-Allow-Origin": "*"}は、CORSを有効にするために必要になります。

API Gatewayの設定

GETリクエストとLambda関数の連携

API Gatewayで、GETリクエストで上で作ったLambda関数を呼び出すように設定します(やり方は上記同様に、過去記事を参照ください)。
AWS Lambda+API Gateway+DynamoDBでCRUD APIを作るのをGolangでやってみた

オーソライザの設定

ここで、APIのオーソライザの設定を行います。
スクリーンショット 2020-09-23 15.56.28.png
以下の通りに設定して作成します。

  • 名前: 任意の名前
  • タイプ: Cognitoを利用
  • Cognitoユーザープール: 先ほど作ったユーザープールの名前を選択
  • トークンのソース: Authrizationを入力

こうすることで、HTTPリクエストのヘッダにAuthrization=IDトークンをつけているものだけがこのAPIを叩けるようになります。
(Authrizationに有効なIDトークンが付いている=Cognitoでの認証済みとAPI Gatewayの方で判断するようになります)

CORSの設定

localhostから、別ドメイン(独自ドメインを割り当てないなら通常https://[文字列].execute-api.ap-northeast-1.amazonaws.comとなる)であるAPIを叩くので、CORS対策が必要になります。

アクションから、「CROSの有効化」を選択します。
スクリーンショット 2020-09-28 23.04.46.png
この設定のままで、CORSを有効化します。

参考:【AWS】これだけ見れば理解できるCognito〜認証機能つきサーバレスアーキテクチャの作成〜

APIのデプロイ→テスト

この状態でデプロイしたAPIをターミナルからcurlで叩くと、権限なしエラーが返ってきます。
(デプロイの仕方は過去記事参照→AWS Lambda+API Gateway+DynamoDBでCRUD APIを作るのをGolangでやってみた)

$ curl [APIURL]
{"message":"Unauthorized"}

これで、Cognitoの認証なしにはAPIが叩けなくなっているということが確認できました。

フロントエンドでAPIを呼び出す

Cognitoで認証して閲覧しているアプリから、このAPIが叩けるかどうかをテストしてみましょう。
Cognito周りのメソッドだけでなく、APIを叩くためのメソッドもAmplifyに存在するので、それを利用します。
参考:Amplify公式Doc API(REST)

まずは、index.js内のAmplify.configure()メソッドで、アプリ内で使うAPIを指定します。

index.js
Amplify.configure({
  Auth: {
      // (略)
  },
+ API: {
+   endpoints: [
+     {
+         name: "TestAPI",
+         endpoint: "APIのエンドポイントURL",
+         region: "APIが公開されているリージョン",
+     }
+   ]
+ }
});

次に、App.jsを書き換えて、APIから受け取った文字列をアラートで表示するボタンを設置します。

App.js
function App() {
  return (
    <div className="App">
      <AmplifyAuthenticator>
      // (略)
      <header className="App-header">
        // (略)
+        <div class='container'>
+         <button id='button' onClick={() => showSecret()}>Click Me!</button>
+        </div>
        // (略)
      </header>
      </AmplifyAuthenticator>
    </div>
  );
}

ボタンをクリックした時に実行される関数showSecret()も、以下のようにApp.js内に実装します。

App.js
import { Auth, API } from 'aws-amplify';

const showSecret = async function() {
    const apiName = 'TestAPI';  // index.jsで指定したnameと同じ名前にする
    const path = ''; 
    const myInit = { 
      headers: { 
        Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}`,
      },
    };

    API.get(apiName, path, myInit)
    .then(response => {
      console.log(response);
      alert(response.Body);
    })
    .catch(err => {
      console.log(err);
      alert(err);
    });

  };

Authorizationヘッダに、IDトークンを付与するように設定しています。

この状態でアプリを動かして、新たに作成したボタンを押すことで、APIからのレスポンスをアラートに表示させることができます。

IDプールの作成

次に、ユーザープールに続いて、Cognitoの別機能であるIDプールを使っていきます。

なぜIDプールを作成するのか

ユーザープールだけでもログインフォームが作れたのに、どうしてIDプールも作るの?ということを説明します。

Cognitoのユーザープールで行えるのは「認証」のみで「認可」は行うことができません。
例えば、「このリクエストを送っているのは、メールアドレスhttp://example.comで登録したAさんだな」ということを保証することはできても、「そのAさんにDBアクセスをやらせていいよ」と許可することはできないのです。

なので、認証したユーザーにDynamoDB等の「AWSリソースにアクセスさせるために」「IAMロールを割り当てる」という「認可」の作業を行うためには、IDプールが必要になるのです。
(逆に言えば、IAMロールを絡ませる必要が全くないアプリケーションの場合、ユーザープールだけで事足ります。)

作成手順

スクリーンショット 2020-09-28 21.34.03.png
IDプール名に好きな名前をつけます。
認証されていないIDへのアクセス・認証フローの設定にはどちらにもチェックはつけずに、「認証プロバイダー」のタブを開きます。

スクリーンショット 2020-09-21 1.18.11.png
ここで、先ほど作ったユーザープールIDとアプリクライアントIDを入力して、次に進みます。

スクリーンショット 2020-09-28 21.35.03.png
ここで、認証されたユーザー・認証されなかったユーザーにどんなロールを与えるのかを設定する画面です。今回はそれぞれに対して「Auth_Role」「UnAuth_Role」を与えるように新しくロールを新規作成しました。

ここまでの設定が済んだら、IDプールの作成は完了です。
作成完了したら、「IDプールの編集」タブから、プールのIDを確認して控えておきましょう。

Reactアプリとの連携

IDプールを作成できたら、それを先ほどのReactアプリで使えるようにしましょう。

index.js内のAmplify.configure()メソッドの中身を以下のように編集します。

index.js
Amplify.configure({
   Auth: {
       region: "Cognitoユーザープールを作ったリージョン",
       userPoolId: "作成したユーザープールID",
       userPoolWebClientId: "作成したアプリクライアントID"
+      identityPoolId: "(先ほど控えた)IDプールのプールID",
   }
});

この状態でアプリを起動することで、ログインユーザー(ユーザープールのユーザー1人)ごとに、IDプールのIDが割り振られる&ログインユーザーが持つIAMロールが与えられるようになります。

IDプールから得られるものの実態を確認する

「IDプールのIDが与えられる」「IAMロールが与えられる」といっても、アプリの見た目上は何も変わらないので、「どこがどうなったの??」と思う方もいるでしょう。
ここからは、IDプールと連携したことで何を得られたのか、実態を確認してみましょう。

まずはコンソールでものを確認

App.jsの中でcurrentCredentials()というメソッドを実行して、結果をみてみましょう。

App.js
// (略)
import { Auth } from 'aws-amplify';

function App() {
+  Auth.currentCredentials().then(console.log);
   // (略)
}

export default App;

この状態でアプリを起動すると、コンソール上でcurrentCredentials()で得られたデータを確認できます。
スクリーンショット 2020-09-28 19.48.09.png
補足:このcurrentCredentials()メソッドは、IDプール未連携時にはエラーが返ってきます。

ここで得られる「accessKeyId」と「secretAccessKey」が、IDプールから得ることができる"AWS Security Credentials"というものの実態です。
また今回の場合、Security Credentialsの中でも"AWS Temporary Security Credentials"というものが利用されます。なので「sessionToken」も重要な役割を果たします。

"AWS Security Credentials"とは?

「accessKeyId」と「secretAccessKey」の組み合わせのことです。

AWSでは、正しいアクセスキーIDとシークレットアクセスキー(=正しいAWS Security Credentials)を持っていることで、特定のAWSアカウントのあるIAMロールを持ったユーザーであると判定しています。

わかりやすい例としては、普段ターミナルでAWS CLIを叩く方だと、~/.aws/credentialsの設定ファイルに普段使用するアクセスキーIDとシークレットアクセスキーを保存しているかと思います。これは、ターミナルからコマンドを実行している人が、正当なIAM UserかどうかをAWS Security Credentialsで判断している例です。

AWS CLIでのAWS Security Credentialsは、ユーザーがAWSコンソールで更新作業を行わない限りずっと有効であり、いわゆるPermanentなものです。
それに対して、今回のような「ユーザーがアプリにログインしている間だけ使える」ようなものを"AWS Temporary Security Credentials"と呼びます。

一般的にPermanentな方のAWS Security CredentialsはIAM Userに、Temporaryな方のAWS Security CredentialsはIAM Roleに紐づいていることが多いです。

AWS Security Credentialsの使い方

ここからは、Security Credentialsを我々はどのように使うのかを見ていきます。

まずは、「accessKeyId」と「secretAccessKey」を使って(=Security Credentialsを使って)利用するAWSサービスというのは、たいていREST APIになっています(AWS APIという)。
AWS APIはリクエストがあるたびに、「このリクエスト主が正当な権限の持ち主かどうか?」というのをリクエスト内容から判断する必要があります。

AWS APIが権限を確認できるように、ユーザーはhttpリクエストヘッダのAuthorizationフィールドに「Signature V4」という方式の署名を付与する必要があります。
その「Signature V4」署名の生成に必要なのが「accessKeyId」と「secretAccessKey」です。

そして、AWS Temporary Security Credentialsを使用する場合は、「権限が期限切れじゃないかどうか」ということを判断するために「sessionID」が使われます(リクエストヘッダのX-Amz-Security-Tokenフィールドに埋め込む)。

まとめ

今回の場合、IDプールから得られるSecurity Credentialsを利用することで、認証されたアプリの利用者がIAMロールを使えるようになるのです。

参考:【AWS】AWS APIの認証・認可の仕組みを理解する【Signature V4】
参考:【Amplify】APIのAuthorization方式「AWS_IAM」を理解する#1 ~解説編~【AWS】

ユーザープールのグループを使ってユーザーごとに権限を分ける

IDプールと連携することで、「認証済みユーザーにはIAMロール"Auth_Role"」「認証されていないユーザーにはIAMロール"UnAuth_Role"」が渡されるように設定することができました。
しかし、認証済みユーザーに割り当てられるIAMロールが1つというのは少々窮屈です。例えば、認証済みユーザーの中でも、「一般ユーザー」と「管理者ユーザー」で違うロールを与えたいというケースはよくあるものでしょう。

今回は、Cognitoユーザープールの「ユーザーグループ」という機能を用いることで、認証済みユーザーにそれぞれ適切なロールを分けて与えられるように設定し直してみようと思います。

参考:AWS CLIで動かして学ぶCognito IDプールを利用したAWSの一時クレデンシャルキー発行

ユーザーグループの作成

スクリーンショット 2020-09-28 22.04.06.png
「ユーザーとグループ」からグループの作成を選択します。すると、以下のようなグループ作成画面が現れます。
スクリーンショット 2020-09-28 22.04.20.png
ここで任意のグループ名をつけます。まずは管理者ユーザー用のグループを作るため、それっぽい名前をつけましょう。
IAMロールのところで、新しく「AdminAuth_Role」という名前(ポリシーはAuth_Roleと一旦同じものにしておく)を作成し、それを選択します。

同様に、一般ユーザー用のグループ(IAMロールはAuth_Role)も作成しておきます。

ユーザーをグループに追加

ユーザーグループを作成したら、ユーザーをそこに所属させます。

ユーザー一覧からユーザーを選択して、「グループに追加」を選択することで可能です。
スクリーンショット 2020-09-28 22.05.11.png

IDプールの設定

先ほど作ったIDプールの管理画面から、「IDプールの編集」を開きます。

編集画面の中から、「認証プロバイダー」というタブを開きます。
スクリーンショット 2020-09-28 20.06.26.png
認証されたロールの選択というところで、「トークンからロールを選択する」と設定します。

これで、ユーザーグループごとに設定されたロールを与えるように設定されました。

APIのアクセス許可をIAMロールで行う

せっかくユーザーごとに違うロールを与えられるようになったので、何かそれを活かした仕組みを作ってみたくなります。

ここでは、管理者ユーザーのロールを持った人にだけ叩けるAPIを作ってみようと思います。

ロールの設定

管理者ユーザーのロールポリシー(AdminAuth_Role)を以下のように編集します。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*",
+               "execute-api:Invoke"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

"execute-api:Invoke"を追加することで、API Gatewayで公開されているAPIを叩くための権限を付けています。

API Gatewayでの設定

API Gatewayで、先ほど作ったAPIのGETメソッドリクエストの設定欄を開きます。
スクリーンショット 2020-09-28 20.33.31.png
「認可」のところを「AWS_IAM」に設定します。
こうすることで、"execute-api:Invoke"のポリシーがついたロールを持ったユーザー以外のリクエストを弾くようにできます。

コードの準備

前述した通り、ユーザーがIAMロールを持っていることを伝えるためには、適切なヘッダをつけたhttpリクエストを送る必要があります。
しかし、Amplifyを使えばこの面倒なヘッダ設定を全部自動でやってもらえます。

まずはコードを直します。
APIの認証が「Cognitoユーザープールでの認証の有無」ではなくなったため、AuthorizationにIDトークンをつける必要は無くなりました。なので、その記述を削除します。

App.js
- const showSecret = async function() {
+ const showSecret = function() {

    const apiName = 'TestAPI';  // index.jsで指定したnameと同じ名前にする
    const path = ''; 
-   const myInit = { 
-      headers: { 
-        Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}`,
-      },
-   };


    // getメソッドを叩いている
-    API.get(apiName, path, myInit)
+    API.get(apiName, path)
      .then(response => {
        alert(response.data);
      })
      .catch(err => {
        alert(err);
      });

  };

コード上では、AWS APIを利用するためのSignature V4署名やsessionID付与を行なっていません。
しかし、AmplifyのAPI.get()メソッドを使うことで、それらの前処理を全てAmplifyに任せるようになっているので、上のコードで問題なく動きます。

実際に、上記のコードでAPIを叩いたときのhttpリクエストをディベロッパーツールで見てみると、確かにAuthorizationヘッダにSignature V4で生成した署名を、X-Amz-Security-Tokenの部分にsessionTokenが自動で埋め込まれている様子が確認できます。
スクリーンショット 2020-09-28 22.30.06.png

補足:Signature V4での署名を付与する際のAuthorizationヘッダの仕様については、次のAWS公式ドキュメントを参照ください。→タスク 4: HTTP リクエストに署名を追加する

まとめ

本記事の内容はこれで全てです。

Cognitoを触ることを通して、Cognitoの使い方だけでなく、一般的な・AWSでの認証認可の知識も深まります。
ここで行なったことは基本的なことばかりだと思うので、あとは公式ドキュメントを読みながら色々いじってみてください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerのlogをAWS CloudWatchに紐付けしVSCodeでリアルタイムに監視する

はじめに

AWS EC2内のDockerコンテナ上で動くサービス(docker-composeで管理)のログをCloudWatchLogsに投げ、VSCodeでリアルタイム監視ができるまでの手順をまとめる。

  • Dockerのlog driverについて
  • CloudWatchLogsへの登録
  • VSCodeのAWS拡張についてとログの閲覧

Dockerのlog driverについて

Dockerの標準機能で/var/lib/docker/containers/にコンテナ内のログを出力するようになっている。そのため、Linuxのlogrotate等を使用する必要はない。
docker-composeでの実装例は以下の通り。

docker-compose.yml
version: "3"
services:
  hello:
    image: "busybox:latest"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

CloudWatchLogsへの登録

上記で解説した、Dockerのlog driverを使用してログの出力先をCloudWatchLogsに変更することができる。
docker-composeでの実装例は以下の通り。

docker-compose.yml
version: '3'
services:
  app:
    build:
      context: .
      dockerfile: prod/Dockerfile
    restart: always
    expose:
      - 8000
    environment:
      - TZ=Asia/Tokyo
    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: log-group-name
        tag: "{{.ImageName}}.{{.Name}}.{{.FullID}}"
  nginx:
    build:
      context: .
      dockerfile: nginx/Dockerfile
    restart: always
    ports:
      - 8080:80
    depends_on:
      - app
    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: log-group-name
        tag: "{{.ImageName}}.{{.Name}}.{{.FullID}}"

docker-composeではサービスごとにlog driverの設定を記述する。
awslogs-groupにはマネジメントコンソールで作成したロググループ名を書く。

VSCodeのAWS拡張についてとログの閲覧

VSCodeの拡張機能の検索窓にawsと打ち込めば、下の画像のようなAWS Toolkitが表示されるので、それをインストールする。
スクリーンショット 2020-09-29 17.43.59.png
リージョンとアクセスキーを指示に従って設定する。
スクリーンショット 2020-09-29 17.47.32.png
上の画像のCloudWatch Logsのタブを開くとロググループが表示されるので、右クリックしview log streamを選択する。
Output Colorizer拡張をインストールしておくと、ログをハイライト付きで見ることができるので入れるとよい。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerのlogをAWS CloudWatchに紐付けしリアルタイムに監視する。

はじめに

AWS EC2内のDockerコンテナ上で動くサービス(docker-composeで管理)のログをCloudWatchLogsに投げ、VSCodeでリアルタイム監視ができるまでの手順をまとめる。

  • Dockerのlog driverについて
  • CloudWatchLogsへの登録
  • VSCodeのAWS拡張についてとログの閲覧

Dockerのlog driverについて

Dockerの標準機能で/var/lib/docker/containers/にコンテナ内のログを出力するようになっている。そのため、Linuxのlogrotate等を使用する必要はない。
docker-composeでの実装例は以下の通り。

docker-compose.yml
version: "3"
services:
  hello:
    image: "busybox:latest"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

CloudWatchLogsへの登録

上記で解説した、Dockerのlog driverを使用してログの出力先をCloudWatchLogsに変更することができる。
docker-composeでの実装例は以下の通り。

docker-compose.yml
version: '3'
services:
  app:
    build:
      context: .
      dockerfile: prod/Dockerfile
    restart: always
    expose:
      - 8000
    environment:
      - TZ=Asia/Tokyo
    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: log-group-name
        tag: "{{.ImageName}}.{{.Name}}.{{.FullID}}"
  nginx:
    build:
      context: .
      dockerfile: nginx/Dockerfile
    restart: always
    ports:
      - 8080:80
    depends_on:
      - app
    logging:
      driver: awslogs
      options:
        awslogs-region: ap-northeast-1
        awslogs-group: log-group-name
        tag: "{{.ImageName}}.{{.Name}}.{{.FullID}}"

docker-composeではサービスごとにlog driverの設定を記述する。
awslogs-groupにはマネジメントコンソールで作成したロググループ名を書く。

VSCodeのAWS拡張についてとログの閲覧

VSCodeの拡張機能の検索窓にawsと打ち込めば、下の画像のようなAWS Toolkitが表示されるので、それをインストールする。
スクリーンショット 2020-09-29 17.43.59.png
リージョンとアクセスキーを指示に従って設定する。
スクリーンショット 2020-09-29 17.47.32.png
上の画像のCloudWatch Logsのタブを開くとロググループが表示されるので、右クリックしview log streamを選択する。
Output Colorizer拡張をインストールしておくと、ログをハイライト付きで見ることができるので入れるとよい。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CloudWatchEventsのインプットでBatchのジョブ定義をオーバライドする

CloudWathEventsru-ルール で AWS Batchでバッチ処理を実行する構成では、CloudWatchEventのルール設定の「input」パラメータで、入力パラメータを設定できます。
さらに、入力パラメータ以外に、ジョブ定義のvcpuや環境変数などのパラメータのオーバーライドも可能ですのでその方法を紹介したいと思います。

TL:DR;

  • CloudWatchEventルールのターゲットInputContainerOverridesを追加する
    • ex)パラメータEnvの定義および、ジョブ定義のVcpuを4,メモリを2048に上書きする場合のInput
      • {"Parameters" : {"Env":"Prod" },"ContainerOverrides": { "Vcpus":4, "Memory":2048}}

利用用途

  • 一つのジョブ定義を使いまわしたいが、条件によっては環境変数やvcpuなどの一部のパラメータを変更したい場合
    • 一部の環境変数の変更やvcpuの変更程度あればジョブ定義を複数用意せずに済む

設定方法

コンソール

  • CloudWatchEventルール > ターゲット > 入力の設定 > 定数(JSONテキスト)
    • ここにContainerOverridesを記述する
    • 画像ではvcpuを4、memoryを2048に上書きする例 ルールのinput項目.png

aws-cli

  • コマンド
    • aws events put-targets --rule [ルール名] --cli-input-json file://target.json
  • target.json
target.json
{
  "Targets": [
    {
      "Id": "1",
      "Arn": "jobQueueArn",
      "RoleArn": "roleArn",
      "Input": "{\"Parameters\" : {\"Env\":\"Prod\" },\"ContainerOverrides\": { \"Vcpus\":4, \"Memory\":2048}}",
      "BatchParameters": {
        "JobDefinition": "jobDefinitionArn",
        "JobName": "jobName"
      }
    }
  ]
}

設定できるプロパティ

以下のJSONのプロパティであればすべてオーバーライドしてBatchを実行することができると思います

  • Command
    • ジョブ実行時のコマンド
  • Environment
    • 環境変数
  • InstanceType
    • マルチノード並列ジョブに使用するインスタンスタイプ
  • Memory
    • ジョブに使用するメモリ
  • Vcpus
    • ジョブに使用するvcpu数
  • ResourceRequirements
    • GPUの利用設定

{
  "ContainerOverrides": {
    "Command": "echo 'hello world'",
    "Environment": [
      {
        "Name": "env",
        "Value": "prod"
      }
    ],
    "InstanceType": "m5.large",
    "Memory": 2048,
    "Vcpus": 2,
    "ResourceRequirements": {
      "Type": "GPU",
      "Value": "2"
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【備忘録】EC2上のMySQLにMySQL Workbenchを接続する手順

MySQL Workbenchは便利、されどわかりにくい。
EC2へのSSH接続について、次やるときには忘れてるかもしれないのでメモします。

筆者の環境

  • Amazon Linux2
  • MySQL
  • RDSは不使用

手順

MySQL Workbenchを起動、スパナマークor+マークを押して設定画面を開く。

各設定内容

  • Connection Name:設定につける名前というだけなので、適当にわかりやすい名前をつける。
  • Connection Method:Standard TCP/IP over SSHを選択
  • SSH Hostname:<ElasticIP>:22という書式で、EC2インスタンスのIPアドレスと、SSHのポート番号を入力(セキュリティグループのインバウンドルールを参照)
  • SSH Username:ec2-user
  • SSH Key File:右側のを押して、秘密鍵を選択するor秘密鍵への絶対パスを直接書く
  • MySQL Hostname:localhost
  • MySQL Server Port:3306
  • Username:設定したユーザーネーム
  • Password:設定したパスワード

これで接続できました。
未来の自分もそうであってほしい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS ALBリスナールール変更

リスナールールでターゲットグループを重み付け指定するコマンド

aws elbv2 modify-rule \
--rule-arn "<arn:ListenerRule>" \
--actions \
'[{
   "Type": "forward",
   "Order": 1,
   "ForwardConfig": {
    "TargetGroups": [
       { "Weight": 1, "TargetGroupArn": "<arn:TargetGroup>" },
       { "Weight": 0, "TargetGroupArn": "<arn:TargetGroup>" }
     ]
   }
}]'

aws:modify-rule

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VirtaulBox上のOpenAMによるSAMLでのAWSへのログイン

第一弾として、こちらの記事で、macOS上のVirtualBoxでOpenAMをインストールして動くところまでを記載しました。この記事では、このOpenAMをSAMLのIdPとして用い、AWSマネジメントコンソールにログインできるようにするところまでを記述します。
federation-openam-aws.png

この構成にした背景を少々。

  • 多くの企業では、ハイブリッドクラウド環境が採用されており、オンプレミスにも従来から利用している多くのアプリケーションが残っている。
  • この状況では、それらのアプリケーションへのログイン認証のための統合ID環境もオンプレミスに残っているケースが多いと思われる(少なくとも私のいる会社ではそう)ため、今回のOpenAMはクラウド上に配置したくなかった。
  • 大企業であれば、その統合ID環境をクラウドでもそのまま利用できるように拡張したい要求があり、フェデレーションやシングル・サイン・オンを検討する必要がある。
  • OpenAMをクラウド上ではなく、macOSの中のVirtualBoxでひっそりと動かすとなると、ID情報を提供するIdP(Identity Provider: 今回のOpenAM)と、それに依存し、アプリケーション・サービスを提供するSP(Service Provider: 今回のAWS)は相互通信できない。
  • フェデレーションのプロトコルとしては、OpenID ConnectとSAMLがメジャーであるが、SAMLは大企業での使用がより一般的で、IdPとSPが相互通信できる必要がない。
    • OpenID Connectでは、Authentication Code FlowとImplicit Code Flowとの2つのフローがあり、前者では、IdPとSPの相互通信が必要。

参考

OpenAMの初期設定

こちらの最後から開始して、OpenAMの初期設定を行います。
「新しい設定の作成」をクリック。
openam-domain2.png
license agreementにチェックを入れて、「次へ」をクリック。
openam-configtool.png
デフォルトの管理ユーザとなるamAdminのパスワードを入力して設定し、「次へ」をクリック。
openam-customconfig.png
「Cookieドメイン」を「samlidp.kawajun.local」(OpenAMホストのFQDN名)とし、「次へ」をクリック。
openam-serverconfig.png
「ポート」、「管理者ポート」、「JNXポート」が以下のようになっていなければそのように入力し、「次へ」をクリック。
スクリーンショット 2020-09-15 0.31.25.png
「OpenAMのユーザーデータストア」にチェックを入れて、「次へ」をクリック。
openam-userdatastore.png
何もいじらず、「次へ」をクリック。
openam-siteconfig.png
デフォルトのポリシーエージェントユーザのパスワードを入力、設定して、「次へ」をクリック。
openam-defaultpolicyagentuser.png
何もいじらず、「設定の作成」をクリック。
openam-configtooloverviewanddetail.png
「お待ちください... 設定しています...」がしばらく表示され、設定が進む。
openam-waiting.png
成功すると、「設定が完了しました」と表示されるので、「ログインに進む」をクリック。もし、ここで失敗した場合、/home/tomcat配下のopenamをリネーム、必要に応じて、サーバのリスタートを試みてからやり直す。
openam-configcompleted.png

OpenAMによるSAML IdPの構成

ここからいよいよOpenAM上にSAMLの設定を行なっていきます。上でセットアップしたデフォルトアドミン(amAdmin)とそのパスワードにより、OpenAMにログインします。TLS/SSLによる暗号化通信をしていないため、「この接続は安全ではありません」の警告が出ますが、検証目的のため、ここでは無視します。
openam-login.png
ログインに成功すると、以下のような画面になるので、SAMLのRealm(レルム)の設定として、"Top Level Realm"をクリックします。
openam-realms.png
今回、フェデレーション・プロトコルとしてSAMLを使うので、"Create SAMLv2 Provider"をクリックします。
openam--realmoverview.png
まず、OpenAMホストをSAMLにおけるIdentityの提供や認証を請け負うIdP(Identity Provider)として機能させる設定を行うため、"Create Hosted Identity Provider"をクリックします。
openam-realmoverview2.png
ここからIdPとしての設定に入ります。「メタデータ」中の「名前」は一旦このまま使い、「署名鍵」はOpenAMに用意されている自己署名を用いた"test"を選択します。「トラストサークル」はSAMLによるフェデレーションを用いる相互に信頼されたIdPとそれに依存してサービスを提供するSP(Service Provider)からなるグループで、今回、AWS以外のクラウドサービスへのログインも後々、やってみたいので、「新しいトラストサークル」は一般的な名前として、"public_cloud"としておきます。最後に「設定」をクリックして次に進みます。
openam-samlidp.png
うまく行くと、「アイデンティティープロバイダが設定されました」「次に何を実行しますか?」と表示されるので、「サービスプロバイダを登録する」をクリックし、SPとなるAWSの登録に進みます。

OpenAMへのAWSのSPとしての登録

openam-idpcomplete.png
SAMLのIdPとして動くOpenAMに対して、SPとして動くAWSを登録するには、まず、AWSのSPとしてのメタデータが配置されているURLを与える必要があります。AWSマネジメントコンソールへのログインの場合、そのURLについては、こちらなどに説明がある通り、https://signin.aws.amazon.com/static/saml-metadata.xml ですので、これを「メタデータが配置されているURL」に入力します。その後、「設定」をクリックします。
openam-removesp.png
うまく行くと、リモートサービスプロバイダとして登録されるので、「了解」をクリックします。
openam-remotespcomplete.png
上部の"FEDERATION"をクリックします。
openam-federation.png
下記のような画面に変わり、IdP(ローカルのOpenAM)とSP(AWSマネージメントコンソール)が、public_cloudと言うトラストサークルに含まれてるように見えます。
openam-trustcircle.png
これで一旦、OpenAM側の作業を止めて、AWSマネジメントコンソール側の作業に入ります。

AWSへのOpenAMのIdPとしての認識

まず、個人のルートユーザでAWSにサインインします。
aws-signin.png
aws-password.png
サービスのメニューの中から"IAM"を探してクリックします。
aws-iam.png
左のメニューから「IDプロバイダー」をクリックします。
aws-idp.png
「プロバイダの作成」をクリックします。
aws-createidp.png
「プロバイダーのタイプを選択する」のプルダウンメニューから"SAML"を選びます。
aws-saml.png
次にIdP(OpenAM)のメタデータがファイルの形で必要となるため、macOSのコマンドラインからcurlコマンドを実行して、メタデータをファイル(ここでは"exportmetadata.jsp"に落とします。

macbookpro% curl http://samlidp.kawajun.local:8080/openam/saml2/jsp/exportmetadata.jsp > exportmetadata.jsp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  4617  100  4617    0     0   901k      0 --:--:-- --:--:-- --:--:--  901k

プロバイダ名に名前(ここでは"OpenAM"としました)を記入し、上記で生成されたファイルを「メタデータドキュメント」の「ファイルの選択」から選び、「次のステップ」をクリックします。
aws-metadata.png
うまく行ってるようなので「作成」をクリックします。
aws-createidpcomplete.png
うまく行ったようなので、次に「ロール」をクリックし、AWS上でのロールの定義に進みます。

AWSでのロールの定義

aws-role.png
次のような画面に移るので、「ロールの作成」をクリックします。
aws-createrole.png
SAML用のロールを準備するので、もちろん「SAML 2.0フェデレーション」をクリックします。
aws-createrole2.png
「SAMLプロバイダー」は上で決めた"OpenAM"をプルダウンメニューから選び、「プログラムによるアクセスとAWSマネジメントコンソールによるアクセスを許可する」にチェックを入れて、「次のステップ: アクセス権限」をクリックします。
aws-samlprovider.png
「ポリシー名」の中から"AdministratorAccess"にチェックを入れて、「次のステップ: タブ」をクリックします。(Alexaとか出てくるんですね。笑)
aws-policy.png
何もいじらず、「次のステップ: 確認」をクリックします。
aws-tag.png
「ロール名」は任意ですが、ここでは、"openam-user-role"を入れて、「ロールの作成」をクリックします。
aws-createrole3.png
どうやらうまく行ったっぽいですね。
aws-createrolecomplete.png
ここから再びOpenAMに戻って設定を続けます。

OpenAMにおけるアサーション設定

アサーション(Assersion: 表明)と呼ばれるSAML上の設定を行います。「エンティティープロバイダ」の中の"urn:amazon:webservices"をクリックします。
openam-trustcircle2.png
以下の画面に移るので、「表明処理」タブをクリックします。
openam-assersion.png
「属性マップ」の「新しい値」に以下の2つの値を入れて「追加」します。
- https://aws.amazon.com/SAML/Attributes/Role=employeeNumber
- https://aws.amazon.com/SAML/Attributes/RoleSessionName=uid
openam-attributemap.png
うまく行くと次のようなメッセージが出るので、「戻る」をクリックします。
openam-attributemapcomplete.png
さらに「アクセス制御」タブをクリックして、元の画面に戻ります。
openam-accesscontrol.png
再び"Top Level Realm"をクリックして中に入ります。ここからはOpenAM上にフェデレーションの対象となるユーザIDの作成に進みます。

OpenAMにおけるフェデレーション対象ユーザ作成

openam-realms2.png
左のメニューから"Subjects"をクリックします。
openam-subjects.png
新しくユーザを定義していくので、「新規」をクリックします。
openam-user.png
新規に作るユーザID(ここでは"kawajun")の情報として、必須フィールドの"ID"、「姓」、「フルネーム」、「パスワード」、「パスワード (確認)」を入力し、「了解」をクリックします。
openam-newuser.png
今、作成したユーザIDのフルネーム("Jun Kawa")をクリックします。
openam-user2.png
ここから先はユーザの属性情報を入力しますが、SPであるAWS側からロールに関する情報を入手する必要があるので、再びAWSマネジメントコンソールに行きます。

AWSからロールに関する情報を入手

先ほど作成したロールの名前"openam-user-role"をクリックします。
aws-openamuserrole.png
「ローカルARN」の値の右にあるアイコンをクリックし、値をクリップボードにコピーし、メモ帳などに一旦、ペーストします。その後、「信頼関係」タブをクリックします。
openam-localarn.png
以下の画面に移るので、「信頼されたエンティティ」の値をコピーし、これもメモ帳などにペーストします。
aws-trustedentity.png
次からは、OpenAMに再び戻り、AWSから得られた2つの値を使って、ユーザ属性を追加します。

OpenAM上にユーザ属性を追加

「社員番号」の欄に、AWSから得られた2つの値をカンマ","でつなげた値を入れます。私のケースだと、"arn:aws:iam::265806162838:role/openam-user-role,arn:aws:iam::265806162838:saml-provider/OpenAM"がその値となります。その後、「保存」をクリックします。
openam-empnumber.png
うまく更新されると以下のような画面に移ります。ここで、amAdminによる管理作業は終わるので、「ログアウト」、さらに"OK"をクリックします。
openam-usercomplete.png
openam-logout.png

SAMLフェデレーション利用によるログイン試行

上記で準備は整いましたので、SAMLフェデレーション利用によるログインを試してみます。このURL: http://samlidp.kawajun.local:8080/openam/saml2/jsp/idpSSOInit.jsp?metaAlias=/idp&spEntityID=urn:amazon:webservices にアクセスし、先ほど定義したユーザID(ここでは"kawajun")とそのパスワードを入力し、"LOG IN"ボタンをクリックします。
openam-federatedlogin.png
そうすると、AWSマネジメントコンソールに画面遷移し、OpenAM(IdP)上に定義したユーザID(kawajun)とそのパスワードで、AWSマネジメントコンソール(SP)にログインできます。アカウント情報のところに、OpenAMを用いて認証されたことが示す情報が見えています。
aws-federatedlogin.png

OpenAMダッシュボードにAWSへのリンク追加

ここまででOpenAM上に作ったユーザIDでAWSマネジメントコンソールにログインできることは確認できましたが、長いURLをエンドユーザに打たせるのはよろしくないので、OpenAMのダッシュボードに一発リンクを追加します。が、その作業中、Tomcatがエラーを吐いて先に進めなくなるので、こちらを参考におまじないを入れます。具体的には、ポート8080の属性の設定のところに、"relaxedPathChars"を追加定義し、Tomcatをリスタートします。

[root@samlidp ~]# cd /opt/tomcat/conf
[root@samlidp ~]# cp server.xml server.xml.org
[root@samlidp ~]# vi server.xml
    (中略)
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443"
               relaxedPathChars="[]|" relaxedQueryChars="[]|{}^\`"/>
    (中略)
[root@samlidp ~]# systemctl restart tomcat

ここから再びOpenAMにamAdminでログインし、"CONFIGURATION"タブに進みます。
openam-configuration.png
「グローバル」タブをクリックします。
openam-global.png
次に「グローバルプロパティー」の中から「ダッシュボード」を探してクリックします。
openam-dashboard.png
ダッシュボードに新たなインスタンスを作成するため、「新規...」をクリックします。
openam-instance.png
先に、AWSロゴのアイコンをネットから拾ってきて、所定のディレクトリに配置しておきます。

[root@samlidp vagrant_data]# cp awslogo.png /opt/tomcat/webapps/openam/XUI/images

以下のように「グローバル属性」の各フィールドの値を入力し、「追加」をクリックします。ここで上から3つ目の"Dashboard Login"に入れるURLは、上でAWSへのログインに用いたURL http://samlidp.kawajun.local:8080/openam/saml2/jsp/idpSSOInit.jsp?metaAlias=/idp&spEntityID=urn:amazon:webservices です。また、一番下はAWSロゴのイメージを入れたディレクトリですが、/opt/tomcat/webapps/openam/XUIからの相対パスで指定します。
openam-globalattribute.png
下記画面に移るので、「保存」をクリックします。
openam-dashboardcomplete.png
以下のメッセージが現れ、これでダッシュボードへの項目追加は完了です。さらに、上で作成したユーザのダッシュボードに、今、追加したAWSマネジメントコンソールが現れるようにします、「サービス設定へ戻る」をクリックします。
openam-dashboardcomplete2.png
「アクセス制御」をクリックします。
openam-accesscontrol2.png
再び、"Top Level Realm"の画面に戻るので、クリックします。
openam-toplevelrealm.png
左のメニューから"Subjects"をクリックします。
openam-subjects2.png
先に作成したユーザ名(ここでは"Kawa Jun")をクリックします。
openam-username.png
「サービス」タブをクリックします。
openam-userservice.png
「ダッシュボード」をクリックします。上のTomcatへのおまじないを入れない場合、ここでエラーを吐きます。
openam-edituser.png
「新しい値」に「AWSマネジメントコンソール」を入れて、「追加」をクリックし、「保存」をクリックします。
openam-userattribute.png
以下のような表示が出れば設定は完了です。
openam-dashboardusercomplete.png
ここでamAdminはログアウトし、作成したユーザID(ここでは"kawajun")でログインし直します。以下の画面にログインするので、"DASHBOARD"をクリックします。
ppenam-userlogin.png
AWSロゴとともに、「AWSマネジメントコンソール」へのリンクが表示されるので、そこをクリックします。
openam-awsdashboard.png
先ほどと同じく、AWSマネジメントコンソールにログインし、確かにOpenAMをIdPとするフェデレーションによるユーザでログインできていることが確認できます。
aws-federatedlogin2.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amplifyでcognito(ソーシャルIDP)使うときにデプロイがうまくいかないケース

cognitoでソーシャルIDP(Facebook, googleログイン等)を使ったログインを実装中にハマったのでメモ。

:skull: 問題

amplify consoleでビルドログを見ると下記のようなエラーが出る。

2020-09-28T14:15:05.671Z [WARNING]: - Fetching updates to backend environment: develop from the cloud.
2020-09-28T14:15:05.891Z [WARNING]: ✔ Successfully pulled backend environment develop from the cloud.
2020-09-28T14:15:05.915Z [WARNING]: ✖ There was an error initializing your environment.
2020-09-28T14:15:05.917Z [INFO]: Failed to pull the backend.
2020-09-28T14:15:05.919Z [INFO]: auth headless is missing the following inputParams facebookAppIdUserPool, facebookAppSecretUserPool, googleAppIdUserPool, googleAppSecretUserPool
2020-09-28T14:15:05.941Z [INFO]: Error: auth headless is missing the following inputParams facebookAppIdUserPool, facebookAppSecretUserPool, googleAppIdUserPool, googleAppSecretUserPool
                                 at updateConfigOnEnvInit (/root/.nvm/versions/node/v10.16.0/lib/node_modules/@aws-amplify/cli/node_modules/amplify-category-auth/src/provider-utils/awscloudformation/index.js:61:15)
                                 at /root/.nvm/versions/node/v10.16.0/lib/node_modules/@aws-amplify/cli/node_modules/amplify-category-auth/src/index.js:230:28
                                 at /root/.nvm/versions/node/v10.16.0/lib/node_modules/@aws-amplify/cli/node_modules/promise-sequential/index.js:16:18
                                 at process._tickCallback (internal/process/next_tick.js:68:7)
2020-09-28T14:15:05.957Z [ERROR]: !!! Build failed
2020-09-28T14:15:06.091Z [ERROR]: !!! Non-Zero Exit Code detected
2020-09-28T14:15:06.092Z [INFO]: # Starting environment caching...
2020-09-28T14:15:06.092Z [INFO]: # Environment caching completed

:zap: 原因

この辺が怪しそうなので調べたところ、amplify pushはしてるのでcognitoの方の設定ファイルでfacebookやgoogleの変数を保持しているのですが、どうやら 環境変数 にも設定しないといけないとビルドでこけるぽい。

2020-09-28T14:15:05.941Z [INFO]: Error: auth headless is missing the following inputParams facebookAppIdUserPool, facebookAppSecretUserPool, googleAppIdUserPool, googleAppSecretUserPool

:tada: 解決

使っているソーシャルIDPの環境変数を追加してビルドし直す。

AMPLIFY_AMAZON_CLIENT_ID
AMPLIFY_AMAZON_CLIENT_SECRET
AMPLIFY_FACEBOOK_CLIENT_ID
AMPLIFY_FACEBOOK_CLIENT_SECRET
AMPLIFY_GOOGLE_CLIENT_ID
AMPLIFY_GOOGLE_CLIENT_SECRET

Group.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSで複数アカウントを切り替えて使う(Swith Role)

AWSで運用を始めると、開発環境と本番環境でAWSアカウントを分けて利用することがあるかと思います。複数アカウントで作業を始めると切り替えが、かなり面倒になってきますよね。

そこで、AWSにはスイッチロールという仕組みがあるので、ちょっと試してみましょう。

スイッチロールとは

所有する異なるAWSアカウントでクロスアカウントアクセスを設定することで、あるアカウントからサインアウトして別アカウントへサインインする必要が無くなります。また、アカウントごとにIAMユーザーをを作成する必要がなくなります。

公式ドキュメントの "IAM チュートリアル: AWS アカウント間の IAM ロールを使用したアクセスの委任"で上記のように説明されています。

AWSクロスアカウント.png

この記事で行うこと

2つのAWSアカウントの間でクロスアカウントアクセスの設定をおこない、スイッチロールできるようにします。
具体的には、BアカウントでIAMロールを作成し、Aアカウントからスイッチロールできるようにします。

事前に決めておくこと

Bアカウントで作成するIAMロールへアクセス権限を設定するため、必要な権限を決めておくこと。

スイッチロールを作成する手順

それぞれのアカウントで以下の作業をおこないます。

Bアカウント(切り替え先のアカウント)で行う作業

  1. Bアカウントへログイン
  2. IAMのコンソールを開く
  3. IAMロールを作成する
  4. 作成されたロールを確認する
  5. Bアカウントからログアウトする

Aアカウント(切り替え元のアカウント)で行う作業

  1. Aアカウントへログインする
  2. 右上のメニューからロールの切り替え
  3. スイッチロールする情報を入力する
  4. ロールを切り替える

実際に作成してみましょう

Bアカウント(切り替え先のアカウント)へ設定する

Bアカウントへログイン

スクリーンショット 2020-09-29 8.44.59.png

IAMのコンソールを開く

スクリーンショット 2020-09-29 8.48.53.png

IAMロールを作成する

スクリーンショット 2020-09-29 8.50.34.png

信頼されたエンティティとして別のAWSアカウントで作成する

別のAWSアカウントを選び、アカウントIDへAアカウント(切り替え元となるアカウント)のID(12桁)を入力しましょう。

スクリーンショット 2020-09-29 8.55.12.png

付与するアクセス権限ポリシーを選ぶ

スイッチロールした際に許可するアクセス権限ポリシーを選択しましょう。

スクリーンショット 2020-09-29 8.56.56.png

タグを追加する

スクリーンショット 2020-09-29 8.57.38.png

ロール名などを設定する

ロール名を決めます。このロール名は、スイッチロールする時に指定するため、わかりやすい名前にしておくことをオススメします。ロールの説明は、IAMコンソールの一覧で表示されるため、目的を簡潔に記述しておくことをオススメします。
信頼されたエンティティポリシーへ設定した値が表示されていることを確認してください。

スクリーンショット 2020-09-29 8.58.34.png

作成されたロールを確認する

スクリーンショット 2020-09-29 9.06.34.png

Bアカウントからログアウトする

Aアカウント(切り替え元のアカウント)で切り替えてみる

Aアカウントへログインする

スクリーンショット 2020-09-29 9.23.05.png

右上のメニューからロールの切り替えをする

スクリーンショット 2020-09-29 9.23.29.png

スイッチロールする情報を入力する

アカウントには、Bアカウント(切り替え先のアカウント)のID(12桁)を入力しましょう。
ロールには、Bアカウントで作成したIAMロールのロール名を入力しましょう。
表示名は、適当な名称でOKです。切り替えた際に右上メニューに表示される名称になります。
色は、右上メニューへ表示された時の背景色になります。大事なスイッチ先の場合は、赤にするとか決めるとアカウントが複数になった場合でもわかりやすくなります。

スクリーンショット 2020-09-29 9.24.42.png

ロールを切り替える

ロールの切り替えでBアカウントへ切り替わったでしょうか?
切り替わると、右上メニューへ先程入力した表示名の名称が表示され、現在アクティブです。へBアカウントのIDとロール名が表示されるようになります。

スクリーンショット 2020-09-29 9.25.36.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BotkitでAWSのリソースを取得する

Botkitの簡単な使い方をAWSのAPIとTypeScriptを使って説明します。
今回はSlack連携はせず、簡単なWebエンドポイントを作って確認します。

サンプルリポジトリをおいて置くので、気になる人はCloneして使ってみてください ?

Botkitの始め方

まずはBotkitをTypeScriptで使えるようにします。

## ライブラリのインストール

$ yarn add botkit # yarnの代わりにnpm使ってもOKです
$ yarn add typescript
$ yarn add botbuilder-adapter-web
$ yarn add aws-sdk

## tsconfigの作成

$ npx tsc --init

tsconfigはこんな感じに修正します。
詳しくはこちらの記事を参照ください。
- https://qiita.com/ryokkkke/items/390647a7c26933940470

// tsconfig.json
{
  "compilerOptions": {
    "module": "esnext",
    "target": "esnext",
    "moduleResolution": "node"
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules"
  ]
}

package.jsonは以下のように修正します。

// package.json
{
  "scripts": {
    "start": "node dist/index.js", // jsを実行
    "build": "npx tsc --outDir ./dist ./src/index.ts" // tsファイルをjsにトランスパイル
  },
  "dependencies": {
    // 割愛
  }
}

TypeScriptでBotkitのサンプルを作成

srcディレクトリとindex.tsを作成

$ mkdir src
$ touch src/index.ts

サンプルコードを作成します。
参考URLはこちら
- https://botkit.ai/docs/v4/platforms/web.html

// src/index.ts
import { Botkit } from "botkit";
import { WebAdapter } from "botbuilder-adapter-web";

const adapter = new WebAdapter; // botbuilder-adapter-webからadapterを作成

const controller = new Botkit({
  adapter: adapter, // 作成したadapterをBotkitの初期化時に渡す
});

controller.on("test", async (bot, msg) => { // イベントのフィルター、`type: test`で返信
  await bot.reply(msg, "I received an text: " + msg.text); // 受け取った`text:`の内容を返す
});

Botkitは本体とAdapterでライブラリが分かれています。
Botkitを初期化する際に、slackやwebなどのアダプタを渡してあげることで機能追加が可能です。

ここで、簡単なテストを実行

# jsファイルを生成
$ yarn build

# botkitを実行、待ち状態になる
$ yarn start

# 別のターミナル or シェルを起動
# Botkitにリクエストを送る
$ curl -X POST \
    -H "Content-Type: application/json" \
    -d '{
          "type": "test",
          "text": "ok bokujyou",
          "channel": "websocket",
          "user": "user"
        }' \
    http://localhost:3000/api/messages

# 以下のレスポンスが返ってくればOK
[{"type":"message","text":"I received an event: ok bokujyou"}]

AWSリソースをBotkitで呼び出す

今回は簡単にCodeBuildのプロジェクト一覧を呼び出してみます。

ソースの作成

$ mkdir -p src/aws
$ touch src/aws/CodeBuild.ts

TypeScriptのコード
(本当はreturnの処理で、返り値とかinterfaceとか書きたいけど今回は省略)

// src/aws/CodeBuild.ts
import * as AWS from "aws-sdk"; // ライブラリをインポート

const codebuild = () => { // classのインスタンスを作成
  return new AWS.CodeBuild; // classを生成
};

// CodeBuildのプロジェクト一覧をAWSから取得して返す関数
const CodeBuildListProjects = () => { // 何も受け取らないLambda処理
  return codebuild().listProjects().promise(); // 非同期処理(コールバック)
};

export { CodeBuildListProjects } // 別ファイルでimportするためにexportしておく
// src/index.ts
import { Botkit } from "botkit";
import { WebAdapter } from "botbuilder-adapter-web";
import { CodeBuildListProjects } from "./aws/CodeBuild"; // import

const adapter = new WebAdapter;

const controller = new Botkit({
  adapter: adapter,
});

controller.on("test", async (bot, msg) => {
  await bot.reply(msg, "I received an event: " + msg.text);
});

// 追加
controller.on("codebuild", async (bot, msg) => {
  // CodeBuildListProjectsを実行
  await bot.reply(msg, await CodeBuildListProjects()); // 非同期処理なのでawaitを忘れない
});

確認してみる

$ yarn build
$ yarn start

# AWSの認証鍵を環境変数に設定

$ export AWS_ACCESS_KEY_ID=<>
$ export AWS_SECRET_ACCESS_KEY=<>
$ export AWS_REGION=<>

## もしくは

$ export AWS_PROFILE=<>

# リクエスト

$ curl -X POST \
    -H "Content-Type: application/json" \
    -d '{
          "type": "codebuild",
          "text": "test",     
          "channel": "websocket",
          "user": "user"
        }' \
    http://localhost:3000/api/messages

# [<CodeBuildのプロジェクト一覧>] が返ってくるはず

まとめ

Botkitを使うとかなり簡単にAPIぽいものが出来上がります。
ここには書いていませんが、slackを使ってデプロイをchatbot的に実行できたりします。
ちょっと敷居が高いかなと思っている方も、ぜひ気軽に遊んでみましょう ?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CloudFront + S3 で SPA 環境を構築

個人メモ
時間があれば、もう少し詳しく書く。

CloudFront, S3 のデフォルトで index.html を設定する方法があるが、
HTTPステータスコードが変な感じになるので、lambdaEdgeで対応

CloudFrontのDefaultにlambdaEdgeを指定。
それ以外は、lambdaEdgeを呼ばない様に。

参考
https://hackernoon.com/how-to-host-a-single-page-application-with-aws-cloudfront-and-lambda-edge-39ce7b036da2

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Terraformのjsonencode関数にはJSONを入れても動くよ

※この記事はTerraform v0.12系で検証してます。

はじめに

まずはこちらをご覧ください。AssumeRoleを定義したこのコード、assume_role_policyにはjsonencode関数を使っていてその引数としてJSONをベタ書きしています。

resource "aws_iam_role" "test_role" {
  name = "test_role"
  assume_role_policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Principal" : {
          "Service" : "ecs-tasks.amazonaws.com"
        },
        "Action" : "sts:AssumeRole"
      }
    ]
  })
}

なんとこのコード、ちゃんとApplyできるんです!!
引数にはJSONを渡していますが、これはTerraformのMap型として解釈され、その上でJSONにエンコードされます。

何が便利か?

上述の例だと便利さがいまいちなので少しだけマシな例を挙げると

resource "aws_iam_role_policy" "test_role_policy" {
  name = "test_role_policy"
  role = aws_iam_role.test_role.id
  policy = jsonencode({
    "Version" : "2012-10-17",
    "Statement" : [
      {
        "Effect" : "Allow",
        "Action" : [
          "s3:ListBucket"
        ],
        # コメントも書けるし当然変数も埋め込める
        "Resource" : [
          aws_s3_bucket.this.arn
        ]
      },# ケツカンマがあっても動く
    ]
  })
}

このような感じでJSONといいつつTerraformのMap型なのでコメントが書けたり変数が埋め込めたり、ケツカンマをつけたりできます。(もちろんカンマなしもOKです)

Terraformのリソースの引数にJSONを入れる方法は幾つかありますが、それぞれ大小問題があります。

  • ヒアドキュメントでJSON文字列を定義する
    • コメントを書けない
    • カンマをつけ忘れたりつけすぎるとエラー
    • ネストが崩れて見にくい
  • 別ファイルとして定義してfileやtemplate関数で呼ぶ
    • コメントを書けない
    • カンマをつけ忘れたりつけすぎるとエラー
    • テンプレートで変数を埋め込んでるケースだと場合によってはリンターに怒られる
  • aws_iam_policy_documentのように専用のデータソースで書く
    • JSONからは離れてしまうのでAWSのドキュメントなどにあるJSONサンプルをコピペできない
    • container_definitionsのようにデータソースが無い場合もある

jsonencode関数にJSONを渡す方法はそれらをいい感じに解決できます。(多分)
- 公式ドキュメントのJSONサンプルをコピペできる
- コメント書ける
- カンマなくてもあってもOK
- ちゃんとネストされる
- どんなリソースでも使える

(多分)と書いたのは、私自身、まだそんなにこの書き方を使っていないので思わぬところでハマりどころがあるかもしれなかもと思ったからです。が、この記事で書いたようなサンプルはちゃんと動くので多分大丈夫でしょう。なんかハマったら追記します!

なんで動くか

そもそもjsonencode関数はTerraformの値をJSON文字列にエンコードする関数です。
TerraformのMap型はKeyとValueの区切りに=を使いますが、実は=の代わりにコロンを使うこともできます。
コロン区切りのネスト可能な{}で囲まれたKey Value、つまりJSONのフォーマットはTerraformの構文中ではMapとして解釈されjsonencode関数によって再びJSON文字列に戻されるのでこの書き方が機能します。

おわりに

Terraformのjsonencode関数にはJSONを入れても動いてHappy!!やったね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS SAAでインスタンスファミリーの選び方を攻略

はじめに

現在、SAA試験の合格に向けて勉強していますが、インスタンスファミリーの選択を問う問題が出題されます。
aws公式で約30種類確認したが、全部の用途・機能について覚えるのはしんどい。試験によく出るところだけ覚えたい。
今回は、試験合格を意識してニーズや用途に合ったインスタンスファミリーを厳選して、一覧にまとめてみました。

5つのカテゴリー

インスタンスファミリーは大きく5つのカテゴリーに分類できる。

  • 汎用
  • コンピューティング最適化
  • メモリ最適化
  • 高速コンピューティング
  • ストレージ最適化

汎用

この分類には「Ax」「Tx」「Mx」シリーズが該当する(頭文字はATMって覚えるとよいかも)。
汎用インスタンスは、CPU、メモリ、ネットワークのリソースバランスが取れた基本的なタイプ。
インスタンスのリソースを同じ割合で使用するアプリケーションに最適。

インスタンスファミリー 特徴
Axシリーズ(A1しかない) AWS Graviton プロセッサで動作する初の EC2 インスタンス
Txシリーズ バースト可能
Mxシリーズ コンピューティング、メモリ、ネットワークの各リソースがバランスよく提供される

Txシリーズは一時的に負荷集中の傾向が見えるシステムに最適。
Mxシリーズは万能タイプなので、迷ったらこれを選ぶ感じ。

コンピューティング最適化

この分類には「Cx」シリーズが該当する(コンピューティングのCと覚えるとよいかも)。
コンピューティング最適化インスタンスは、コンピューティング負荷の高い集約型ワークロードに最適。
具体的にはバッチ処理、メディアトランスコードなど。

インスタンスファミリー 特徴
Cxシリーズ コンピューティング負荷の高いワークロードに最適化

高い性能を要求するシステムの場合、コストパフォーマンスの観点からCxシリーズが最適。

メモリ最適化

この分類には「Rx」「Xx」「Ux」「Zx」シリーズが該当する。
メモリ負荷の高いアプリケーション向けに最適。

インスタンスファミリー 特徴
Rxシリーズ RAMを大容量搭載
Xxシリーズ RAM 単価が最安
Uxシリーズ EC2の中でも 6~24 TB の最大級のインスタンスメモリ
Zxシリーズ 高速な最大 4.0 GHz の持続的な全コア周波数を提供

高速コンピューティング

この分類には「Px」「Gx」「Fx」シリーズが該当する。
GPU搭載のため、汎用GPUコンピューティングアプリケーションに最適。
ユースケースは、機械学習のようなデータ分析に使用されることが多い。

インスタンスファミリー 特徴
Pxシリーズ ハイパフォーマンスで単純計算に長けている
Gxシリーズ グラフィック集約型アプリケーション用に最適化
Fxシリーズ FPGAを搭載、ASICが抱える設定変更できないデメリットを緩和

ストレージ最適化

この分類には「Hx」「Ix」「Dx」シリーズが該当する。
ストレージ容量が大きく、高いシーケンシャル読み取り及び書き込みアクセスを提供。

インスタンスファミリー 特徴
Hxシリーズ HDDを大容量搭載
Ixシリーズ SDDを使用
Dxシリーズ HDDを大容量搭載、ディスクスループットパフォーマンスあたりの価格は Amazon EC2 では最小

参考

Amazon EC2 インスタンスタイプ
【AWS】EC2のインスタンスとは?インスタンスタイプの特徴と比較,覚え方/選び方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む