Amazon API Gateway エンドポイント(REST API)のカスタムドメイン設定・認可・アクセス制限

この記事は個人ブログと同じ内容です

www.ritolab.com


サーバレスアーキテクチャ構築の第二弾です。

今回は Amazon API Gateway で作成したエンドポイントを使いやすくしたり制限をかけたりしていきます。

開発環境

今回の開発環境は以下になります。

Terraform v1.0.2
なお、今回は前回の続きになるので、操作する Amazon API Gateway については、AWS Lambda / Amazon API Gateway の連携・エンドポイント作成 で作成したものをベースに行っていきます。

API のタイプは REST API です)

カスタムドメインを設定する

デフォルトでは API Gateway で作成されるエンドポイントは以下のようになります。

https://<REST_API_ID>.execute-api.<REGION>.amazonaws.com/<LAMBDA_FUNCTION_NAME>/<RESOURCE_NAME>...

これをカスタムドメインを設定することで以下のように短くする事ができます。

https://<CUSTOM_DOMAIN>/<RESOURCE_NAME>...

以下、カスタムドメインを設定していきます。

ACM 証明書発行

ACM で証明書を発行します。ネイキッドドメインは Route 53 に登録済みの前提です。

main.tf

locals {
  api_gateway_sub_domain_name = "${var.sub_domain_host_name}.${var.domain_name}"
}

# 作成済みホストゾーン情報の取得
data "aws_route53_zone" "main" {
  name = var.domain_name
}

# ACM 証明書作成
resource "aws_acm_certificate" "APIGateway" {
  domain_name       = local.api_gateway_sub_domain_name
  validation_method = "DNS"

  tags = {
    Name = var.domain_name
  }
}

## ACM 検証用 CNAME レコード
resource "aws_route53_record" "api_gateway_acm_c" {
  for_each = {
    for d in aws_acm_certificate.APIGateway.domain_validation_options : d.domain_name => {
      name   = d.resource_record_name
      record = d.resource_record_value
      type   = d.resource_record_type
    }
  }
  zone_id         = data.aws_route53_zone.main.zone_id
  name            = each.value.name
  type            = each.value.type
  records         = [each.value.record]
  ttl             = 60
  allow_overwrite = true
}

## ACM 証明書 / CNAME レコード 連携
resource "aws_acm_certificate_validation" "APIGateway" {
  certificate_arn         = aws_acm_certificate.APIGateway.arn
  validation_record_fqdns = [for record in aws_route53_record.api_gateway_acm_c : record.fqdn]

  depends_on = [
    aws_acm_certificate.APIGateway,
    aws_route53_record.api_gateway_acm_c
  ]
}

API Gateway カスタムドメイン登録

API Gateway でカスタムドメインを適用します。

main.tf

# カスタムドメイン
resource "aws_api_gateway_domain_name" "main" {
  domain_name              = local.api_gateway_sub_domain_name
  regional_certificate_arn = aws_acm_certificate.APIGateway.arn
  security_policy          = "TLS_1_2"

  endpoint_configuration {
    types = ["REGIONAL"]
  }
}

## API マッピング
### ドメイン名から API ステージへのパスをマッピング
resource "aws_api_gateway_base_path_mapping" "main" {
  api_id      = aws_api_gateway_rest_api.to_lambda_node.id
  stage_name  = aws_api_gateway_stage.hello_world.stage_name
  domain_name = aws_api_gateway_domain_name.main.domain_name
}

# route53 A レコード作成
resource "aws_route53_record" "APIGatewayCustomDomain" {
  name    = aws_api_gateway_domain_name.main.domain_name
  type    = "A"
  zone_id = data.aws_route53_zone.main.id

  alias {
    evaluate_target_health = true
    name                   = aws_api_gateway_domain_name.main.regional_domain_name
    zone_id                = aws_api_gateway_domain_name.main.regional_zone_id
  }
}

カスタムドメイン名を登録したら APIマッピングを行い、カスタムドメイン名を A レコードで登録(ルーティング先に API Gateway ドメイン名を指定)します。

これでエンドポイントがカスタムドメインを用いた URI になります。

https://<CUSTOM_DOMAIN_NAME>/hello_world こんな感じです。

IAM 認証を導入してエンドポイントに認可制限を設ける

前回までの時点ではこのエンドポイントはどこから・誰からでも叩ける状態なので、必要に応じて何らかの制限を掛けてあげる必要があります。

サーバレスという事で今回のアプリケーションを静的サイトと想定した場合、S3 にホスティングして CloudFront で配信の構成が多いと考えます。

その場合 VPC や IP で制限を掛けるといった事が難しいので、IAM 認証を導入して API Gateway で作成したエンドポイントに認可制限を掛けたいと思います。

IAM ユーザー作成

まずはエンドポイントにリクエストする際の IAM ユーザーを作成します。

main.tf

# IAM Role for ApiGateway
## AWS管理ポリシー
data "aws_iam_policy" "AmazonAPIGatewayInvokeFullAccess" {
  arn = "arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess"
}

# IAM User for ApiGateway execution
resource "aws_iam_user" "api_gateway_requester" {
  name = "api_gateway_requester"
}

resource "aws_iam_user_policy_attachment" "api_gateway_requester" {
  user       = aws_iam_user.api_gateway_requester.name
  policy_arn = data.aws_iam_policy.AmazonAPIGatewayInvokeFullAccess.arn
}

今回はテストなのでポリシーは AWS 管理ポリシーである AmazonAPIGatewayInvokeFullAccess を使用しています。

サービスやエンドポイントによって細かくリクエストを制限したい場合は管理ポリシーではなく個別にリソースを指定します。

メソッドの authorization を IAM 認証へ変更

次に、作成したメソッド(GET)の authorization を IAM 認証へ変更します

main.tf

### メソッド設定
resource "aws_api_gateway_method" "hello_world" {
  rest_api_id   = aws_api_gateway_rest_api.to_lambda_node.id
  resource_id   = aws_api_gateway_resource.hello_world.id
  http_method   = "GET"
  authorization = "AWS_IAM" // <-  ここを NONE から AWS_IAM へ変更
}

これまで NONE としていた部分を AWS_IAM へ変更します。

動作確認

AWS 側の設定はこれだけなので、動作確認を行なってみます。

まずはローカルのターミナルから curl コマンドを叩いてみた結果です。

% curl -IX GET  https://<CUSTOM_DOMAIN_NAME>/hello_world
HTTP/2 403
content-type: application/json
content-length: 42
x-amzn-requestid: xxxx-xxx-xxx-xxxx
x-amzn-errortype: MissingAuthenticationTokenException
x-amz-apigw-id: xxxxxxxxxxxxx

認可エラーを示す HTTP ステータスコード 403 が返ってきたので、しっかり制限がかかっている事が確認できます。

一方で、IAM 認証を行なった上でリクエストを行えば、認可で弾かれずにエンドポイントにアクセスする事ができます。

SigV4 - API リクエストに認証情報を追加する

AWS API Gateway で作成したエンドポイントのリクエストで IAM 認証を通すためには、Signature Version 4 (SigV4) を用いて AWS API リクエストに認証情報を追加する必要があります。

docs.aws.amazon.com

docs.aws.amazon.com

ここはアプリケーション側の実装になりますが、JavaScript 及び PHPAWS SDK を用いて実装してみたので参考までに記します。

AWS SDK for JavaScript v2(SigV4 部分を抜粋)

import aws from 'aws-sdk'
import core from  'aws-sdk/lib/core'

