production.log

ピクスタ株式会社でエンジニアのマネージャーをやっている星直史のブログです。

なぜ自分はストイックなのかを考えてみた

概要

他の人と話していると、自分はストイックだと言われることが多いので、それはなぜなのか考えてみた。

自分の考えの傾向

自分は物事の多くを投資の対象として捉える癖がある。 それは、金銭、時間、効率などの観点から考えることが多い。

例えば、通勤用のクロスバイクを購入する時はこんな考えだった。

電車の場合: 往復60分、定期代月額5,000円 自転車の場合: 往復40分, 駐輪代月額3,000円、初期購入費用50,000円

  1. 1日20分節約できるので、自分の給与から時給を算出して、20分ぶんの費用が浮く
  2. 運用コスト月額2,000円の差がある
  3. 50,000円 / (1+2のコスト)

を考えた上で、決断する。要は、回収できるの?ということを考えた。 自転車に対する好みはほとんど考慮しておらず、投資効果だけを考えて買った。 そのため、決断は、ほぼ即決。

また、自転車を2年くらい運用すれば、リターンが蓄積されるので、 自分にとっては、そのリターンが最大の喜びになる。

自分にとってストイックとは?

自分の傾向を基に、なぜストイックなのかを考えて、結論まで出してみた。 自分が思うストイックとは、自分という投資対象に自分の資本(金銭、時間など)における比率が高ければ高いほどストイックになっていくのだと思う。

大概の場合、投資は不確定要素や隠れた前提、事実が多いため、資本の投下を悩むことが多い。 また、100%増えるとわかっていても投資収益性が低ければ、投資を渋ることも多い。

しかし、自分に対する投資は、以下の点に置いて不確定要素が少ない。

  1. 自分に対して自分が投資をするので、投資をする側と受ける側の情報の透明性が保たれている
  2. 自分の行動次第で、投資効率や収益性を最大限に高めることができる
  3. 投資をする側と受ける側の利害が完全に一致している

つまり、投資判断における情報を投資家が全て掌握している点、 利率を上げようと思えば、投資家自身でコントロールできる点と投資を受ける側の納得を100%得られる点、 また、投資を受ける側と投資する側の両方が自分であるため、スピーディーに投資判断ができるため、 非常に優れた投資対象であると思う。

※これを読むと超絶ナルシストだと感じるかもしれないが、自分のことは特に優れていると感じていない。ただ、成長の余地は十分にあると踏んでいるため、惜しみなく投資しているって感じ。 ※あとは、ダラダラする時間は、何の利益も生まない時間だから苦手。

結論

自分の成長は投資家である自分に対して責任を持ちたいのと、 リターンは確実に自分(や関係者)に返ってくるため、成長に対して手を緩めることはしたくない。 この思想から来る行動が、他の人から見るとストイックなんだぁ〜と思われる要因なのだろう。

というのが結論。

ゴールデンウィークにペンションひみつ基地でお手伝いをしてきました

概要

ひょんなことから、今年のゴールデンウィークの8日間を栃木県那須塩原市のペンションひみつ基地のお手伝いを住み込みですることになったので、そこで得た学びを書こうと思います。

やったこと(業務内容)

お手伝いとはいえ、学びを得ることが目的だったので、分業化されている(であろう)大規模リゾートホテルではなく、個人経営の小規模ペンションに的を絞りました。
ですので、業務内容としては、ペンションの現場レベルの仕事は、ほぼ全て体験しました。
具体的には下記の通りです。

  • テーブル設営
  • 朝食の調理、配膳、皿洗い
  • ベッドや部屋の清掃
  • ベッドメイキング
  • 風呂場、洗面所、トイレ清掃
  • 掃除機、窓拭き
  • 夕食の調理、配膳、皿洗い

文面で見ると大したことなさそうですが、限られた人的リソースで、短時間で行わなければならないのと、ハイシーズン時の20人前後のお客様が襲来したときは死ねます。
ハードモードの縛りプレイ状態です。
むしろ、全員野球で取り組まなければ成り立たないと思いました。

