Nuxt.js の context をおさらいする

こんにちは、 ROXX の匠平@show60です。

Nuxt.js の context は色々な機能を内包してくれているためそれとなく使えている感覚でしたが、そもそも中身どうなってんの?と気になったので調べてみることにしました。

そもそも context とは

JavaScript では this という呼び名の context ですが、直訳すると 文脈、脈絡、前後関係 という意味となります。

日常では曖昧な使い方をされてしまうため一層理解に戸惑いますが、 MdN では outside of any function (どの関数の外側にもある) と説明されています。

Global context

In the global execution context (outside of any function), this refers to the global object whether in strict mode or not.

this - JavaScript | MDN

global object とありますが、つまるところグローバルで定義したオブジェクトであり、文脈という言葉を使うとすれば、「すでに定義したという文脈があるんだよー。知っておいてね。」という解釈になるでしょうか。

Nuxt.js の context

Nuxt.js の公式ドキュメントには context で使用可能なキーのリストが載っています。

API: コンテキスト - Nuxt.js

このリストを見ていくと、例えば routefrom といったものは Vue Router Route のインスタンスの型をとっています。 Nuxt.js のアプリ内の適当な箇所で context を呼び出し、 console.log(route) を行ってみると以下のような内容が表示されます。

{
  fullPath: "xxx",
  hash: "",
  params: {},
  path: "xxx",
  query: {}
  ...
}

これは Vue Router のルートオブジェクトプロパティがそのまま含まれているということが分かります。

API Reference | Vue Router

つまり context にはVue Router が定義してあり、 route などの名前で呼び出すことによってどこでも使えますよ、というのが context です。

上記のドキュメント内のキーのリストに app という項目が含まれているのですが、 NuxtAppOptions の型を持っていると説明してあります。

これはどのように使うのでしょうか。

Nuxt.js の context の app

物は試しで、 context の app を呼び出したところで console.log(app) してみましょう。

{
  $axios: ƒ wrap(),
  beforeCreate: ƒ beforeCreate()
  components: {NuxtLoading: {}}
  computed: {isOffline: ƒ}
  context: {isStatic: false, isDev: true, isHMR: true, app: {}, store: Store, …}
  ...
  store: Store {_committing: false, _actions: {}, _actionSubscribers: 
Array(0), _mutations: {}, _wrappedGetters: {}, …}
  watch: {nuxt.err: "errorChanged"}
}

コンポーネント内で使用するオブジェクトなども並んでいます。

この中の context を開いて見てみると、先程の routefrom も存在しており内容も同一となっています。

この context 内の app について、先程のリスト内では すべてのプラグインを含むルートの Vue インスタンス と説明されています。つまり、 app には Vue RouterVue Store など context に含まれるオブジェクトがすべて参照できるというわけです。

context の定義

念のために Nuxt.js の Github 上でコードも確認してみましょう。

GitHub - nuxt/nuxt.js: The Vue.js Framework