-----------------------------------------------

const awsAccessKey = '<AWS_ACCESS_KEY>';
const awsSecretKey = '<AWS_SECRET_KEY>'
const awsRegion  = '<AWS_REGION>';
const awsComponentServiceName    = "execute-api";
const awsAPiGatewayHelloWorldUrl = 'https://<CUSTOM_DOMAIN>/hellow_world';

const splits = awsAPiGatewayHelloWorldUrl.split('?');
const host   = splits[0].substr(8, splits[0].indexOf("/", 8) - 8);
const path   = splits[0].substr(splits[0].indexOf("/", 8));
const query  = splits[1];

const options = {
  url: awsAPiGatewayHelloWorldUrl,
  region: awsRegion,
  method: 'GET',
  headers: {
    host: host,
  },
  pathname: () => path,
  search: () =>  query ? query : '',
};

const signer = new core.Signers.V4(options, awsComponentServiceName);

signer.addAuthorization(new aws.Credentials(awsAccessKey, awsSecretKey), new Date());

const response = await axios.get(awsAPiGatewayHelloWorldUrl, {
  'headers': {
    'authorization': options.headers['Authorization'],
    'x-amz-date': options.headers['X-Amz-Date']
  }
});
// => Hello from Lambda!

こちらについては以下の記事を参考にさせていただきました

dev.classmethod.jp

API Gateway で生成された JavaScript SDK を使用

var apigClient = apigClientFactory.newClient({
    accessKey: awsAccessKey,
    secretKey: awsSecretKey,
    region: awsRegion
});

apigClient.helloWorldGet()
            .then(function(result) {
                console.log(result.data);
                // => Hello from Lambda!
            })
            .catch(function(result) {
                console.log(result);
            });

docs.aws.amazon.com

AWS SDK for PHP v3

/** @var \GuzzleHttp\Client $client */
$client = new Client();

/** @var \GuzzleHttp\Psr7\Request $request */
$request = new Request('GET', 'https://<CUSTOM_DOMAIN>/hellow_world');

/** @var \Aws\Credentials\Credentials $credentials */
$credentials = new Credentials('<AWS_ACCESS_KEY>', '<AWS_SECRET_KEY>');

/** @var \Aws\Signature\SignatureV4 $signer */
$signer = new SignatureV4('execute-api', 'ap-northeast-1');

// クレデンシャルを使用し必要なヘッダーをリクエストに追加することで指定されたリクエストに SigV4 で署名します。
$requestWithSign = $signer->signRequest($request, $credentials);

// エンドポイントへ HTTP リクエストを送信
$response = $client->send($requestWithSign);

$response->getBody()->getContents()
// -> "Hello from Lambda!"

指定している AWSACCESS_KEY と SECRET_KEY は、API Gateway へのリクエスタとして(このセクションの冒頭で)作成した IAM ユーザーのものになります。

IP Address でアクセス制限を掛ける

API Gateway のエンドポイントに対して IP 制限をかけたい場合は、リソースポリシーを用いることで実現できます。

docs.aws.amazon.com

main.tf

resource "aws_api_gateway_rest_api_policy" "to_lambda_node" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id
  policy = jsonencode({
    Version : "2012-10-17",
    Statement : [
      {
        Effect : "Allow",
        Principal : "*",
        Action : "execute-api:Invoke",
        Resource : "arn:aws:execute-api:${var.aws_region}:${var.aws_id}:${aws_api_gateway_rest_api.to_lambda_node.id}/*",
        Condition : {
          // IP アドレス制限
          "IpAddress": {
            "aws:SourceIp": "xx.xxx.xx.xxx/32"
          }
        }
      }
    ]
  })
}

CORS の有効化を行う

作成したリソースの CORS を有効化するのなら、terraform モジュール api-gateway-enable-cors が簡単でいい感じに使えます。

OPTIONS メソッドを追加してクロスオリジンリソースシェアリング(CORS)プリフライトリクエストを許可する Terraform モジュールです。

main.tf

## CORS 設定
module "cors" {
  source  = "squidfunk/api-gateway-enable-cors/aws"
  version = "0.3.3"

  api_id          = aws_api_gateway_rest_api.to_lambda_node.id
  api_resource_id = aws_api_gateway_resource.hello_world.id

  allow_headers = ["Content-Type", "authorization", "x-amz-date"]
  allow_methods = ["OPTIONS", "GET"]
  allow_origin  = "*"
}

必要な項目を設定すると、OPTIONS メソッドが作成され CORSプリフライトリクエストが可能になります。

今回はここまでになります。Amazon API Gateway で作成したエンドポイントの方はあらかた使いやすくなったので、次回は DynamoDB を絡めてデータ操作周りをやっていこうと思います。

image-magick-lambda-layer を使ってオリジナル LGTM 画像を作ろう

この記事は個人ブログと同じ内容です

image-magick-lambda-layer を使ってオリジナル LGTM 画像を作ろう


LGTM画像といえば、LGTMoon、いつもお世話になっています。 でも、たまにはオリジナリティ出したいですよね。

そこで、今回は5秒でオリジナルLGTM画像を生成するサービスを作成します。

準備するもの

lambda layer とは

Lambdaレイヤーは、追加のコードまたはデータを含むことができる .zip ファイルアーカイブです。レイヤーには、ライブラリ、 カスタムランタイム 、データ、または設定ファイルを含めることができます。レイヤーを使用すると、コードの共有と責任の分離を促進し、ビジネスロジックの記述をより迅速に繰り返すことができます。

素の lambda にライブラリを追加する方法。layer に分けることで処理速度を上げることができる。今回はAWSで用意されているレイヤーを使用するが、もちろん独自のレイヤーを作成することもできる。関数に追加できるLayerは5つまでで、合計サイズが250MB以下となる必要がある。

lambdaファンクションを作成する

lambda > 関数 に行き、「関数の作成」をします。 関数名は適当に lambda-image-magick(任意)、ランタイムは Node.js 14.x とします。

API Gateway、POST メソッドを作成する

これから作成する API Gateway の POST エンドポイントに画像のURLをリクエストし、 lambda で受け取れるようにしていきます。

{
  url: '画像のアドレス'
}

API Gateway から REST API を作成、アクション > メソッドの作成 から POST を作成。先程作成した lambda 関数名を指定します。

CORS を有効化する

「CORSを有効にして既存のCORSヘッダーを置換」をクリックし、「はい、既存の値を置き換えます」をクリックするとOPTIONSメソッドが作成されます。

image-magick-lambda-layer をデプロイする

image-magick-lambda-layer にアクセスしてデプロイをクリックします。

デプロイしただけでは使用できないので有効化します。

レイヤー > レイヤーを追加。 AWSレイヤーに出てこなかったため、ARN を指定してレイヤーを追加しました。

コードを用意

ImageMagick, GraphicsMagick を node.js で扱えるようにする gm を入れます。axios は画像ダウンロードで使用。

$ yarn init -y
$ yarn add axios gm 

ローカルで動作を確認する場合はインストールをお忘れなく。

$ brew install imagemagick
$ brew install graphicsmagick

重ねる lgtm.png を用意します。

LGTM画像は以下のように、適当に数パターンサイズを用意。 (当初は送られてきた画像のサイズに合わせてリサイズを考えましたが、lambda上に一時的に画像を保存しておくことができないため断念。)

├── assets
│   ├── lgtm.png
│   ├── lgtm100.png
│   ├── lgtm150.png
│   ├── lgtm200.png
│   ├── lgtm250.png
│   ├── lgtm300.png
│   ├── lgtm500.png
│   └── lgtm700.png
├── index.js
├── node_modules
├── package.json
└── utils.js