8日間で得られた学び

自営業のことを学べた

ペンションひみつ基地は、基本的に夫婦2人で経営しており、ハイシーズン時に、パートさんや住み込みアルバイトさんを雇い、経営しています。
逆にオフシーズンの平日などは、比較的のんびりされているようでした。そのため、収支や労働の負荷の浮き沈みが激しく、究極のメリハリだと感じました。

また、自分の中で自営業のイメージは、従業員100人以上を雇い、売り上げも数十億を目指すのが自営業(というか起業)のイメージだったのですが、大きく稼げはしないが、夫婦二人としては余裕で暮らすことができ、仕事以外の時間も楽しめる形があるのだと学びました。
自分の人生の選択肢や考え方が一つ増えたことは大きな学びだと感じました。
那須の自然が豊かなところや、土地柄なのか、時間がゆっくりしており、このような働き方もあるのだと学びました。

あとは、自営業とはいえ、ペンション業界で生き抜くための戦略が感じられました。
ペンションひみつ基地は囲炉裏タイムと呼ばれる宿泊者同士のコミュニケーションの場を設けていたり、
めちゃくちゃ可愛い看板猫(現在は4匹)がいたり、ニジマス釣(リアルにキャッチアンドイート)りや露天風呂で日本酒が楽しめたりするなど*1、単純に宿泊するだけではなく、宿泊者に非日常感を味わせる仕組みを設けていると感じました。
8日間を通して、リピーターの方が一定数いたことや、宿泊予約サイトの管理画面で上位に位置付けていることを見た時は納得感がありました。

初心に帰った

今回は、異業種の仕事に対して、未経験者が一から全てを学びにいくので、完全に新人状態からスタートすることになります。 ウチの会社では勤続年数が比較的長いことや、メンバーに指導する立場にいるので、新人の感覚に戻れたのはとても新鮮でした。

まず、異業種の仕事をするので、単純に知的好奇心が満たされました。
単調な作業の繰り返しといえば繰り返しなのですが、

  • 前回の作業時間より1秒でも短くするためには、どのように動いたら効率的なのか
  • 次の作業を考えた時に、今やるべき作業は何か
  • 上司は何を考えて自分に作業指示を出しているのか
  • 上司のスキルを盗み、自分の行動に反映させ、成長を高速化させる

などなど、新人の頃に重点的に心がけていたことを、今やり直すことで、今の会社で(少なからず)奢っていた自分を恥じ、反省する機会になりました。
これらの作業中に、守破離はだいぶ意識しました。
まずは、ベーシックな作業内容を覚え、体に染み込ませ、
次に、ベーシックな作業内容をいかに改善できるかを考え、
最後に自分のやり方を編み出す。といった感じです。
特に、夕食の片付けにおいては、自分の作業領域を完全に作り出し*2、クローズまでの時間を最短にすべく、最速で動くことができました。

他には、最初の1,2日感の緊張感はすごかったです。
その緊張感とは、上司に「こいつ使えねぇな」と思われたら終わりだ という緊張感です。
当たり前ですが、初日から全力で取り組み、短期間で上司に対する信頼貯金をいかに増やすかを考えて行動しました。
心地よい緊張感で仕事に取り組めたのはよかったです。
信頼残高を積み上げていき、信頼されるまでを短期間で感じ取れたので、仕事の醍醐味の一つを味わえてとても楽しかったです。

異業種の仕事内容に触れることができた

単純に、宿泊業で働くのは初めての体験だったので、何もかもが新鮮で楽しく学び、仕事をすることができました。
特に、宿泊者がチェックアウトした後の清掃はとても興味深かったです。
というのも、普段、自分がホテルなどに泊まるとき、いつも、生活感がなく清潔な部屋だなぁ〜と感じるのですが、それを演出する側に回れたからです。
数時間前までに人が存在したことを感じさせないように、ベッドを整えるのは、だいぶ神経を使う作業であり疲れはするのですが、宿泊施設の提供者としての体験ができたので、とても貴重な時間でした。