Nuxt.js の package/vue-app/template/index.js 、 58 行目あたりに async function createApp とあります。この処理内で const app = { してある箇所があり、どうやらここで app を定義しているようですね。

async function createApp(ssrContext) {
  const router = await createRouter(ssrContext)
  ...
  const app = {
    router,
    <% if (store) { %>store,<%  } %>
    nuxt: {
      defaultTransition,
      transitions: [ defaultTransition ],
      setTransitions(transitions) {...},
      err: null,
      dateErr: null,
      error(err) {...}
    },
    ...App
  }
  ...
  await setContext(app, {
    route,
    next,
    ...
  })

router はここで注入されているようです。setContext で context を定義しているようですのでこちらも見てみましょう。

nuxt.js/utils.js at dev · nuxt/nuxt.js · GitHub

export async function setContext(app, context) {
  // If context not defined, create it
  if (!app.context) {
    app.context = {
      isStatic: process.static,
      isDev: <%= isDev %>,
      isHMR: false,
      app,
      <%= (store ? 'store: app.store,' : '') %>
      payload: context.payload,
      error: context.error,
      base: '<%= router.base %>',
      env: <%= JSON.stringify(env) %><%= isTest ? '// eslint-disable-line' : '' %>
    }
    ...
  }
  ...
  app.context.params = app.context.route.params || {}
  app.context.query = app.context.route.query || {}
}

index.js が起動し、そこで context の中身を諸々注入していることが分かりました。この app がどこでも参照できる状態にあることで各コンポーネントから context 内のオブジェクトを扱うことができるわけですね。

実務では storeerror あたりを使うことが多いでしょうか。ページから store に保管してあるデータを参照したり、 API へのリクエストが失敗したときのエラーハンドリングを行うため自ずと頻度が上がります。

プラグインの注入

新たに context にプラグインを注入したい場合は、上記の Nuxt.js の utils をわざわざ編集するのではなく plugins ディレクトリと nuxt.config.js に記述するだけです。

プラグイン - Nuxt.js

プラグインではないですが、 app にテキストを入れて表示してみたいと思います。

plugins/sample.js

export default ({ app }) => {
  app.sample = 'sample'
}

nuxt.config.js

plugins: [
  '~/plugins/sample'
]

npm installyarn 等をして config.js を再読み込みすると、先程の console.log(app) の結果に sample が追加され、テキストが表示されました。

f:id:show-hei:20190729132411p:plain
plugins/sample の注入

とても簡単に扱えるようにしてくれてますね。

まとめ

外部プラグインは context だけでなく Vue インスタンスに注入しても使用することができます。

どちらもコンポーネント内で呼び出すことができるのですが、どちらの使用を選択するべきかについて、また改めて調べてみたいと思います。

最後に、私たち ROXX では一緒に開発を進めていただける仲間を募集しています。 ご興味お持ちいただいた方はぜひご応募ください!

www.wantedly.com

www.wantedly.com

Frontend de KANPAI! で登壇してきました

こんにちは、株式会社ROXXの石岡 将明( @masaakikunsan )です。

7月19日の Frontend de KANPAI! #7 - Going on 令和 で登壇させていただきました。

今回は、登壇した話について、ブログを書いていこうかなと思います。

登壇でのテーマ

今回は Going on 令和 というテーマがありました。 内容としては令和でも Frontend やっていきみたいなのを考えていましたが、Frontend を普段からやっている人や、ある程度スキルのある人達は勝手に成長していくので今回は令和から Frontend やっていきたい人に向けての発表にしようと思いました。

なのでゴールとしては、下記としました。

  • これからフロントエンドを始めたい、伸ばしていきたい人の背中を押す
  • フロントエンドエンジニアとしてのキャリアを悩んでる人の手助けができる

実際のスライド

下記が実際のスライドです。

slides.com

タイトルは 令和から始める Frontend としました。

アジェンダは、下記です。

  • これまでのフロントエンド
  • ここ数年のフロントエンド
  • これからのフロントエンド

スライド内では、僕のキャリア的な話を書いております。 ここでは、僕が3年ぐらいでこんな感じのことをやっているって話をしました。

詳しくは、スライドを見ていただきたいのですが、全体的にこれから始める人はこうしたほうがいいんじゃない?という話 + 僕が3年で普通に働けているのでみんな普通に1年本気でやればやっていきできるよっていった感じの発表になりました。

f:id:masaakikunsan:20190726135017j:plain

フロカンについて

フロカンでは、LT 枠はなく運営が登壇依頼をしその人達 + DeNA の社員が登壇する形になっています。 それもあり、毎回発表のクオリティはかなり高いものになっています。

また、スタッフもやりたくてやってるんだろうなぁといった感じが強く、ものすごく雰囲気が良いです。(あとコミュ力が高いと感じた)

今後も参加していこうと思っていますし、弊社イベントでも取り入れられることはかなりあるかなぁと。

また、登壇させてもらえるように日々頑張っていきをします。

さいごに

現在、株式会社ROXX では、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

SARDINE開発チームを支えるツールたち

こんにちは。2019年7月1日からジョインしました です。 入社当日に 株式会社ROXXへ社名変更 するというリリース作業に立ち会うレアイベントを経験できてラッキーでした。

この記事では SARDINE をどのように開発しているのか、SARDINEの開発チームを支えるWEBサービスやツールを紹介します。

SARDINE

開発体制

開発体制ではスクラム手法を取り入れています。月曜日に始まり金曜日まで、毎週1週間のスプリントです。 金曜日はスプリントレビューと振り返りや次スプリントのプランニング、ときには意識合わせのワークショップなどを行い1日が終わるので開発する時間は月曜日から木曜日までの4日間です。 スプリント期間が短いこと、しっかりプランニングを行うことで1週間の予測が立てやすいので、メリハリをつけた活動ができています。 トレードオフスライダーは納期と品質は毎スプリント一定を保つ、スコープで実装を調整するよう意識しています。

スクラムマスターの hirokinisizawaスクラムマスターの話 、またCTOの kotamat技術組織の話 として記事を公開していますのでそちらもご覧ください。

Jira

スプリントの管理には Jira をフル活用しています。たとえば朝会ではスクラムボードで今日やることの確認をします。スプリント中に発生した課題は随時バックログへ追加して必要に応じてスプリント中に対応したり次スプリントに回したりの判断をしやすくします。

秘匿性が高い情報も含まれているため黒塗りが多くなってしまいました...イメージだけでもご確認いただければ幸いです。

Jiraのスクラムボード ほぼ真っ黒でごめんなさい

チームのコミュニケーション・情報の共有や蓄積

リモートで作業しているメンバーも多数いるのでコミュニケーションは密にとるようにしています。

Google Meet, Discord

朝会では Google Meet を利用して会話しています。 Google Meetで画面共有をおこなってJiraのスクラムボードを全員で参照したり各自困っているコミットなどを共有して全員で認識を合わせられるように心がけています。 また、チームメンバーの顔や状況をわかりやすくするために外部カメラを繋いだり声が聞き取りやすいように外部スピーカーを使っています。

モニター上部に設置したカメラ

かんたんに声をかけたり話しかけたりしたい場合には Discord も使っていますね。

Slack

テキストコミュニケーションには Slack を利用しています。 Slackは開発メンバーに限らずROXXの全メンバーが活用しています。 出社退社時の打刻はスラッシュコマンドで行いますし、全社周知事項の案内、お客様からのお問い合わせ速報など様々な内容が通知されます。

ただし開発用のチャンネルだけでもが多岐にわたるので慣れないと追うのが大変でした。 このへんは課題があるなと感じています。

esa

esa もROXXの全メンバーが活用しています。 開発環境の構築手順、ステージング環境のアクセス方法などなどとりあえずesaを探せば情報に辿りつけるようになっています。

development and deploy

画面デザインの設計には Figma を、ソースコードの管理には GitHub を、 CIに Travis CI を利用しています。 サービスのインフラに AWS を利用しておりAPIサーバーはEC2、データベースはRDS、静的サイトの配信にはS3 + CloudFrontを利用しています。 静的サイトでもランディングページなどの一部のサイトには Netlify を利用しています。

イメージ

ここまで出てきたツールを画像にまとめるとこのような図になります。

SARDINEの開発ワークフローまとめ

ほかにも

ほかにもスプリントレビューやミーティングで利用する50インチ程度の大きなディスプレイが開発チームエリア近くに配置されています。 またディスプレイも申請せずとも1人1枚以上配給されます。私のチームでは27インチ程度のモニターを2枚利用しています。

...以上のように、SARDINEサービスの運営開発をより円滑に行うために様々なツールを用いています。

SARDINEの開発体制がみなさんにイメージが伝われば幸いです。

最後に

株式会社ROXXでは一緒にSARDINEの開発に携わってくれるメンバーを、また新規サービスの back check にもメンバーを随時募集しています。 この記事を読んでROXXに興味を持ってくれた方はぜひご応募ください。  

www.wantedly.com

LaraVue勉強会 #10 開催しました!

こんにちは、株式会社ROXXの佐藤( @r_sato1201 )です。

先日、Laravue勉強会#10を開催いたしました。
はやいもので、今回でなんと10回目。 今回もたくさんの方に参加して頂き、非常に有意義な勉強会となりました。
その模様を報告させて頂きたいと思います。

laravue.connpass.com

会場

今回は、heyさんのオフィスをお借りしました。開放感のあるとてもきれいなオフィスでした。

f:id:ryonnsui1201:20190721002315j:plain

発表内容

スポンサー枠も含め、6名の方に発表していただきました。
すべて紹介するのは冗長なので、私が気になった発表を紹介させていただきます!

Storybookを用いたVue.js共通コンポーネント開発との戦い (@howdyさん)

f:id:ryonnsui1201:20190721003110j:plain

トップバッターはスポンサー枠の@howdyさん。
storybookを使った共通コンポーネント集を使う際の苦悩、戦いについて話していただきました。
予期せぬUI崩れに気づくために、REG-SUITという画像回帰テストを行ってくれるツールの話など、とても興味深かったです。

speakerdeck.com

Vuex-ORMの紹介 (@Yasshieeeeさん)

f:id:ryonnsui1201:20190722113816j:plain

VuexをDBとして使い、ORMライクなアクセスをすることができるVuex-ORMについてご紹介いただきました。
ノーマライズしてくれるので、複雑にネストされたデータ構造を持っているとき便利そうです。

www.slideshare.net

Laravel + AWSでCI/CDしてみた話 (@mjimaさん)

f:id:ryonnsui1201:20190721020257j:plain

AWSのCodePipelineでLaravelプロジェクトをCI/CDする話をしてくださいました。
AWSCodeBuild内のビルドの流れなど、詳しく知ることができました。

slides.com

Laravelのイベントディスカバリー (@niisan-tokyoさん)

f:id:ryonnsui1201:20190721020318j:plain

弊社からは、@niisan-tokyoさんにLaravel5.8.9から実装されたイベントディスカバリについて発表していただきました。
5.8.9以前だと、イベント・リスナーが増えるたびにEventServiceProviderが膨らんでいくが、イベントディスカバリを使うとリスナー側のhandlerに指定されているイベントの型宣言を読んで、その型のイベントのリスナーに自動で登録してくれるというお話でした。
非常に便利ですし、興味深い話でした。

qiita.com

PHPUnit + openapi-validator で「スキーマが正、実装が追従」にする (@kon_shouさん)

f:id:ryonnsui1201:20190721020345j:plain

スキーマが正、実装が追従となるようPHPUnitで、APIスキーマと異なるレスポンスを返したら、落ちるテストを書く話をしてくださいました。
OpenApiでAPIに関するインターフェース定義のyamlを吐き出し、openapi-validatorでそのyamlPHPUnitに取りこむ、という方法で実現していました。

qiita.com

懇親会

f:id:ryonnsui1201:20190721020218j:plain

発表後は、heyさんが提供してくれた美味しいケータリングを囲み懇親会をしました!
多くの方に残っていただき、会場の使用時間ギリギリまで親交を深めていただきました。

まとめ

今回は、Laravel,Vueの発表が半々でまさに「LaraVue勉強会」を体現する勉強会になったと思います。

次回の開催は未定です。詳細が決まり次第connpass上で告知いたします!

最後に

ROXX社では一緒に頑張ってくれる方を募集しております。 デザイナー、エンジニアの皆さん興味のある方はご応募お願いします!

www.wantedly.com

www.wantedly.com

www.wantedly.com  

スクラムマスターとして失敗したと思った2つのこと

こんばんは今年の3月から3ヶ月だけですがPOをやり今月からスクラムマスターをやることになりましたhirokinishizawaです。

スクラムマスターになってから1ヶ月がたつので本記事で振り返りつつ、スクラムマスターになってから失敗したなと思ったことを話していければと思います。

はじめに

SARDINEの開発チームは2軸でやっています。その中で1チームだけ10月頃からスクラムを導入し始めました。

当時自分はスクラムを導入した方のチームに所属しており、3月からもう一つのチームがスクラムを導入するタイミングで3ヶ月だけですがPOをやりました。そして今月6月から現在そのチームでスクラムマスターをやっています。

スクラムチームのそれぞれの役割

  • PO(プロダクトオーナー): 開発チームから生み出されるプロダクトの価値を最大化すること
  • 開発チーム: 開発チームの開発効率を最適化すること
  • スクラムマスター: スクラムの促進と支援をし目的を実現する確率を最大化すること

スクラムマスターの責任

スクラムマスターはスプリントを円滑に進めることに責任を持ちます。

その中には

  • 開発チームが行ったスプリント計画の障害になるものを取り除く
  • 次のスプリントをよくするために気づきやコミュニケーションを促す
  • プロダクトオーナーのプロダクトバックログの作成や並び替えを支援する
  • プロダクトオーナーと開発チームの会話を促す

などまだまだありますが基本的になにかを決定をするのではなくあくまでサポートする動きをしてスプリントを円滑に進めるように動きます。

スクラムマスターになってからの失敗談

POをやっていたときはなるべく要件から仕様までやってプランニング(見積もり)readyの状態をつくるようにしていました。なるべくというのはどうしてもバックログの調査や整理などが間に合っていなく、必要なときはタスクを作っていて開発チームがプランニングをするために障害がなくなることに注力してしまっていました。

その状態のままスクラムマスターになって失敗したなと思ったことを書いていこうかと思います。

POから要件を受け取りプランニングreadyの状態にする

POをやっていた時はゆくゆく仕様なども開発チームに降ろすようにしなければいけないと思っていました。ですがスクラムも始まったばかりだったので基本的にはプランニングの時に見積もりが出来る状態を意識してやっていました。そんな状態のままスクラムマスターになり、POから降ってきた要件を見積もりが出来る状態までタスク分解をやったことにより

  • プランニングがただポイントをつけるだけの作業になってしまっていた
  • スクラムマスターで閉じてしまっているため質問がないと共有出来ていないことがあり共通認識があわなかった

のような状態になってしまった。

開発チームの中継役になってしまう

実際にスクラムマスターとしてサポートするというのがどういうことなのか深く理解できていませんでした。最初はサポートとしてタスクの管理やアサイン、スプリント内であった開発チームの不安要素などを吸い上げPOに確認を自分が行っていました。ですがそれを続けたらPOと開発チームの中継になりコミュニケーションをスクラムマスターを通すというルールになっていきそうだったためやめました。基本的に開発チームがPOやステークホルダーとコミュニケーションを取っては行けないというルールはありません。

現在はスプリント内で上がった不安要素や技術課題などの優先順位をつけるためにPOとのコミュニケーションを促すようにしています。

まとめ

スクラムマスターの最終目的としてスクラムチーム にスクラムマスターってもういらないよね? というチームにしなければいけないと思っています。なのでスクラムマスターが見積もりが出来る状態までやるのはやりすぎに入ってしまいます。

見積もりするために何を準備すればいいのではないか、またどのように解決すればいいのではないか。というのように決定・命令はせずスクラムチームに気づきを促すサポートをしていくのがスクラムマスターなのではないかと思います。

まだまだ未熟なので間違っているところなどご指摘いただけると幸いです!8/7~8/9の間CSM行ってまいります!

最後に

で8/7~8/9でCSMを受けに行ってくるので終わったらいろんな人が書いていますが自分もブログを書きたいと思います!

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

www.wantedly.com

www.wantedly.com

Laravelのイベント&リスナについてまとめてみた

こんにちは、 株式会社SCOUTERエンジニアの佐藤(@r_sato1201)です

今回は、Laravelのイベント機能についてまとめたいと思います。

Laravelのイベントとは?

ReaDouble(公式リファレンスサイトを和訳したサイト)には以下のように記述してあります。

Laravelのイベントはシンプルなオブザーバの実装で、アプリケーションで発生する様々なイベントを購読し、リッスンするために使用します

参考:Laravel5.7 イベント

Laravelのイベント機能はObserverパターンで実装されています。
Observerパターンとは、観察される側(Subject)と観察する側(Observer)の2つの役割が存在し、観察される側の状態が変化した際に、観察する側に通知されるデザインパターンです。
Laravelではイベント(観察される側)とリスナ(観察する側)とに分かれており、イベントの変化をリスナが補足することで処理を行うという流れになっているようです。

イベント、リスナの登録

ここからは、「アカウント登録した際に、ユーザーに登録完了メールを送信する」機能を例に説明したいと思います。
まずはイベントとリスナの登録です。Laravelでは基本的にEventServiceProviderで管理されているので、listenプロパティに以下のように記述します。

app/Providers/EventServiceProvider.php

/**
 * The event listener mappings for the application.
 *
 * @var array
 */
protected $listen = [
    // アカウント登録
    Events\RegisterAccount::class => [
        Listeners\SendRegisterAccountNotification::class,
    ],
];

イベント、リスナの作成

次は、イベントとリスナの作成です。

php artisan event:generate

上記のコマンドを実行することで、EventServiceProviderに記述してあるイベントやリスナを生成してくれます。なお、既に作成済みのものには、変更を加えません。
app配下にEventsディレクトリとListenersディレクトリが生成され、RegisterAccount.phpとSendRegisterAccountNotification.phpが生成されます。

イベント定義

次に、イベントの定義をしていきましょう。

app/Events/RegisterAccount.php

<?php

namespace App\Events;

use App\Entities\User;
use Illuminate\Queue\SerializesModels;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;

class RegisterAccount
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * @var User
     */
    private $user;

    /**
     * Create a new event instance.
     *
     * @param User $user
     */
    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function getUser()
    {
        return $this->user;
    }
}

メンバ変数のuserを定義して、getUser()でいつでも取り出せるようにしています。

リスナ定義

次に、リスナの定義です。

app/Listeners/SendRegisterAccountNotification.php

<?php

namespace App\Listeners;

use App\Events\RegisterAccount;
use App\Notifications\RegisterAccountMailNotification;

class SendRegisterAccountNotification
{
    /**
     * Handle the event.
     *
     * @param  RegisterAccount $event
     *
     * @return void
     */
    public function handle(RegisterAccount $event)
    {
        $user = $event->getUser();
        $user->notify(new RegisterAccountMailNotification($user));
    }
}

ここでは、Eventでキャッチした$userを、ユーザーにメール送信するNotificationである RegisterAccountMailNotificationに渡しています。
※RegisterAccountMailNotificationの内容はここでは省きます。

ちなみに、app/Entities/User.phpで、Notifiableトレイトを定義しておかないとNotificationが動かないので注意してください。

class User extends BaseModel 
{
    use Notifiable {
        notify as traitNotify;
    }
}

イベント発行

最後に、これまで記述してきたイベント処理を発行させる処理を記述します。
app/Http/Controllers/Api/RegisterController.php

<?php

namespace App\Http\Controllers\Api\Company;

use App\Events\RegisterAccount;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class RegisterController extends Controller
{
    /**
     * Create a new user instance
     *
     * @param array $data
     *
     */
    protected function create(Request $request)
    {
        $user = ([
            'email' => $request['email'],
            'password' => $request['password'],
        ]);

        RegisterAccount::dispatch($user);
    }
}

先程作成したイベントファイル(RegisterAccount)をuseし、dispatchしています。 これで、ユーザーがアカウント登録をした際に、ユーザーに対してアカウント登録メールを送信することができるようになりました。

まとめ

以上で、「アカウント登録した際に、ユーザーに登録完了メールを送信する」機能を一通り実装することができました。

今後は、

・イベントリスナのキュー投入
・ブロードキャスト

などを学び、理解してアウトプットしていけたらと考えています。

さいごに

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

www.wantedly.com

www.wantedly.com

参考資料

saml2awsを使ったセキュアなTerraform管理

saml2awsを使ったセキュアなTerraform管理

こんにちは kotamatです。

本日発売の WEB+DB PRESS Vol.111に 【第2回】コードの書き方の統一 ……PHP_CodeSnifferによる規約への準拠,PHPStanによる静的解析……というタイトルで寄稿させていただいております。

gihyo.jp

PHPをチームで開発されている方にはぜひ読んでいただければと思っております! が、今日は最近触っているインフラに関して、工夫したところを紹介させていただきます。

Terraform の provider管理

インフラ構築をよりセキュアにするために、SARDINE全体的にインフラを再構築しました。 今までTerraformでインフラを構築してきましたが、providerを下記のようにAWS のIAM keyを用いて実装してきておりました。

provider.tf

# Configure the AWS Provider
provider "aws" {
  region     = "ap-northeast-1"
  access_key = "my-access-key"
  secret_key = "my-secret-key"
}

GitHubでインフラ構成を管理しているのですが、もちろんこれだとコードベースにkeyがコミットされてしまい、必要以上のメンバーに共有されてしまいますので、Terraformのvariableを使い、コミットしない形で運用しておりました。

provider.tf

provider "aws" {
  region     = "ap-northeast-1"
  access_key = var.access_key
  secret_key = var.secret_key
}

variable.tf

variable "access_key" {}
variable "secret_key" {}

こうすることによって、対象のユーザごとにIAMユーザを作成し、適切な権限のみを与えることによってユーザを管理する事ができました。不必要になったタイミングで当該ユーザを消したりaccess keyを失効することによってログイン出来ないようにできますが、このような運用は頻度が多くなると大変ですし、なんといってもアカウント発行、失効の属人性も上がり、更にはPC側にkeyが残ってしまうことによって端末紛失などに耐えられない構成となっていることが課題でした。

この問題を解決するために、applyユーザだけはステップサーバーなど別のインスタンスだけで実行できるようにし、自動的にapplyが走るという環境を整備されているところもありますが、いくらplanやvalidateをしているとはいえ、apply時に実行時エラーが出ることもあるため、何かが発生したときの対応が難しく、やはりある程度は手元で実行できるようにしておきたいです。

AWS SSO as SP

そこで、今回導入したのが、SAMLベースでの認証形式を用いた構成です。 AWSにはSSOの仕組みがありますが、通常はIdPをAWSのIAMとした上でのSSOの構成を行うと思います。 ただ、逆にAWSのIAMにIdPを登録することによって、SAML形式かOpenID Connect形式によるAWSへの認証を行うことができます。

詳細の設定方法はAWSのブログが詳細を紹介されておりますので、こちらをご覧ください。

aws.amazon.com

こちら見てもらえるとわかりますが、ユーザごとにSession時間を設定する項目があり、デフォルトは1時間ですが、Terraformの設定中は1時間以上の作業時間を設ける事があると思いますので、こちらの設定項目を随時変更する事によって作業時間中にセッションが切れることがなくなります。

SSOをどうやってつかうのか

通常SSOはコンソール上で使用しているため、webの画面で使用するものかと思います。 しかしsaml2awsというものを使うことによって、任意のプロバイダーでのSSOログインをCLI上のみで行い、かつそのユーザをセッション付きで ~/.aws/credentials に保存することができ、そのユーザを用いてTerraformのproviderを設定することができます。

内部構造としては、go queryを用いてWebページにアクセスし、認証しているようです。

具体的な使用方法

saml2awsの設定

まずはsaml2awsに使用するIdPを設定します。

~/.saml2aws

[gsuite]
app_id               =
url                  = <GSuite側のログインURL>
username             = <GSuiteアカウント>
provider             = GoogleApps
mfa                  = Auto
skip_verify          = false
timeout              = 0
aws_urn              = urn:amazon:webservices
aws_session_duration = 3600 # 秒。必要であれば伸ばす
aws_profile          = saml # 使用したいprofile名
resource_id          =
subdomain            =
role_arn             =

設定後下記のコマンドでログインします。

saml2aws login -a gsuite

もしユーザ名、パスワード入力を省略したい場合は下記のようにします(端末にパスワードが残ってしまうので、あまりおすすめはしません)

saml2aws login -a gsuite --username=username --password='password'

Using IDP Account gsuite to access GoogleApps <GSuite側のログインURL>
To use saved password just hit enter.
? Username username
? Password

Authenticating as username ...
Open the Google App, and tap 'Yes' on the prompt to sign in

MFAを有効にしている場合、こちらが出たら端末側でYesをおします。

? Please choose the role  [Use arrows to move, type to filter]
  Account: XXX / role1 
  Account: XXX / role2 
  Account: XXX / role3 
❯ Account: XXX / role4 
  Account: XXX / role5 
  Account: XXX / role6 
  Account: XXX / role7 

そうすると、当該ユーザに紐付いているroleの一覧が表示されるので、適切なユーザを選択するだけです。

そうすることによって、 ~/.aws/credentialsに saml2awsに設定したprofile名でcredentialが保存されます。

Terraformのproviderを修正

最後にproviderの設定を修正します。

provider "aws" {
  region     = "ap-northeast-1"
-  access_key = var.access_key
-  secret_key = var.secret_key
+  profile = "saml" # saml2awsで設定したprofile名
}

Terraformの設定ファイル上では、saml2awsの内容ではなく、あくまでAWSのprofile名という抽象レイヤーで記述するので、他ツールに移行も簡単なのが便利ですね。

ログイン情報の除去

いくらSTSで期限付きのユーザを発行したとはいえ、ログイン情報をアカウントに保持させ続けるのは不安です。 そういった場合でも、GSuiteなどのIdP側のAttributeから、当該ロールを除外してあげるだけで、そのユーザからは二度とログインができなくなり、実質Terraformの実行権限がなくなります。

まとめ

AWS STSを用いたログイン方法を紹介させていただきました。 Terraformのベストプラクティスは結構各々実装しており散在している印象です。 この記事がなにかの手助けになれば幸いです。

最後に

弊社ではエンジニアを募集しております!

ぜひご応募ください!

www.wantedly.com