lambda 関数を作成

  1. 画像パスを受け取り、画像をダウンロード
  2. 画像とあらかじめ用意している lgtm.png と合成
  3. 合成したバッファをリサイズ(出力サイズは width 300px とした)
  4. response 返却
// index.js
const { downloadImage, composite, resize } = require('./utils');

exports.handler = async (event) => {
  try {
    const buf = await downloadImage(event.url); // 1
    const composited = await composite(buf); // 2
    const resized = await resize(composited, 300); // 3
    const base64 = 'data:image/png;base64,' + Buffer.from(resized).toString('base64');
    const response = {
      statusCode: 200,
      headers: {
          'Content-Type': 'image/png',
          'Access-Control-Allow-Origin': '*',
      },
      body: base64,
      isBase64Encoded: false,
    };
    return response; // 4
  } catch(e) {
    console.error(e);
  }
};

処理の詳細はこちら

// utils.js
const GM = require('gm');
const gm = GM.subClass({ imageMagick: true });
const axios = require('axios');

exports.downloadImage = async (url) => {
  const res = await axios.get(url, { responseType: 'arraybuffer' });
  return Buffer.from(res.data);
}

const getBufferSize = (buf) => {
  return new Promise((resolve, reject) => {
    gm(buf)
      .size((err, { width, height }) =>
        err ? reject(err) : resolve({ width, height }))
  })
}

const getLgtmPng = (width) => {
  if (width >= 1000) {
    return './assets/lgtm700.png'
  } else if (width < 1000 && width >= 800) {
    return './assets/lgtm500.png'
  } else if (width < 800 && width >= 500) {
    return './assets/lgtm300.png'
  } else if (width < 500 && width >= 300) {
    return './assets/lgtm150.png'
  } else if (width < 300) {
    return './assets/lgtm100.png'
  }
}

const getPosition = (lgtmPath, width, height) => {
  return new Promise((resolve, reject) => {
    gm(lgtmPath).size((err, size) => {
      const centerWidth = width / 2;
      const centerHeight = height / 2;
      const left = Math.floor(centerWidth - (size.width / 2));
      const top = Math.floor(centerHeight - (size.height / 2));
      const geometry = '+' + left + '+' + top;
      return err ? reject(err) : resolve(geometry)
    });
  });
}

const getCompositedBuffer = (buf, lgtmPath, geometry) => {
  return new Promise((resolve, reject) => {
    return gm(buf)
      .composite(lgtmPath)
      .geometry(geometry)
      .quality(100)
      .noProfile()
      .toBuffer((err, buffer) =>
         err ? reject(err) : resolve(buffer));
  })
}

exports.composite = async (buf) => {
  try {
    const { width, height } = await getBufferSize(buf) 
    const lgtmPath = getLgtmPng(width)
    const geometry = await getPosition(lgtmPath, width, height);
    return await getCompositedBuffer(buf, lgtmPath, geometry);
  } catch(e) {
    throw e;
  }
}

exports.resize = async (buf, width, height) => {
  return new Promise((resolve, reject) => {
    gm(buf)
      .resize(width, height)
      .noProfile()
      .toBuffer('PNG', (err, buffer) =>
        err ? reject(err) : resolve(buffer));
  });
};

デプロイ

コードができたら zip にします。

$ zip -r deploy.zip ./

lambda の画面から .zip ファイルをアップロード。

完成 🎉

ブラウザの任意の jpegpng の画像のURLをエンドポイントにリクエストする bookmarklet を作成する。

Next.js にレイアウトに関するドキュメントが追加されました

この記事は個人ブログと同じ内容です

zenn.dev

 

 

Next.js の Layout 周りのドキュメントが新しく追加されました。

単一のレイアウトを扱う場合

単一のレイアウトで十分な場合はカスタマイズした <Layout/><Component /> タグを囲むだけで実装できます。

// pages/_app.js

import Layout from '../components/layout'

export default function MyApp({ Component, pageProps }) {
  return (
    <>
      // Layout でアプリケーションのコンポーネントを囲むだけ
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </>
  )
}

ページごとに異なるレイアウトを扱う場合

ページごとに異なるレイアウトが必要な場合は getLayout でページごとにレイアウトを定義します。 getLayout を使用してレイアウトの切り替えを行うことで、コンポーネントツリーがページ遷移間で維持されるため永続的にレイアウト内の state を保持することができます。 コンポーネントツリーが維持されると React は state を維持しつつ、必要な要素のみ再レンダリングします。

// pages/index.js

import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'

export default function Page() {
  return {
    /** Your content */
  }
}
// 個別のページごとにレイアウトを定義する
Page.getLayout = (page) => (
  <Layout>
    <NestedLayout>{page}</NestedLayout>
  </Layout>
) 
// pages/_app.js

export default function MyApp({ Component, pageProps }) {
  // ページごとに定義されたレイアウトがある場合はそれを使用する
  const getLayout = Component.getLayout || ((page) => page)

  return getLayout(<Component {...pageProps} />)
}

データフェッチ

Layout 内でのデータフェッチはクライアント側でのみ可能です。 Layout はページではないため、今のところ getStaticPropsgetServerSideProps は使えません。

プレビュー

Next.js 公式リポジトリのサンプルがわかりやすいのでぜひ触ってみてください。

Open in StackBlitz

生活習慣をエンジニアリングする

株式会社ROXX agent bank開発チームのorifujiです。チーム内での取り組み「Rock Activity」の一環で本記事を投稿します。 元々、何かしら新しく身につけたい技術に触ってみてそのコード解説をしようかとも思っていました。いたのですが、ごく最近の自分の取り組みとして最もちゃんとしたActivityだったのはこれだな、ということで、直近の生活改善内容を綴ってゆく方向へ舵を切りました。 生活改善に関わるちょっとしたtipsと、それに対するわたしの所感が書かれています。たぶん、ギリギリ技術ブログなのでは……

ちなみに最近始めた個人ブログで先に公開しています

「これは、ポンコツポンコツによるポンコツのための生活改善案だ。万国のポンコツよ、とりあえず朝起きろ!」

というわけで、生活習慣を見直すための記事を始めます。ここ数ヶ月のわたしの悪戦苦闘の様子が見られます。

TL;DR

  • 意志ほど信用ならないものはありません。良い行動を仕組みや条件反射に落とし込み、自らの身体を傀儡のように扱いましょう
  • 睡眠をコントロールしましょう
  • 適切に休憩するためのフックを用意しましょう
  • 疲れにくくするために身体を鍛えましょう
  • 業務中に自然とできていることは私生活にもだいたい応用可能なので、とりあえずやってみましょう(まずは、スクラム的なふりかえりから!)

序:経緯・動機

なんかうまくいかない日々

冒頭に偉そうな発言を引用しましたが、何を隠そう、誰よりもまずわたしがポンコツだったのです……

具体的には、現職への転職後、概ね下記のような状態に陥っていました。

  • 毎日、業務時間内に体力が尽きる
  • ちょっとしたことで生活リズムが崩壊する
  • 結果、入社2ヶ月目で2回も遅刻
  • 趣味・学習に時間が費やせず、成果もイマイチ
  • 思考が澱み、たいして面白いアイデアも出てこない
  • 何よりも仕事が遅い