また、戦術した生存戦略を実行していく中で、
自分達のサービスが目に見えるお客様に対して直接ぶつけることになるので、お客様のリアルな反応をリアルタイムで感じることができ、 一つの成功や失敗のヒリヒリ感はWebサービスとは全く異なる体験でした。
配膳などの小さな作業*3でも、全てお客様の事を考えぬいて配置/提供されていたので衝撃を受けました。

その他感じたこと

仕事

8日間、住み込みだったんですが、お客様の朝食を作ることから始まり、夕食の片付けで終わるので、1日の生活にリズム感があり体調崩すことなく終えることができたのは良かったです。
ただ、単調といえば単調だった仕事なので、おもしろくないと感じる人は一定数いるのではないかと思います。
でも、自分としては、単調な仕事でも前回の作業より1秒でも早く終わらせたり、効率化について考えたりするので、楽しく仕事をすることができました。

猫様が4匹おり、当初大丈夫かなと心配してたんですが、すごく癒しなりました。むしろ、飼おうか検討しているレベルです。夜寝てる時に毎晩お腹の上に乗って来る猫がいたんですが、懐かれたな〜という感覚が嬉しかったです。
f:id:watasihasitujidesu:20170505120713j:plain f:id:watasihasitujidesu:20170511084829j:plain f:id:watasihasitujidesu:20170503124329j:plain

温泉

温泉ペンションなので、蛇口をひねると温泉が出て来ます。
硫黄の温泉なので、温泉に入っている感(?)がすごくありました。また、毎晩広い風呂に入れるのは、温泉/銭湯好きのオレとしては最高でした。
(水風呂は欲しかったが・・・・ッ!!)

避暑地 那須

避暑地として有名な那須にペンションがあるのですが、日に日に木々が青くなってきたり、川のせせらぎが聞こえるので、心がリフレッシュされる感覚がありました。
何もないことの贅沢感がありました。(普段東京にいるからかも?)
f:id:watasihasitujidesu:20170503134522j:plainf:id:watasihasitujidesu:20170503134300j:plainf:id:watasihasitujidesu:20170503134034j:plainf:id:watasihasitujidesu:20170503124405j:plain

筋肉痛

8日間、全力で働いたら筋肉痛になりました。
立ち仕事が多いので、脚はもちろん、窓拭きで肩周りや腕周りも筋肉痛になりました。
3日目あたりに調子に乗って筋トレしたのは失敗でした。。
ジムでは鍛えることができていないなぁ〜と思ったのと、実用的な筋肉もつけなきゃなぁと感じました。

みんな良い人

ペンションで働いている人は全員良い人でした。
オーナー夫婦はもちろん、パートできてる方まで、とても優しく、仕事ができる人だなぁと感じました。
自分は、心理的安全性が確保されている環境ではないと、パフォーマンスを発揮できないのと、その安全性は一緒に働く人によって左右されることを経験から学んでいます。
繰り返しになりますが、一緒に働く人が親切で優しかったのと、教えるのがうまかったので、自分の仕事がうまくいったんだと思います。
また、パートの方には、出会って1時間で潔癖症だとバレました。何か行動するたびに、めっちゃ手洗うので、それを見られていました。。
あと、ストイックな食生活を話してしまったので、完全に変な人だと思われただろうなと感じてます。パーフェクト食生活をしているだけで変な人ではないことは強調しておきます!

まとめ

スポットで、ですが、異業種で働くことで、さまざまなことを学ぶことができました。

  • 人生における仕事観に少なからず影響があった
  • 初心に帰ることができた
  • 異業種の仕事がどんなものか知れた

また、那須の自然に触れられたことや、本職での仕事の疲労感とは全くことなる疲労感は、とても心地よかったです。
いつものゴールデンウィークはゴロゴロして終わってしまうのですが、今回は何かから学びを得ようと試みたゴールデンウィークであり、 その収穫の多さからとても充実感がありました。

普通の生活をしていたら個人経営のペンションで働くことはありえないと思うので、人生の中でも思い出に残るのではないかと思っています。 あぁ〜楽しかった!
f:id:watasihasitujidesu:20170511084711j:plain

*1:その他、蛇口から温泉が出たり、ハンモック、夏は花火などあります

*2:たと自分では評価しています

*3:作業時間という意味での小さな作業

RedashでAWS DynamoDBに対してDQLを発行しデータ取得する方法

概要

表題の通りです。 RedashでDynamoDBからデータ取得する場合DQLという、SQLライクな書き方でデータを取得することになります。
今回はDynamoのIndexを指定して絞り込みを行う際に、ちょいハマりしたので、メモとして記録します。

AWS DynamoDBの設定

テーブル構造

  • Table name: test_tables
  • Primary partition key: partition_id (Number)
  • Primary sort key: sort_id (Number)

インデックス

  • Name: partition_id-index
  • PartitionKey: partition_id (Number)

DQLの書き方

test_tablespartition_idで絞り込んでデータ抽出したい場合、DQLでは下記の書き方となります。

SELECT *
FROM test_tables
WHERE partition_id = 60515 USING partition_id-index; # USINGでindex名を指定することが重要

WHERE句で属性名だけで条件指定したいところですが、DynamoDBの特性上、インデックスを指定しなければなりません。
そのため、上記のように、partition_idはpartition_id-indexというインデックスやで〜
というように明示させる必要があります。

もし、USINGを書かないと下記エラーが発生します。
No index specified with USING <index>, but multiple possibilities for query: TABLE, partition_id-index

自前で立てたRedash(0.12.0+b2449)を最新1.0.3にupgradeする方法

概要

Redashが0.12.0の時に、AWS AMIから立ち上げたのですが、
この度、メジャーバージョンアップしたので、手動でupgradeした時のメモです。

手順もなにも、ドキュメントがあるのでその通りにやるだけですが、 コマンド実行する際に設定を変更する必要があったので、その説明をします。

手順

ドキュメントの通りwgetし、実行権限を付与。

wget https://raw.githubusercontent.com/getredash/redash/master/bin/upgrade
chmod +x upgrade

その後、処理を走らせます。

sudo ./upgrade

すると、こんなメッセージが出ます。

Before upgrading to this version, please make sure to do the following changes to your /opt/redash/.env file:
    
1. If you have local PostreSQL database, you will need to update the URL from `postgresql://redash` to `postgresql:///redash`.
2. Remove the `REDASH_STATIC_ASSETS_PATH` definition.

Make sure to complete these changes before doing the actual upgrade.

この変更をしなければ、DBが見当たらない旨のエラーが吐かれてアップグレードできないので、 /opt/redash/.envを開き、下記の通り修正します。

  1. postgresql://redashpostgresql:///redashに修正。*1
  2. REDASH_STATIC_ASSETS_PATHを消す。

まとめ

ドキュメントには書かれておらず、実行時に注意してくる感じなので、
英語読めない><
とりあえずy ><
ってやってると見落としまうので、上記修正は忘れず対応しましょう!

*1:/が一つ多くなる

【ServerlessFramework】DynamoDB Streamsでデータの更新をトリガーにLambdaを動かす方法。

概要

表題の通り、DynamoDB Streamsでデータの更新をトリガーにLambdaを動かす方法について説明します。

AWSマネジメントコンソール上でのDynamoDB Streamsの有効化

まず、AWSマネジメントコンソールでStreamsを有効化する必要があります。 DynamoDB > Tables > テーブル選択 > Overview > Stream details > ManageStreamを選択

これを選択すると、ストリームを経由して渡されるデータ構造を選択できるラジオボタンが表示されます。 それぞれの意味は下記の通りです。

  • [Keys only] — 変更された項目のキー属性のみ取得。
  • [New image] — 変更後のデータのみ取得。
  • [Old image] — 変更前のデータのみ取得。
  • [New and old images] — 変更後、変更前のデータの両方取得。

あとは作成すれば、ARNが表示されます。

serverless.ymlの設定