前職での心身の疲れが尾を引いていたとはいえ、これはなかなか手痛いズッコケです。 「面倒だけど、どうにかしないとなぁ」などと零しつつ、どうにか挽回するためにさっと簡単にできるとこから始めちゃおうぜ、というポジティブシンキングを開始しました。

そのあたりまえ、あたりまえに出来ていますか?

そこで、簡単にできるNext Actionとして、「問題解決方法・生活改善に関して書かれた本」を多読してみてアイデアを拝借するを案出しました。

のですが、白状すると、実は筆者はこれまで、ビジネス書・自己啓発書をあまり読んできませんでした。正直に申し上げると、「内容が薄い」「あたりまえのことが書かれているだけ」「一面的な思想を煽情的なキャッチコピーとともに押し出している」ような印象を抱いていたのです。そのため、長年に渡り距離を置いていました。

そのため、アイデアとして思いついておきながらも、ちょっぴり及び腰になります。

しかし、昨今の為体から、自身の考えに対して疑問が生じてきます。例えば、「本当にデキるビジネスマンが参考にしているなら一読の価値はあるのでは?」「あたりまえのことって……本当にできてる?」というように。

そんなわけで、とりあえず馬鹿にせずに読みまくってみよう、と決めました。だいたい、2021年6月から7月の出来事です。

読んだ本

書店やAmazonで幾らか見繕います。あまり知識のない領域なので、売れ線や「わたしでもよく聞く本」から選んできました。

  • 安宅和人『イシューからはじめよ』
  • ジム・クウィック『LIMITLESS 超加速学習: 人生を変える「学び方」の授業』
  • グレッグ・マキューン『エッセンシャル思考』
  • Testosterone・久保孝史『超 筋トレが最強のソリューションである 筋肉が人生を変える超・科学的な理由』
  • 樺沢紫苑『インプット大全』
  • 樺沢紫苑『アウトプット大全』
  • 樺沢紫苑『神・時間術』

などなど。時には(神の時間術ってなんだよ……)などと思いつつも、全て読み切りました。結構楽しい体験でした。折を見てジュンク堂書店池袋本店1F, 5F(↑のような本がいっぱい置いてあるところ)とか覗きに行くかもしれません。

生活習慣改善・行動改善の本は少なめかも知れませんが、いずれの本も今回のテコ入れには少なからず役に立ってと思っております。

振り返ってみると、今回採用した改善案には幾らかの書籍に共通して書かれていた事項が多いです。また、著者独自というよりは一般論的な見解も多かったと判断しているため、直接的な引用をしない限りは個別に詳しく典拠を注釈することはしません。上記の紹介に留めます。

本:やったこと、わかったこと

学習効率化や問題解決の本が多めになったのですが、今回はいったん、そういうものよりも生活習慣改善・行動改善にフォーカスして、得た知識をまとめてみようかと思っています。 今回はとりあえず最近の取り組みをまとめるだけですが、後々、プラクティスをより体系化して記事にします。

睡眠改善

ツラミポイント

  • 起きるのが遅いと仕事の立ち上がりが悪い
  • そもそも睡眠時間が短い
  • 目覚めが悪い
  • 昼食後、かなり眠くなる

対処

早寝のための行動

上掲の書籍群の一部を参考にしつつ、下記を行いました。

  • 後述する筋トレ活動で身体を追い込み、疲れで寝てしまう状態をなるべく作るようにした
  • 生活リズム補正が必要なときのために、かかりつけの医師と相談のうえ、メラトニン錠剤を個人輸入した
早起きのための行動と、目覚めを良くするための行動

「意識が覚醒したら意地でも目を瞠ることを習慣とする」は効果的でした。セロトニンが云々……などと説明することもできますが、それ以前の単純な話、目をあけていれば二度寝はしないんです。意識が安定し、立ち上がることができるまで目を開き続けます。 気合いで解決、みたいな方法に見えますが、慣れると条件反射的に目を開けるようになりますし、いきなり起き上がるよりはたぶん楽です。

しばらくして立ち上がることができたら、廊下に出て日光を浴びます。自宅の場合、窓の構造上の問題等で自室に日光を入れることが期待できないため、都度廊下に出ています。日光浴の最中は軽く動的ストレッチなどをすると、手持ち無沙汰感がないです。

それが終わったら、階段昇降または散歩を行い、軽い運動とします。これやるだけで頭の冴え方が違う気がしています。

日中の眠気や集中力低下を抑制するための行動

これは端的に「昼寝」です。30分程度で済ませるのがベストだと思います。

なお、万一の寝落ち防止のため、布団に臥せることは避けます。万年床は危険なのです。朝のうちに畳んでおくと良いでしょう。

布団の代わりに、ネックピローをつけながら椅子で軽く寝ることにしています。目覚ましがなったら確実に起きられる程度の状態で寝る、がちょうどいいですね。

得たもの

  • 過度な夜更かしをしなくなった
  • 毎日コンスタントに7-8時間の睡眠が確保できるようになった
  • 午後も集中してタスクに取り組めるようになった

筋トレ

ツラミポイント

  • 体力がない
  • 気分が沈みがち
  • 音ゲーで疲れる

対処

筋トレする、が対処な訳ですが、具体的にどういうメニューをやっているか、継続するためにどのようなことをしたかと綴っていこうかと思います。

メニュー
  • 拳立て 10 x 3
  • 腹筋 10 x 3
  • 腹斜 10 x 3
  • プランク 30sec x 3
  • 足上げ腹筋 10 x 3
  • 背筋 10 x 3
  • ダンベル(計12kg)有スクワット 10 x 3
  • ハンドグリップ(60kg) 左右 10 x 3
  • ランニング(距離不定 range: 0-8km)

上記を基本として、回復させたい部位は適宜メニューから除外、その分は別のメニューを補填する形で運用しています。例えば腕を重点的に鍛えたいときはダンベルカールを追加しています。

取り掛かる

このメニューをPC画面に大きく表示させて、上から順に

  1. メニュー内容を声に出す
  2. 声に出したら「やらないとな……」という気分になって取り掛かる
  3. 次のメニューに移る

を繰り返します。思いのほかこの声出し法は推進力が高くぶっ続けになりがちです。「続かないしんどい追い込み」を避ける意味でも、適宜休憩宣言(21:50まで休憩! とひとりで叫ぶ)をしています。現状、タスクの区切りごとに叫ぶのは本当に効果的なので、筋トレ以外にも応用したいところです。

続ける

筋トレやダイエットの三日坊主はよく聞くところです。予防的に、以下の対策をしておきました。

  • おいしいと思えるプロテインを購入し、「プロテインを飲みたい→せっかくなら筋トレしなきゃ」のサイクルを確立(プロテイン駆動)
  • 会社のslackに筋トレchを作成し、毎日筋トレ宣言or報告
  • 筋トレのアニメをNetflixで自宅上映し、気分を盛り上げる

得たもの

  • 目覚めが良くなった
  • 体力が戻り、一日中業務に集中できるようになってきた
  • 前向きな気持ちになってきた

氾濫する情報の群れをシャットアウトする

つらみポイント

下記について流石に嫌気がさしていました。

  • 怒り、焦り、そのほか諸々の良くない感情
  • コロナ禍の不安なニュース
  • オリンピックなどの時事にまつわるネガティブなニュース

社会課題が浮き彫りになる事件ではありますし、議論を避けることが現状追認を招いてしまう側面もあるかとは思います。 ただ、あまりにもネガティブな感情が出回っているため、健康を取り戻すまでは離れた方がいいな、と。 行動改善系の書籍でもSNS断捨離的なワードがよく目につきました。 そういうわけでとりあえずTwitterから始める形で、各種SNSと距離を置くことに。 それにしても、インターネット歴約20年にして、ようやくTwitter疲れを検知できたわけです。遅すぎですね。