次にserverless.ymlに設定を書き、Lambdaと紐づける必要があります。

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev
  region: us-west-2
  iamRoleStatements:
    - Effect: "Allow"
      Action:
        - dynamodb:DescribeStream
        - dynamodb:GetRecords
        - dynamodb:GetShardIterator
        - dynamodb:ListStreams
      Resource:
        - "${AWSマネジメントコンソール上に表示されているARN}"
functions:
  dynamo_stream:
    handler: handler.dynamo_stream
    events:
      - stream:
          arn: "${AWSマネジメントコンソール上に表示されているARN}"
          batchSize: 1
          startingPosition: TRIM_HORIZON
          enabled: true

functions.dynamo_stream.events.stream内の各項目は

batchSize: 一度に処理するストリームの数を表します。1~10000まで指定できます。1以上の値を指定した場合、Lambdaの処理の中でループで処理をする必要があります。 startingPosition: 処理開始するストリームの位置を指定します。最新から取得か最古から取得かのどちらかを指定できます。LATEST | TRIM_HORIZON enabled true: 有効 / 無効を指定 true | false

また、eventの設定以外には、IAMの設定も必要となります。 上記の設定項目は最低限の設定です。これを設定しないとsls deploy -vをした時に下記エラーに見舞われます。

  Serverless Error ---------------------------------------
 
     An error occurred while provisioning your stack: DynamoUnderscorestreamUnderscoreproxyEventSourceMappingDynamodbDevitems
     - Cannot access stream AWSマネジメントコンソール上に表示されているARN
     Please ensure the role can perform the GetRecords, GetShardIterator,
     DescribeStream, and ListStreams Actions on your stream
     in IAM..
 

取得できるJSON

{
  "Records": [
    {
      "eventID": "78h9r97gawegaj7ddnga6e6w",
      "eventName": "MODIFY",
      "eventVersion": "1.1",
      "eventSource": "aws:dynamodb",
      "awsRegion": "us-west-2",
      "dynamodb": {
        "ApproximateCreationDateTime": 1492654321,
        "Keys": {
          "id": {
            "N": "123456789"
          }
        },
        "NewImage": {
          "column_a": {
            "N": "2"
          }
        },
        "OldImage": {
          "column_a": {
            "N": "1"
          }
        },
        "SequenceNumber": "405760000000000123456789",
        "SizeBytes": 123,
        "StreamViewType": "NEW_AND_OLD_IMAGES"
      },
      "eventSourceARN": "AWSマネジメントコンソール上に表示されているARN"
    }
  ]
}

しっかり新旧両方のデータが取得できていますね。

解決できなかった点

  1. ServerlessFrameworkだけで完結できない。 説明の通り、AWS マネジメントコンソールでストリームを追加し、それを元にserverless.ymlを設定しなければなりません。 deploy時に失敗するから気づけるとは思うのですが、漏れが発生する気しかしないです。

  2. 設定できるストリームが1テーブルにつき1つ 当初、レコード追加時のストリーム、Aカラムを更新したときのストリーム….といったように、細かく設定できると思っていたのですが、 試して見た結果、「何かしらの変更が加えられたらJSONに新旧両方のデータを詰めて渡すからよしなにやってね」って感じでした。 DynamoDBにカラムが新規作成されたらLambdaAを、更新の時はLambdaBを実行しようとした場合、テンプレートメソッドパターンや、プロキシパターンなり使わないと収集つかなくなりそうな印象を受けました。

まとめ

  1. AWSマネジメントコンソールでStreamsを有効化
  2. serverless.ymlに設定

この2ステップだけでDynamoDBストリームを受け取りLambdaに渡せるのはやはり便利です。 ただ、マネコンとserverless.ymlの両方に設定を加えなければならないのは漏れが発生しそうなので、もう一声といったところでしょうか。

【ServerlessFramework】S3のオブジェクト格納をトリガーにLambdaを動かす方法

概要

S3に何かしらのオブジェクトを配置したことをトリガーにLambdaを動かす設定について紹介します。 runtimeはnode.jsです。

lambdaの処理を書く

lambdaにはトリガーが正常に動いていることだけを確認すれば良いので、 console.log("hello")とだけ書いておきます。 ファイル名はhandler.jsにしておきます。

'use strict';

module.exports.hello = (event, context, callback) => {
  console.log(JSON.stringify(event, undefined, 1));
  console.log("hello");
  const response = { 
    statusCode: 200,
    body: "{\"msg\": \"success\"}"
  };  
  callback(null, response);
};

serverless.ymlの設定

functions:
  hello:
    handler: handler.hello
    events:
      - s3:
          bucket: "uploads"
          event: s3:ObjectCreated:*
          rules:
            - prefix: original-files/
    memorySize: 128 
#resources: # もしリサイズ処理をするなら、リサイズ後のデータを格納するバケットが必要
#  Resources:
#    UploadBucket:
#      Type: AWS::S3::Bucket
#      Properties:
#        BucketName: "lambda-result"

functions.hello.events.s3ブロックは、トリガーを検知するS3バケットの設定 & 生成時に実行されます。 そのため、serverlessを使用しないでAWSマネジメントコンソールから手動で作ったバケットを後からserverlessで操作しようとすると、下記のエラーに見舞われます。

  Serverless Error ---------------------------------------
     An error occurred while provisioning your stack: S3BucketBucketName
     - bucket-name already exists.

LambdaのTriggerだけを後から追加はできない(というか、しにくいというか、避けたい)ってことですね。

【ServerlessFramework】AWS LambdaとCognitoで作るセキュアなS3へのオブジェクト格納

概要

ユーザーがアップロードした画像データをS3に保存するケースにおいて Serverless Frameworkを使用して、AWS API Gateway 経由しLambdaで処理をするときに、 Cognitoで認証したユーザーのIAMをSTSを使用してS3にPUTするときの説明です。

f:id:watasihasitujidesu:20170417083912p:plain

今日は上記の図の四角で囲った部分の話をします。

serverless.yml

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev 
  region: us-west-2
#  iamRoleStatements:
#    - Effect: "Allow"
#      Action:
#        - "s3:PutObject"
#      Resource:
#        - "arn:aws:s3:::yukashita-image-uploads/original-files/${self:provider.stage}/*"
functions:
  auth:
    handler: auth.auth
  upload:
    handler: handler.upload
    events:
      - http:
          path: upload
          method: post
          authorizer: auth
    response:
      headers:
        Content-Type: "'application/json'"
      template: $input.path('$')
resources:
  Resources:
    UploadBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: "bucket"

iamRoleStatementsをコメントアウトしていますが、Lambda自体にIAM(認可)の必要はありません。 というのも、認証されたCognitoIdentity UserのIAMで操作をするので、 必要な権限はCognitoのIAMに設定していきます。(設定自体はCognitoのIAM設定で説明します。)

Lambda

'use strict';

const aws = require("aws-sdk");

var cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({
    apiVersion: '2016-04-18',
    region: 'us-west-2'
});