対処

で、もうばっさりやってしまいました。

  • 手元端末からの公式クライアントアプリの削除
  • 自発的な更新をほぼ停止
    • またその旨を宣言してプロフィールに固定
  • フォロー先全てに対してRTミュートを適用
    • 例外的に見に行く必要が出た場合にも「知らない人の知らない言葉」を表示しないように

ここまでしてやっとTwitterに費やす時間を大幅に減らせました。

得たもの

イライラする契機が格段に減りました。ながら作業的に触って集中力を乱されることもなくなりました。

「独り言を多方面に向けて発信する」欲はslack, discordで代替できています。

スクラム的な考え・活動を私生活に輸入する

弊社開発チームでは一週間スプリントでスクラム開発を回しており、特に毎週金曜日には

  • スプリントレビュー
  • ふりかえり(レトロスペクティブ)
  • リファイメント
  • プランニング

などのスクラムイベントを行っています。このFWをプライベートにも持ち込んでみるのは面白そうだなと思い、同居人と、2週間スプリントで軽くスクラム生活をしてみることにしました。その施策の内容を書き出してゆきます。厳密さよりも手軽さを優先する方針ですので、ここに関してはツッコミご容赦を。

ふりかえり

良かったこと・悪かったこと・生活上の課題などを出してゆきます。例えば、以下のような感じに。

  • Good:仕事に結構集中できた
  • Good:オンラインゲームで遊ぶのに時間を捻出できたのは良かった
  • Good:筋トレ用具を買い足したおかげでモチベUP
  • Bad:ゲームに熱中しすぎて睡眠時間が減った
  • Bad:浪費の傾向がある
  • Problem:玄関先の雑草が邪魔で歩きにくく、雨の降った後はズボンがやたらに濡れる
  • Problem:2人で半日かければ終わりそうだから、来週末にカレンダーを押さえておこう

リファイメント

検知された問題への具体的な対処方法を検討します。例えば先程のProblem「玄関先の雑草が邪魔で歩きにくく、雨の降った後はズボンがやたらに濡れる」「お米のストック追加がギリギリになるから夕飯を食べ逃すことがある」などの困りごとについて、対処法を検討します。 前者は「草むしりとりあえずやろうか」「除草剤撒いちゃう?」などのようなアイデアが出るかと思います。そんな感じで、ブレインストーミング→タスク化→合意形成を進めます。

プランニング

リファイメントで詳細化したタスクがどれだけ時間をかければ問題なくこなせる作業かを見積り、いつ着手するかを計画します。例えば、リファイメント時の結論「玄関先の草むしりを歩くときに邪魔にならない程度にやっておく」というタスクに対して、「2人で半日かければ終わりそう」のような見積もりを行い、「カレンダーに予定を入れておこう」というように計画を立て、アクションに繋げます。

やってみた感想

やってみれば、これもやはり「当たり前の話し合い」に尽きるところはあったかと思います。特別なこともない営みではありましたが、意識的に実行すること自体の良さが存在しました。 「アイデア出しの時間を決めて、付箋に書き出し、ラウンドロビン形式でメンバーに展開する」などのチームである程度慣れていた方法を流用したことで、時間対効率を高められましたし、問題検知が普通に雑談しているよりかは高精度でできるようになった気がしています。 決まった日時に話し合う機会を設けることで、事前に考えておくこともでき、そもそも問題を棚上げにしてなあなあで済ませることもなくなるのが、体験として素晴らしいですね。

跋:成果、つぎにやること

自分に期待しない、という期待の仕方

意志ほど信用ならないものはないですね。頑張るとかどうにかするとか、曖昧な対処ではなく、仕組みに落とし込むことこそが大事。あたりまえですね。繰り返しになりますが、そのあたりまえができていない点に、病因があります。

いまだにポンコツのままですが、幾らかの成果は出ました

生来のポンコツ具合ゆえになかなか思うようにはいきませんが、当初目的は達成できたと認識しています。

  • 仕事内容の質的向上
  • 学習・趣味に費やす時間の増大
  • 生活リズムの安定
  • メンタルの安定

ですね。読書時間が増えたので、積読が高速消化されています。こんなに楽しいことはありません。

今後の予定

「◯◯をエンジニアリングする」シリーズを継続的に投稿してゆこうと考えています。

題材候補

  • 集中力をエンジニアリングする
  • 業後学習をエンジニアリングする
  • 家計をエンジニアリングする

……エンジニアリング、の意味が良くわからなくなりそうですが、小さなことでも「明確な目的意識を持ち」「なるべく技術的に問題を解決しようとする」態度をそう表現するのも悪くないかもなと思い、気軽になんでもエンジニアリングに含める方向でこのまま突き進みます。

感想

全体、気分が良くなったので満足しています。 レベルの低いこと、当たり前のこと、でもできていないこと。そういう見落としを認識する契機としては有意義な時間だったのではないかと省みる次第です。

今後は本記事に載せたような生活改善施策を高精度のプラクティスとして洗練させ続け、生活の質を高めたいなと思っています。引き続き、自己改善を行ったり、身近な人としっかり話し合ったり。生活にメリハリをもつと気持ちいいんですね。ごく最近、気がつきました。

プロダクトを改善するなら自分も改善していかないと、との思いで今後も続けてゆきます。

ROXXに転職して2ヶ月が経ったエンジニアから見た開発チームのあれこれ

2021年6月に開発チームにjoinしたpoisonと申します。これからよろしくお願いいたします。

今回、開発チームのタスク内で自分の価値を発信しよう!という動きがあったので、ブログを書くことに決めました。

はじめに

私はSESで各現場にアサインされJavaScriptFWでよく使われているAngular・Reactを使用したWebアプリケーション製造に携わっておりました。

そしてこの度、Vueを扱っている弊社にjoinすることができ3大制覇したことをきっかけに、その扱い方の違いについて書こうと思いました。

ただ、2ヶ月経った今、開発チームの雰囲気や1週間の流れを入ったばかりのエンジニア視点で書けるのは今しかないと思い、今回はこちらを題材にしました。

(3大制覇に関してのブログは後日書けたら書きます…) 

私のスペック

2017年新卒、ROXXで3社目(1~2社目はSESで現場常駐)

主に扱ってきた技術は、JavaJavaScript(Angular,React)・Redux等

もっと自分の価値市場を上げたい、事業会社に入ってプロダクトを育てる立場になりたいという思いから2020年12月に転職活動を開始し、翌年3月にROXXから内定をもらい今に至る

開発チームの実情

ROXXには「agent bank」「back check」の2つのサービスがあり、私が所属しているのはagent bankのDevチームです。

今は、フルリモートで働いており、業務中は常にDiscordというアプリを使用して、常時通話状態で作業している状況です。

常時通話状態なので、リモートなのに出社している緊張感を感じることができ、家にいるけど職場の雰囲気を感じながら仕事しております。

基本はスクラムチームとして動き、1週間スプリントでタスクをこなし毎週金曜日にスプリントレビュー・振り返り・リファイメント・プランニングを実施しているという流れになってます。

個人的には、この流れがすごく好きで、1週間できちんと終えて、また次の週から始められることでスッキリ週を終えることができます。

チームの人数は執筆時点で12人ほどいて、プロジェクトが複数動いていることから半分ずつチームが分かれ、日々のタスク消化をしている人数は5~6名で回しています。そこから、基本ペアプロを行っているため、2~3レーンで進めてます。