module.exports.upload = (event, context, callback) => {
  var AWS = require('aws-sdk');
  AWS.config.region = 'us-west-2';
  
  console.log(AWS.config.credentials) // Enviroment Credentialとなる。つまり、Lambdaで設定されているCredential

  var options = { 
    params: {
      apiVersion: '2006-03-01',
      Bucket: "bucket"
    }   
  };  
  var bucket = new AWS.S3(options);

  var idToken = event.headers.Authorization; // 認証されたCognitoIdentity UserのidTokenを取得
  
  // AWS IAM STSでCognitoAuthUserに紐づいたクレデンシャルを取得。
  // CognitoIdentityCredentialsは内部的にSTSを返してくれる。
  AWS.config.credentials = new AWS.CognitoIdentityCredentials({
    region: "us-west-2",
    IdentityPoolId : 'us-west-2:your-identity--pool-id',
    RoleArn: "arn:aws:iam::123456789:role/Cognito_your_poolAuth_Role", // AuthRoleであることに注意
    Logins : { 
      'cognito-idp.us-west-2.amazonaws.com/us-west-2_ABCDEFG' : idToken // idToken != accessTokenであること
    }   
  }); 

  // AWS.config.credentialsを書き換えただけでは反映はされない
  // この時点では、AWS.config.credentials.needsRefresh == trueとなる
  
  // AWS.config.credentials.getを呼ぶと、未反映の場合はAWS.config.credentials.refreshを呼び出し、反映させる
  AWS.config.credentials.get(function(err) {
    // この時点で、AWS.config.credentials.needsRefresh == falseとなる
    // Cognito Credentialとなる。
    console.log(AWS.config.credentials)
    if (err) console.log(err);

    var params = { 
      Key: ["bucket", AWS.config.credentials.identityId, "object_name.jpg"].join("/"), // AWS.config.credentials.identityIdで認証をされたCognitoIdentity Userしか触れない領域を確保する。
      ContentType: "image/jpg",
      Body: event.body
    };
    bucket.putObject(params, function(err, data) {
      if (err) {
        console.log("Error", err);
      } else {
        console.log("Success", err);
      }
    });
  });
  callback(null, event);
};

ちょっと長いのですが、注意点はコードコメントにしています。 かいつまんでポイントを説明します。

  1. Lambda起動直後はAWS.config.credentialsEnviroment Credentialとなる。つまり、Lambdaで設定されているCredentialです。この時点ではLambdaにはなんの権限がないので、AWSリソースにアクセスしようとした場合、AccessDenyになります。
  2. AWS.CognitoIdentityCredentialsでCognitoIdentity Userのクレデンシャルを取得します。また、このときに渡すTokenはidTokenです。accessTokenではありません。公式ドキュメント
  3. AWS.config.credentials = new AWS.CognitoIdentityCredentialsとしただけでは未反映の状態です。AWS.config.credentials.getで反映させる必要があります。
  4. AWS.config.credentials.get後のAWS.config.credentialsCognitoCredentialとなります。これでCognitoで設定したIAMの権限に切り替わります。

最後にCognitoのIAM設定を確認します。

CognitoのIAM設定

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "mobileanalytics:PutEvents",
                "cognito-sync:*",
                "cognito-identity:*"
            ],
            "Resource": [
                "*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::bucket/${cognito-identity.amazonaws.com:sub}/*"
            ]
        }
    ]
}

Resourceで設定する内容は、LambdaのS3.putObjectの際に指定するKeyの許可について指定します。 ${cognito-identity.amazonaws.com:sub}を指定していますが、これはCognito Identity poolのIdentityIDです。
設定するIAM 変数名がものすごくわかりにくいのですが、Cognito User pool の Userに割り当てられるsubではありません。*1

このIAM変数を利用すると、s3://bucket/Cognito Identity poolのIdentityID/オブジェクトにしかアクセスできなくなります。 言い換えると、他のユーザーの情報にアクセスが不可能になります。 また、同様にDynamoDB Finegrain アクセスも同じ概念となります。

解決できなかったこと

今回はCognitoのIAMをAWSマネジメントコンソールから手で書き換えるしかありませんでした。 理由はserverless.ymlでIAMのresourceを作成できないからです。 具体的に言うと、IAM > Roles > Trust relationship の Principal > Federatedをcognito-identity.amazonaws.comに設定できません。

そのため、ここだけは手動でやらなくてはならないという悲しい状況でした。

Cognitoは設定値が紛らわしく、どれを使えば良いの?といった感じです。 今回出てきたものは、下記の通りです。

  • idToken != accessToken
  • Cognito Identity pool の IdentityID != Cognito User pool の User sub

まとめ

一言でまとめると、 Lambdaの処理中にCognito Identity で設定したIAMを使う だけだと思います。
serveless.ymlでIAMを定義できなかったり、AWS.congif.credentialsがrefreshしないと反映されなかったり、そもそもCognitoの設定値が紛らわしかったりで、散々ハマりましたが、 おかげで各サービスの理解が深まりました。

*1:ここでめちゃくちゃハマりました。