毎日、同じメンバーとペアプロするのではなく、日々シャッフルしながら実施しているためメンバー内で話したことがないというのはないです!日々、和気藹々とやっている反面、真面目に議論するときもあるので個人的にはいい雰囲気で過ごしています。

いろんな現場を見てきた身からすると、技術が強いメンバーもいるため、わからないこととかは気軽に聞くことができる環境となっており、個人的にはとても助かっております。 

おすすめの活動

私がチーム活動の中でおすすめする部分として2点あります。

1つ目は、毎日の振り返りです。

私たちのチームでは、月曜〜木曜までは、朝会・昼会・夕会を実施しており、メンバーの進捗を随時確認してます。

夕会では、「やったこと」「おきたこと」「わかったこと」の頭文字をとりYOWというFWを使用して取り組んでおります。

その日のうちに、実際やったこと・それをしたことで起きたこと・その中で分かったことを振り返りし、週末の振り返りの際にはそれを見返すことで忘れず、1週間の流れを確認することができます。

その後、何がよかったのか・問題点はなんだったのか・今後どうアクションしていくかを決めスプリントを閉じるという流れで実施しております(俗にいうKPTですね)。

デイリーの中でメンバーそれぞれが出し合ったものを確認すると、分かってなかったことその日に起きたことなどを知る機会が増えるので、いろいろと話し合ったりしてお互いに知識を深め合う場としても活用しております。

2つ目は、リファイメントについてです。

スクラムチームの活動は、転職前にも経験していたのですが、その時は、タスクの見積もりをするだけの活動となっていました。

私たちのチームでは、タスクの見積もりはプランニングで行い、リファイメントではPOが何個かの改善点をBizの方々から抽出し、チームに落とし込んで、何をどうすればその状況が解決するかを全員で考える場として使ってます。

これをすることで、受動的ではなく能動的にタスクと向き合えることができる気がしております。

それだけでは終わらず、リファイメントした後に設計タスクと題して、DB構造であったりどんな機能が必要かであったりを複数人で考えるタスクがあり、コーディングだけでなく設計能力も高めることができる場が設けられております。

コーディングだけを主に行ってきた身としてはとても新鮮で、毎度関わることができているので、どんどん価値を出していきたいと思っております!

私が思うチームの課題点

新参者がいきなりチームの課題を出すというものも、大変烏滸がましいと思いつつまだまだここは足りないかもしれないと思う点があります。

それは、属人化についてです。

主に、インフラに関してですが、作業できるレベルの方が少数しかおらず、その方が休んだ暁には、危機的状況になっているのが現状です。

この件に関しては、チームだけでなく開発事業部全体で考えており、属人化を回避するために、2つのサービスの開発チーム合同で勉強会を行ったりしております。そういった行動を積み重ねて、各人が作業レベルで触れるようになるために、まずは基礎知識をつけてその上でわかる方とペアプロする形で学んでいくような体制を作ることができればベストかと思っております。

私もまだまだインフラに関してはど素人なので、とりあえず環境を作ってみようということで、今後触っていこうと思っております。

今後の目標

今後の目標としては、今までは主にフロントエンドしか触ってなく、サーバーエンドやインフラに関してもどんどん積極的にタスクをこなして、自分のものにしたいと考えております。

それを実施することができる、チームにはとても感謝しております。今後も自分の価値を発揮できるように精進していきたいと思います。

終わりに

この記事を読んで、もしROXXや開発チームに興味が出てきた方がいましたら、ぜひご応募ください!お待ちしております!

 

careers.roxx.co.jp

AWS Lambda / Amazon API Gateway の連携・エンドポイント作成(REST API)

この記事は個人ブログと同じ内容です

www.ritolab.com


サーバレスアーキテクチャ構築の第一弾です。

今回は AWS の Lambda と API Gateway を連携させて、エンドポイントを作成していきます。

サーバーレス・コンピューティング

自身でサーバーを運用せずに IaaS 等のサービスを利用して処理のロジックだけをデプロイ。必要な時にだけホストした処理を動作させる一連の仕組み。

例えば、いくつかの処理を行う API サーバを持っているとして、そのサーバは 24 時間 365 日稼働させる必要がある。

サーバレスにすれば処理のロジックをリモートにホストしておき、必要な時にだけ動作させる事ができて、自身でサーバーを運用する手間が省ける。(サーバーに関するセキュリティや可用性をその分気にしなくて良くなる)

さらには動作した時間だけの課金になるので、24 時間 365 日サーバーを稼働させるよりも(大抵は)費用が抑えられる。

サーバーレス・コンピューティングを提供している主な IaaS

余談ですが AWS では、Well-Architected Framework という指針(AWS を用いた設計のベストプラクティスなど)を出していて、その中の「パフォーマンス効率」の章でサーバーレスアーキテクチャを推奨していたりもします。

docs.aws.amazon.com

AWS Well-Architected Framework

wa.aws.amazon.com

AWS Lambda

AWS Lambda はサーバーレスコンピューティングサービスで、サーバーのプロビジョニングや管理、ワークロード対応のクラスタースケーリングロジックの作成、イベント統合の維持、ランタイムの管理を行わずにコードを実行できます。

引用元:AWS Lambda(イベント発生時にコードを実行)| AWS

AWS でサーバレスやるぞってなったらこいつですね。

aws.amazon.com

Amazon API Gateway

フルマネージド型サービスの Amazon API Gateway を利用すれば、デベロッパーは規模にかかわらず簡単に API の作成、公開、保守、モニタリング、保護を行えます。API は、アプリケーションがバックエンドサービスからのデータ、ビジネスロジック、機能にアクセスするための「フロントドア」として機能します。

引用元:Amazon API Gateway(規模に応じた API の作成、維持、保護)| AWS

こちらはサーバレス云々というより、API を作成できるサービスです。今回は API Gateway でステートレス API を作成して、アプリケーションからそのエンドポイントを叩こうと思います。

API Gateway で作成したエンドポイントにリクエストしたら、Lambda の処理が動く。という流れです。

aws.amazon.com

開発環境

今回の開発環境は以下になります。

  • Terraform v1.0.2

ちなみに今回、AWS Lambda は Node でやります。

ホストするコードは JavaScript になるわけですが、今回はこれも terraform で管理します。

実運用だと別々での管理が望ましいと考えますが、それよりもとにかく動かしてみたい欲が先行したので今回はその辺は割愛します。

それと、tf ファイル作成していく中で functions へのパスを書いているのでディレクトリ構成を事前共有しておきます。

.
├── functions
├── src
└── terraform
  • functions(Lambda にホストするコード)
  • src(アプリケーション)

Lambda にホストするコード

まずは、AWS Lambda にホストするコードを作成しておきます。

functions/tf-test-node-hello-world/index.js

exports.handler = async (event) => {
    return {
        isBase64Encoded: false,
        statusCode: 200,
        headers: {},
        body: JSON.stringify('Hello from Lambda!'),
    };
};

「Hello from Lambda!」を返すだけの関数です。

また、API Gateway が Lambda からレスポンスを受け取る場合は形式が決まっているため、そちらに倣った形式にしています。

docs.aws.amazon.com

Lambda の構築

Lambda を構築していきます。

CloudWatch

Lambda の実行ログを CloudWatch に流すのでロググループとロールを作成します。

AWS Lambda の Amazon CloudWatch ログへのアクセス

docs.aws.amazon.com

main.tf

variable "function_name" {
  type    = string
  default = "tf-test-node-hello-world"
}

# CloudWatch Logs for lambda
resource "aws_cloudwatch_log_group" "node_lambda_hello_world" {
  name = "/aws/lambda/${var.function_name}"
}

# Lambda Role for logging CloudWatchLogs
resource "aws_iam_role" "lambda_node_logging" {
  name = "${var.app_name}-lambda-role"

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

resource "aws_iam_policy" "lambda_node_logging" {
  name        = "${var.app_name}-lambda-policy"
  description = "IAM policy for logging from a lambda"

  policy = jsonencode({
    Version : "2012-10-17",
    Statement = [
      {
        Action   = "logs:CreateLogGroup",
        Effect   = "Allow",
        Resource = "arn:aws:logs:${var.aws_region}:${var.aws_id}:*"
      },
      {
        Action = [
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ],
        Effect = "Allow",
        Resource = [
          "${aws_cloudwatch_log_group.node_lambda_hello_world.arn}:*"
        ]
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_node_logging" {
  policy_arn = aws_iam_policy.lambda_node_logging.arn
  role       = aws_iam_role.lambda_node_logging.name
}

Lambda へのデプロイ

今回は terraform で Lambda にデプロイするコードも管理するので、ローカルで ZIP を作成するようにしておきます。

main.tf

data "archive_file" "lambda_function" {
  type        = "zip"
  source_dir  = "../functions/tf-test-node-hello-world"
  output_path = "../functions/upload/tf-test-node-hello-world.zip"
}

これで terraform plan ないし terraform apply 時に ZIP が作成されます。

Lambda function の作成

最後に、メインである関数を定義します。

main.tf

resource "aws_lambda_function" "hello_world" {
  filename      = data.archive_file.lambda_function.output_path
  function_name = var.function_name
  role          = aws_iam_role.lambda_node_logging.arn

  handler = "index.handler"

  source_code_hash = filebase64sha256(data.archive_file.lambda_function.output_path)

  runtime = "nodejs14.x"

  depends_on = [
    aws_iam_role_policy_attachment.lambda_node_logging,
    aws_cloudwatch_log_group.node_lambda_hello_world
  ]
}

docs.aws.amazon.com

docs.aws.amazon.com

Lambda 構築実行

ここまで書いて terraform apply を実行すると、Lambda に関数が出来上がります。

f:id:ro9rito:20210720113408p:plain

API Gateway の構築

次に、作成した Lambda 関数を実行するためのインターフェースを Amazon API Gateway を使って作成していきます。

API 作成

API を作成します。インターフェースの大本を作成するイメージ。

API タイプは用途によって最適なものを選択する必要があります。今回は REST API で作成します。

docs.aws.amazon.com

main.tf

# API Gateway
resource "aws_api_gateway_rest_api" "to_lambda_node" {
  name        = "${var.app_name}-to-lambda-node-api"
  description = "REST API for lambda node test"
}

リソース作成

/hello_world のリソースを作成します。

main.tf

## for Function: hello world
### リソース作成
resource "aws_api_gateway_resource" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id
  parent_id   = aws_api_gateway_rest_api.to_lambda_node.root_resource_id
  path_part   = "hello_world"
}

メソッド作成

メソッドはリソースに対して GET や POST などの HTTP リクエストメソッドを作成します。ここでは GET で作成します。

main.tf

### メソッド設定
resource "aws_api_gateway_method" "hello_world" {
  rest_api_id   = aws_api_gateway_rest_api.to_lambda_node.id
  resource_id   = aws_api_gateway_resource.hello_world.id
  http_method   = "GET"
  authorization = "NONE"
}

lambda 統合を設定

今回は API Gateway で作成したエンドポイントがリクエストを受け取ったら Lambda function を実行するので、API Gateway と Lambda を紐付けます。

API Gateway では Lambda との連携がスムーズにできるようにこういった設定も用意されているので紐付けが簡単に行えます。

main.tf

### lambda 統合
resource "aws_api_gateway_integration" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id
  resource_id = aws_api_gateway_resource.hello_world.id
  http_method = aws_api_gateway_method.hello_world.http_method

  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.hello_world.invoke_arn
}

ポイントとしては、integration_http_method を POST に指定している点です。

lambda への統合 method は POST で固定となっています。

aws.amazon.com

デプロイメント作成

デプロイメントを作成します。デプロイメントは、REST API 構成のスナップショットのイメージ。作成したデプロイメントを次の、ステージと紐付ける事で API が公開されます。

main.tf

### デプロイメント
resource "aws_api_gateway_deployment" "hello_world" {
  rest_api_id = aws_api_gateway_rest_api.to_lambda_node.id

  depends_on = [
    aws_api_gateway_integration.hello_world
  ]
}

再度デプロイメントを行う場合の注意点

新しいリソースやメソッド作成、もしくは既存のリソースやメソッドの変更を terraform から反映する場合は、再度デプロイメントを作成する必要があります。

単純にリソースやメソッドをコード上で追加して反映しても、リソース上で追加されるだけでそれはデプロイされないからです。

そしてデプロイメントは、リソースやメソッドを追加・変更しても apply 時に変更対象とはなりません。(既に作成済みのため)

そのため AWS コンソールから手動でデプロイしてあげる必要があります。

リソースやメソッドの新規作成や変更を検知する、もしくは毎回の apply 時にデプロイメントの作成を強制する仕組みも作れますが、terraform 上での反映でデプロイメントを作成する場合は現在のものを削除した上で作り直す必要があるため、カスタムドメイン名を設定していない場合はエンドポイントのサブドメイン名が変更になってしまうので注意が必要です。

resource "aws_api_gateway_deployment" "hello_world" {
  .
  .
  .
  lifecycle {
    # 変更に対応するため一度削除して作り直す
    # REST API ID の変更によりエンドポイントの URI が変わるので注意
    create_before_destroy = true
  }
}

また、上記の設定を行わなずにデプロイメントの変更を apply した場合は以下のエラーが出力されます。

Error: error deleting API Gateway Deployment (xxxxx): BadRequestException: Active stages pointing to this deployment must be moved or deleted

どうしても仕組みが必要な場合以外はここは記述しないか false にして手動で API デプロイを行うのが良いと思います。(今回は特段必要としていないので lifecycle については記述しない(=false)で進めます。)

ステージ作成

ステージを作成します。ここでデプロイメントを参照して作成する事で API が公開されます。

main.tf

### ステージ
resource "aws_api_gateway_stage" "hello_world" {
  deployment_id = aws_api_gateway_deployment.hello_world.id
  rest_api_id   = aws_api_gateway_rest_api.to_lambda_node.id
  stage_name    = var.app_name
}

API Gateway 側の設定はこれで終わりです。

Lambda へのアクセスを制限する

最後に Lambda 関数(hello_world)へのアクセス許可を API Gateway に付与します。

main.tf

resource "aws_lambda_permission" "execution_hello_world_for_api_gateway" {
  statement_id  = "test-AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.hello_world.function_name
  principal     = "apigateway.amazonaws.com"

  source_arn = "${aws_api_gateway_rest_api.to_lambda_node.execution_arn}/*/*"
}

source_arn を本 API のみにしたので、今回作成したエンドポイントからでしかこの Lambda 関数は実行できないようになっています。

API Gateway 構築実行

ここまで書いて terraform apply を実行すると、API Gateway が作成され、Lambda 関数にも関連付いた事が確認できます。

f:id:ro9rito:20210720120201p:plain

動作確認

一通りミニマムで作成したので、アプリケーションからエンドポイントを叩いてみます。

f:id:ro9rito:20210720120235p:plain

正常にレスポンスが返ってきました。これで API Gateway 経由で Lambda 関数を実行できました。

今回はここまでになります。作成したエンドポイントはデフォルトの URL であったり制限がかかっていなかったりするので、次回はエンドポイントの設定や制御を行っていこうと思います。

【読書メモ】「成長する企業はなぜ SSO を導入するのか」

この記事は個人ブログと同じ内容です

【読書メモ】「成長する企業はなぜ SSO を導入するのか」

---

最近チームのメンバーが誕生日で、よくバラエティ番組で流れる曲「Happy Birthday - Stevie Wonder」が頭から離れない sekitats です。

今月は個人的に認証について学ぶ月間で、本を3冊読みました。認証周りわからんことばかりだったので。

OAuth については、別記事を書いてみたのでそちらも興味があれば読んでみてください。

ratatatat30.hatenablog.jp

2冊目の Auth0 + Nuxt + Rails のデモも試した。

本記事は3冊目「成長する企業はなぜ SSO を導入するのか」の要約となります。
引用元:日本ヒューレット・パッカード株式会社 (著) . 『成長する企業はなぜSSOを導入するのか 』. 日経BP, 2017

なぜ、この本を読んだのかというと、SSO に関する本を amazon で検索したら、本書しかなかったからです。(あとはミリタリーバックパック

認証はすでに経営課題

以下のような企業における認証の課題があげられている。

  • 社員のパスワードの使い回しによってパスワード盗難リスクが高まる。企業側の危機意識が低い
  • 厳しすぎるパスワードポリシーが返って生産性を低下させる
  • 個々の業務システムの担当者は、自分が担当している業務システムだけを考えて対策を立ててしまう傾向にある → サイロ化
  • サイロ化した中で認証強化してもそれは「部分最適化」でしかない。「全体最適化」とは程遠い。

    ※ サイロ化:企業内にある部門が他部門との連携をすることなく、自らの業務の部分最適化のみを優先するようになった状態をいう。元々サイロとは穀物などを貯蔵するタンクのことだが、英語圏では「窓がなく周囲が見えない」という意味も含んでいる。

出ました。サイロ化。大規模になればなるほどサイロ化の弊害は大きくなっていく。

企業経営にまで影響を与える問題となっているということ。

パスワード認証の課題

ユーザーの本人確認を行う認証にも問題は多い。

  • 他人に知られた場合になりすましが可能
  • 安易なパスワードの場合類推や解読が容易
  • 忘れたり間違えたりしやすい
  • 複雑なパスワードは記憶が困難
  • パスワードの使い回しにより、安全性が低下

パスワード認証は、「知られない」、「推測されない」、「同じものを使い続けない」、「同じものを使わない」といった「努力」をユーザーに強いていて、その「努力」には限界がある。

パスワード認証を強化する他要素認証(ワンタイムパスワード、生体認証)も導入が難しい。

そんな、企業の課題、ユーザーの課題を SSO(シングルサインオン)が解決してくれると言うてます。

SSO 導入メリット

  • SSO とは、業務システムで個別に行っている認証を一元化し、ユーザーがいずれかのシステムを利用する際に認証を一度行えば、連携している全てのシステムが利用できるようになる。ユーザー ID,パスワード、ユーザー属性などのユーザー情報の管理や、問い合わせ対応の負荷が軽減される。
  • SSO が導入されるということは、入口が1カ所で管理されるということ。唯一の入口に防犯対策をを一本化できる。情報漏洩の防止策として SSO は役に立つ。
  • 新規に導入する業務システムだけでなく、既存の業務システムにも SSO は効果を発揮する。既存の業務システムで管理されているユーザー情報に大きな変更を加えないため、導入も用意で短期間かつ少ない労力で導入できる
  • 導入によってユーザーの利便性が向上するだけでなく、認証に関する管理の負荷も軽減される。
  • 今後登場する未知のクラウドサービスの導入と行った変化にも柔軟に対応できる
  • 各業務システム間のサイロ化の課題も、個々の業務システム担当者に大きな負担を強いることなく解決でき、IT 投資を効率化できる。

なんのことはない。一つのユーザーID・パスワードだけ管理できるようになれば問題は解決する。

SSO の 4 つの方式と特徴

方式には「リバースプロキシ方式」、「エージェント方式」、「クライアントエージェント方式」「フェデレーション」の 4 つ紹介されているが、最初の 3 つはオンプレミスでSSOを用意する方式。クラウドサービスを利用するものが「フェデレーション」。SSO = フェデレーションと捉えてしまってよい。

フェデレーションの仕組み

フェデレーションの仕組みの一つとして「SAML」や「OpenID Connect」といった。「標準規約」が用意されている。
SAML とは(Security Assertion Markup Language)の頭文字で、OASIS(Organization for the Advancement of Structured Information Standards)が定めた、クラウドサービスと企業の間で認証の連携を行う標準規格のこと。ほとんどのクラウドサービスがこの SAML に対応しており、クラウドサービスと認証の連携を行う場合は、SAML を利用することが多い。

OpenID Connect つまり JWT を使うこともできるのだろう。が、SSO では SAML を使う。

SSO(フェデレーション) はシンプル

本書では、フェデレーションの仕組みをパスポートの仕組みに例えて説明している。

パスポートは所持する本人の国籍の国で発行され、その発行には比較的厳格な本人確認、つまりユーザーの認証が求められる。 ただし、一旦パスポートが発行されれば、渡航先の国ではパスポート自体が本人確認となる。 入国の際にパスポート以外の本人確認の手続きは必要とされない。

f:id:sekilberg:20210713002050p:plain

  1. IdP(IdentityProvider)で厳格な本人確認をする
  2. IdP がアサーションSAML = パスポート)を発行
  3. アサーションがブラウザを介して SP(Service Provider = アプリケーション)に提示される
  4. アサーションの正当性と信頼した IdP による発行かどうかを SP が確認する
  5. SP がユーザーに利用を許可する

SAMLアサーションがパスポートと異なるのは、一度発行されたアサーションは特定の SP へのログインにしか使えないこと。SP 毎にアサーションを発行することが必要。(これは SAMLの仕様)


IdP による厳格な審査を通過していることとSPとの信頼関係によって成り立っているわけですね。

おまけ

いきなり、IdP(IdentityProvider)、SP(Service Provider)といった用語が出てきました。 OAuth(= 認可)の世界での登場人物は、リソースオーナー、クライアント、認可サーバー、リソースサーバーですが、OpenID Connect や SAML(= 認証)の世界になるとユーザー、SP、IdPといった用語になるところも混乱しがちなところです。(トークン→アサーションとか)

まとめ

そもそも技術書ではないし、SSO導入事例、オンプレ導入方法といった企業の経営者や情報システム担当者向けの内容だったので、OneLogin, okta などの IDaaS に関するお目当ての情報はなかった。
ただ、SSOの概要は理解できました。特にパスポートに例えた説明がわかりやすく、OAuth に比べずっとシンプルに感じました。(どう実装するかは別として。。)SSO=パスポートの仕組みということがわかっただけでも収穫アリですね。

SAML に関してはほとんど説明されていませんが、id token のような個人情報が XML 形式になっただけと捉えれば良いかと思います。

近い将来 SSO による生体認証が当たり前になって、フォームに E メール, パスワードを入力していたことが時代遅れになる未来もそう遠くないのかもしれません。