Got Some \W+ech?

Could be Japanese. Could be English. Android, セキュリティ, 機械学習などをメインに、たまにポエムったり雑感記載したりします。

広義のIAMをどうAWSで実装するかメモ: 基礎編

今までIAMをなんとなく使っていたので、ちゃんと勉強し直して自分(ひいては所属先)の指針について考えを残していきたい。基本的には、2020/02/14にSecurity-JAWSで話した内容の補足だが、時間が足りずに話せなかったSCPについても書いた。

当日のスライドはこちら↓。

speakerdeck.com

Identity-Based Policy と Resource-Based Policyについて

基本的にIdentity-Based (IAM) を念頭に置くのが良いとおもわれる。理由としては主に以下の通り。

  1. クロスアカウント時にも応用が効く
  2. 今後やることになるであろうABACにはIdentity-Basedでやるしかない
  3. 「誰がどこに何をできる」をResource側でチェックするのが大変

もしResourceが同じアカウントにあるのであれば、明示的なPolicyをResource側に定義しなければ、Identity-Based側の設定が全て反映されるはずだ。 自組織内クロスアカウントでの利用は基本的に単一アカウントでの利用より少ないことが予想される。そうであれば引き続きPolicyの主役はIdentity-Basedであり、Resource-based Policyは補助的な役割になるはずだ。

設定の粒度は組織の規模や分業レベル、ひいてはResourceの性質にもよるので一概にはいえない。しかし、自分が所属しがちな組織(~400人)ぐらいでは、とりあえず "Principal": {"AWS": “arn:aws:iam::xxxxx:root"} でクロスアカウント元を指定する以上に厳しい要件があったことはない。

一方、DataDogのような外部サービスや主体からのアクセスがある場合、当然それらのIdentity-Based Policyは管理化にないため必然的にResouce-Baed Policyを充実させるしかない。  

SCP (Service Control Policy)

Service Control PolicyはAWS Organizationレベルで管理できるポリシーのことだ。Organizationレベル

SCPでEffect Denyの用途

Organization全体で統一されたPolicyを、Accountレベルで適用(Attach)する、というのがSCPの一番の効果だと考えられる。 Attachする先を個別のAccountか選択することもできる。全アカウントを指定することも可。 いずれもMasterアカウントに対しての効果はない。

Security Hub, Cloud Trail, Config, Guarddutyなど全体に適用させて設定を変更されたくないリソースや、IMDSのようなv2だけに利用を限定さえたいときには、 SCPでEffect Denyすることが有効に思える。Terraformのリソースも容易されているので*1、可視性も担保されるだろう。

SCPでEffect Denyの制限

例えば、EC2:*をDenyするSCPを作ったとしよう。

{
    "Version": "2012-10-17",
    "Statement": [{
            "Sid": "Statement1",
            "Effect": "Deny",
            "Action": ["ec2:*"],
            "Resource": ["*"]
    }]
}

公式ドキュメント*2に書かれている通り、AWS Orgのマスターアカウントには適用されない。つまり、上記のようなEC2へのアクセスをメンバーアカウントでは拒否できるものの、マスターアカウント内はマスターアカウントのIAMあるいはResourceのポリシーがダイレクトに反映される。 そのため、やはりマスターアカウントでの操作はなるべく減らす && 最小権限の法則というベストプラクティスは、SCPを有効にしても変えるべきではない。

SCPでEffect Allowを使いたいとき

SCPを有効にするとデフォルトでは、以下のポリシー(FullAWSAccess)がオンになっている。

{
    "Version": "2012-10-17",
    "Statement": [{
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
    }]
}

もし、厳密なホワイトリストポリシーを運用したいのであれば当該SCPをDetachして、明示的に許可したいリソースをAllowするSCPを作成し、Attachすればよい。 ただSCP側でAllowされたアクセス制御はDenyと違って適用されない。実際のアクセスを許可するには、AWSメンバーアカウント側のIAMポリシーで許可しなければいけない。

AWS SSO vs IAM Assume RoleでのFederation

IAM Roleと違い、AWS SSOはタグをつけられない。つまり、現時点でABACはできない。 また、Permissionに利用するPolicyはAWS SSO内で使いまわしできず、またPermissionと1:1で運用するしかない。 将来的にやりたいことと、現時点でやりたいことにおいて、穴があると言わざるをえない。 ただポテンシャルはあると思っていて、もしAWSがログイン元をAWS SSOにする内部方針を持っているのであれば、 両方の課題解決は時間の問題かもしれない。

今の状況(所属先の状況も含めて)をふまえて個人的に選ぶのであれば、AWS SSOかなあ、とは思っている。 ABACのためのタグポリシーや運用を決めきって広げ終わるくらいには、十分改善されるんじゃないかな。 駄目だったらIAM Roleに寄せればいいや、とおもっているくらい。

その他

IMDSに対する所感

難しい。メタデータサービスにアクセスしたいニーズはあると思う。 ノードから集中管理サーバーにタグを渡して、ノード一覧画面のラベルとして表示させるOSSは何度かみたことある。 管理方法としてはいまいちだが、あるものはあるのだからしょうがない。 その場合、iptablesで169.254.169.254へのアクセスを遮断するのよりもいい方法はないものか、とは思う。全インスタンス 余計なプロセスを動かさないコンテナとそのタスクにSCPを使ったPolicy制限をまるっとかけるのが現実的では?って思うけどどうだろう。

https://docs.amazonaws.cn/en_us/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html#instance-metadata-v2-how-it-works

Internet Identity Workshop(Fall 2019)に初めて参加してきた話

ちょっと真面目にIdentity系に集中しようと思い、今年の5月にEuropean Identity Conferenceに行ったところ、 プロの方に「Internet Identity Workshopにも行ったほうがいい」と進められたので、ほいほい来てしまった話をします。

IIWとは

IIWは毎年2回、マウンテンビューで開催されるアイデンティティ関係の「アン(Un)カンファレンス」です。

アンカンファレンスが何かと言うと、一言でいうとカンファレンス形式ではない会合みたいなものです。 通常のカンファレンスですと、事前にカテゴリーを決定し、CfPと審査を経てコンテンツが決定されます。 一方、アンカンファレンスでは、そのような事前準備はありません。当日に、参加者が話したい内容を提案し、その聞きたい内容がホスティングされている部屋におもむくような形で進行されます。

具体的には、まず朝09:00に始まると、参加者全員が輪になって座ります。

f:id:kengoscal:20191003101833j:plain
輪になって踊らりはしない

何か喋りたい内容がある人は、その場でアブストラクトを発表し、時間と場所を確保しにいきます。

f:id:kengoscal:20191003101423j:plain
セッションを決める時間

当日の発表内容が決まると、一覧化されます。参加者は時間になったらこのボードから、行く場所を決める寸法です。

f:id:kengoscal:20191003102432j:plain
セッション一覧

発表中は、発表者以外がノートを取る役を果たします。取られたノートは、下記のページで公開されます。

Past IIW’s – IIW

このように進むIIWですが、今回は第29回目とかなりの歴史を持ち、過去最大の参加者数(300名)がいらしたそうです。 さらに人数が増えるとセッション数が増えて、もしかしたらアンカンファレンスでは運営できなくなったりするんでしょうかね...

セッションの内容

セッションの一覧は、先述のリンクにありますが、内訳は下記のような感じでした。

  • SSI/DID系が5割
  • OAuth/OIDC系が4割
  • プライバシー含むその他が1割

また、認証の話は少なく、全体的にIdentity Assuranceの話が多かったようなイメージです(DID Authはあったかな)。 自分は、基本的にOAuth/OIDC系に参加しつつ、時間があいたらSSI/DIDに足を運んでいました。今おもうと、もっとデモ見にいきゃよかったな、という気持ちもあります。

  • Beyond Bearer Token
  • Verifiable Credential based Authn via OIDC
  • Push Authorization Request (OAuth)
  • OAuthXYZ
  • How Web Browser is impacting Identity Flows
  • Me2B Relationship Management
  • Machine Identities
  • DID Architectural Implementation (Hyperledger Aries)
  • OIDC Identity Assurance
  • CIBA
  • Deep Dive to OAuth XYZ and DID

この(アン)カンファレンスのレベルは高く、発表者も聴講者も、OAuth/OIDCなどの仕様を一度は実装かつ運用した経験を議論の土台としている様に感じられました。GAFAMのA・Mを始めとしたSaaS系のIdentityチームや、そういったサービスを展開してる人ばかりが、英語で議論・質疑応答しているので、なかなか難しかったです。自分は、議論自体はなかなかできず、議論のたねになるような質問を投げるくらいしかできなかったです。 英語については大丈夫そうなので、次回までにもうちょい議論に加われるといいんだが。

LastPass EnterpriseにAzure AD経由でSSOできるようになった話

TLDR

LastPass Entepriseに対してAzure ADからOIDCできるようになった

序文

企業でパスワードManager使ってますよね? え?使ってない?じゃあ、このブログ読んでる場合じゃないので、とりあえず導入してきてください。

さて、IdPを導入しようが、SSO対応してないwebサイトや、そもそもWeb以外のパスワードを要するシステムなんてものは星の数ほどあります。 そういったパスワードを運用するに際に、我々人類は「パスワードは使い回すな!複雑なものを使え!」といった敎育をされてきました。 もちろん、人類の脳みそは有限なので、そこへの負担を軽減するパスワードマネージャーは、企業活動には必須アイテムです。

パスワードマネージャーの課題(の1つ)

大抵のパスワードマネージャーは、作る・保存する・共有するといった欲張り3点セットを備えていて大変よろしいのですが、厄介な点が1つありました。 それは、パスワードマネージャーそのものにパスワードを要することです。 パスワードマネージャーの認証に通らないと管理されたパスワードにアクセスできないなんてのはそりゃそうなんですが、IdPを用意する(べき)エンタープライズ内の管理者と利用者にとって、パスワード管理とMFAせにゃならんサービスが1つ増えるだけであって、そこについてはちっとも嬉しくありません。

ところが、そんなシステム管理者の気持ちをよそにパスワードマネージャーはIdPへの昇格をもくろんでいたのか、かたくなにIdPからのフェデレーション機能の実装を拒んできました。むしろパスワードマネージャーからIdPへのフェデレーションをする機能に力を注いでいた気配すらします。

ちなみにパスワードマネージャーの1つであるDashlaneはSAML 2.0使えますが、そもそも彼らはSOC2 Type IIをとってなかったりで、エンタープライズに利用できるかというとチョット...

LastPass Took a Step Forward

しかし、私が所属している企業が導入したLastPass Enterpriseでは、そんな状況を打破するような機能をリリースしました。それが、待望のIdPからのOIDC連携です。現時点ではAzure ADしかサポートされていませんが、まあ当社はAzure ADにしてるから全く問題ありません。やったぜ。

流れ

GIFを貼ろうとしたところHatenaではエラーがでたので、スクショでお見せします。

f:id:kengoscal:20190917224341p:plain
エクステンションからメアドを入力する

f:id:kengoscal:20190917224428p:plain
AzureADログイン画面にリダイレクト

f:id:kengoscal:20190917224456p:plain
え、まだyubikeyつかってないんですか?

f:id:kengoscal:20190917224536p:plain
パスワードレスログイン最高

f:id:kengoscal:20190917224611p:plain
フェデレーションされてLastPassもログイン完了

このようにAzure ADと組み合わせることで、LastPassにSSOできます。 また、Azure ADはパスワードレス認証(あまり好きな言い方ではないのだが...)もサポートしているため、 人間が覚えるべきパスワードを極限まで減らせます。最高。

さらに、LastPassの素晴らしいところは、どのユーザーにフェデレーションを適用するかを管理者側で選択できることです。 つまり、Administratorだけ除外しておけば、仮にAzure ADが落ちてフェデレーションできなくなったとしても、LastPassを継続利用するためのバイパス経路を用意できます(賛否両論あり)。

また、ユーザーの適用先が選択できるということは、段階的なリリースが可能になります。手順書などの不備やエッジケースが見つけやすくなります。

こういう細かいケアがあるのが嬉しいですね。

方法

手順は公式に用意されているので、そちらをご参照ください。

Set Up Federated Login for LastPass Enterprise Using Azure Active Directory

その他

LastPassのエクステンションに脆弱性があったというニュースがありますが、ちゃんと脆弱性発見者とコラボして修正・リリース、リリースされたものはエンドユーザー側で自動更新されるので、ブラウザ側であえて自動更新をオフるみたいなことしない限り何も問題ないと思います(LastPass脆弱性があったことだけをもって、騒がれないように)。

その他2

Response Type 「token id_token」なImplicit Grantなんだよなぁ...

「Secure旅団」は技術書典7に参加します

「こういうセキュリティもあるんだぜ!」という著者の思いを読者に届け、「お、こういうセキュリティもあるんだ!」というWowと新しい知識を少しでも多くお届けする同人誌を作ります。セキュリティであればジャンルに拘らずバイナリからWeb、機械学習からレガシー、設計からエモ内容など様々な内容を提供します。

TLDR

  • 技術書典7に「Secure旅団」として出典します
  • 場所は「お51C」です
  • 著者は第一線で活躍している方がそれぞれ自信が注目・取り組んでいるセキュリティのことを好きに書きます

f:id:kengoscal:20190831181448p:plain
位置

techbookfest.org

主題

当サークルの目的

私が参加している「セキュア旅団」の技術書典7の当確が決まりました。当日は「お51C」にいますので是非起こしください。

当サークルは名前の通り、セキュリティをメインに扱うサークルです。 ただし、それを1つのトピックに限定せず、あえてバラバラにしています。 というのも、当サークルは「こういうセキュリティもあるんだぜ!」という著者の思いをのせ、購入いただいた読者に「お、こういうセキュリティもあるんだ!」と少しでもセキュリティの多様性とWowをお届けする同人誌を目指しているためです。 そのため、セキュリティであればジャンルに拘らずバイナリからWeb、機械学習からレガシー、設計からエモ内容など様々な内容を提供します。 各トピックは、おそらくそれ単体で1つの同人誌が作れるレベルのクオリティでしょう。

最終的には、著者も読者も等しく当サークルの参加者と考えており*1、文章を通じて読み手と書き手が交わることで、一見特別に見えるセキュリティと他の領域の間にある壁を薄くし、交差する領域を実社会でも増やせれば最高です。

今回のトピック

過去の作品

boothよりお求めいただけます。

https://booth.pm/ja/search/セキュア旅団

*1:書いててこれがコミュニティ?って思った

セキュリティ・キャンプ全国大会2019に講師として参加してきた

今年は僭越ながら講師として招かれ、セキュリティ・キャンプ全国大会2019に参加してきました。

www.ipa.go.jp

自分はユーザー企業のセキュリティ担当としての立場から、「運用と開発」トラックの一部を担当し、「ユーザー企業における情報システムとセキュリティ」というタイトルで講義させていただきました。 4時間という長丁場でしたが、受講生の皆様にご参加いただいて嬉しかったです。 また、運営の皆様にも色々支援いただきましたので、この場をかりてお礼申し上げます。 ありがとうございました。

講義の内容

「運用と開発」トラックは「[世の中を自分たちの力で変えていきたい] (https://www.ipa.go.jp/jinzai/camp/2019/zenkoku2019_characteristic.html) 」といった強い目的を持った参加者が、 「きちんと運用する 」 ためのスキルを身につけるトラックでした。

そこで、 私は「世の中を変える」を「新しい価値を生み出す」ことと仮定し、価値を持続的に提供し続けるうえで、その障壁となるリスクに対してどのように俯瞰的にアプローチするかをテーマにしました。

昨今、スタートアップと大企業のような異なる規模、あるいは業種、時として金融産業と非金融産業がサービス提携するなど、いわゆるConnected Industriesな時代は既存産業の可能性を拡張すると同時に、 従来では想定しえなかった攻撃の道を舗装してしまいました。 皆さんもご存知の通り浸透しつつあった様々なFintech系サービスやHR系サービスで、そういったリスクの顕在化がここ1.5年で相次いでいます。

残念な事例ではありますが、価値を生み出す際には「Done is Better Than Perfecet」な思想だけではなく、同時にリスク対策も進めねばならないことが明白になったのではないしょうか。

そういった背景をふまえて、私が勤務していた証券会社やFintech企業での勤務経験や同僚から学んだことをベースに、前半では「法令・戦略・戦術・設計」で全体像を把握し、後半では「設計」について深堀りしたのが、私の講義です。  

前半: 法令・戦略・戦術・設計

総合的なリスク対策をするには、まず業界のルールをそもそも知らなければなりません。 法令(Act、Regulation)や標準(スタンダード、ガイドライン)はリスクベースな考え方になりつつあるとはいえ、 リスク受容するにはそれを知った上で受容しなければいけないからです。 本講義では、金融商品取引業者としての証券会社に関わる法令(例: 金商法、個人情報保護法)の紹介をしつつ、「金融商品取引業者等向けの総合的な監督指針」および「FISC安全対策基準」を標準あるいはガイドラインをして、遵守すべきルールを提示しました。

そして「FISC安全対策基準」と同じくリスクベースを前提にしている「Cyber Security Framework」を経営レベルでのアプローチとし、個別具体的な対策としては「ATT&CK」を、それぞれサイバーセキュリティ戦略・戦術として紹介しました。

後半: BeyondCorp

全体戦略と戦術はさておき、とるべき設計(あるいはアーキテクチャ)についてはここ十数年の間に大きくかわりました。

DMZをインターネットと社内NWの間に設置することで生まれた「Trusted Zone」をトラストアンカーとするビジネスIT環境は、以下のような移り変わりをしてきたかと思います。

  • 2003年頃: 経理・顧客管理などの業務システムのSaaS化 (Salesforceなどの)
  • 2006年頃: 基幹システムのSaaS化(GSuite, AWSなど)
  • 2010年頃: iPhoneの業務活用
  • 2014年頃: 大手企業とスタートアップの提携開始(第四次スタートアップブーム)
  • 2016年頃: リモートワークの広がり

このようにビジネス環境が、自社のTrusted Zoneから従来Untrusted Zoneとされていた領域に比重を移していったことにより、トラストアンカーとしての「Trusted Zone」の再定義を見直さざるをえない時代になっています。

そういった時代背景とともに、社内システムにおいてもセキュリティ設計を見直さなければいけません。 本講義では、新しいビジネス環境における設計の一例として「BeyondCorp」を解説いたしました。 具体的には論文内からBeyondCorpのエッセンスを抽出し、それぞれの要素、現実世界における各種ソリューション、そして現時点での制限(挑戦)を私の知る限り詰め込みました。

このようにして、新しい世の中を作る才能が、同時に発生するリスクへ俯瞰的なアプローチをできる教材を提供しました。

講義資料

公開にあたって特定の情報を削る・マスクするなどしています。 もし気になるポイントがあればDM ( @ken5scal )などに連絡ください。

speakerdeck.com

個人の感想

  • 応募者には全員参加してもらいたかった。選抜するときはかなり悩んだ。
  • 4時間分の資料を、0から作り上げるのを個人の時間でやるのは死んだ。
  • もし次のチャンスがあれば、ハンズオン形式にしたい。
  • リハやっていなければ即死だった。

願わくば受講生のみなさんがセキュリティの業務に就いたとき、「そういえばあの環境では @ken5scal があのような発言してたな」と、一瞬思い出していただければいいと思います。

Frontend/BackendのOAuth2.0クライアント書いてみた

個人的に認証・認可まわりに興味を持ち出して以来、RFCやドキュメントを読みまくっていた。しかしながら、仕事が忙しかったり、そもそもここらへんを仕事でやるポジションにいないため、ちゃんと実装してみないことにはどうにもならんな、と思いだした。よって、最終的なゴールを雑なFAPI*1準拠したOAuth/OIDCシステムを実装していくことにした。具体的には以下の順番でやろうとしている。認証はもしかしたら、以前つくったFIDO2サーバー使うかも。

  • OAuth2.0クライアント(Code Grantのみ)
  • OAuth2.0認可サーバー
  • OAuth2.0リソースサーバー
  • FAPI Part1化
  • OIDC化
  • FAPI Part2化

まずは、OAuth2.0クライアントを雑に作成した。ある程度できたので、一旦、棚卸しもかねてブログを書く。 その過程で湧いた疑問は、解を求める終わりのないRFC・ドキュメント漁りの旅にでるよりも、所感をこの場に残して、棚にあげようと思う。

構成

Front: Vue。Authorization Requestを送信する。

GitHub - ken5scal/oauth-client-front

Back: Go。Token Requestを送信する。

GitHub - ken5scal/oauth-client-back

AS: Okta Preview。rfc6749に則ってて扱いやすかった。 RS: なし

f:id:kengoscal:20190607015111p:plain

なぜ、フロントエンドとバックエンドを分けたのかと言うと、

  1. FAPI R&Wが認可レスポンスのパラメタインジェクション攻撃やIdP Mix Up攻撃への緩和策として、OIDC Hybrid Flowを必用としているから
  2. 過去いた会社では、どのようなステージだろうがビジロジック部(バックエンド)とUI部(フロントエンド)を担当するチームが別だったから

といったところから、分割している。

実装(一部)

  • Authorization Requestを送るVue Component(/pages/index.vue)
<template>
<v-btn color="info" @click="requestForAuthorizationCode">Authorize</v-btn>
</template>

<script>
export default {
data() {
    return {
      unreserverdChars: //★PKCEで使うChar郡。RFC3986 Sec2.3参照
        '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz-._~'
    }
  },
mounted() {
    // because it's in dev, secure attribute is set as false
    const state = uuid() ★stateとして作成。本当はRedisに保存したい。
    Cookie.set('SessionID', state, {
      secure: process.env.NODE_ENV !== 'development'  
    })
  },
methods: {
    // https://tools.ietf.org/html/rfc7636
    requestForAuthorizationCode() {
      let codeVerifier = ''

      const l = this.unreserverdChars.length
      for (let i = 0; i < 128; i++) { //★PKCEのCode Verifierは48~128長だが、長くてなんの問題があるんじゃい、と128固定
        codeVerifier += this.unreserverdChars[Math.floor(Math.random() * l)]
      }
      this.$store.commit('oauth/setVerifier', codeVerifier) ★後につかうのでVuex保存。保存先はSession Storage。

      window.crypto.subtle
        .digest('SHA-256', new TextEncoder().encode(codeVerifier)) ★Code Challenge生成。Plain使ったらPKCEの意味ないよね...なくない...?Sha256できない環境が想像できない。
        .then(digestValue => {
          const codeChallenge = window
            .btoa(
              new Uint8Array(digestValue).reduce(
                (s, b) => s + String.fromCharCode(b),
                ''
              )
            )
            // base64 to base64url
            .replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=/g, '')

   // ★Authorization Request
          window.location.href =
            process.env.oktaAuthzEndpoint +
            '?client_id=' +
            process.env.oktaClientId +
            '&response_type=code&scope=openid offline_access' +
            '&redirect_uri=' +
            process.env.oktaAuthzRedirectURL +
            '&state=' +
            Cookie.get('SessionID') +
            '&code_challenge_method=' +
            'S256' + // For Now
            '&code_challenge=' +
            codeChallenge
        })
        .catch(err => {
          // console.log(err)
        })
    }
  }
</script>
  • 認可サーバーから認可コードを送り、それをバックエンドに連携するVue Component(/pages/callback.vue)
<script>
import axios from 'axios'

const Cookie = process.client ? require('js-cookie') : undefined

export default {
  mounted: function() {
{
    // Checking CSRF attack on the `code` during the Authorization Response
    if (this.$route.query.state !== Cookie.get('SessionID')) { ★認可コード差し込み対策
      this.$root.error({
        statusCode: 403,
        message: 'State is invalid. Suspected to be CSRFed.'
      })
    } else {
      this.authzResponse = this.$route.query
      axios
        .post(
          'http://localhost:9000/token', ★
          JSON.stringify({
            authz_code: this.$route.query.code,
            code_verifier: hoge // this.$store.getters['oauth/getVerifier']★PKCE: 認可コード横取り対策
          }),
          {
            headers: {
              'Content-Type': 'application/json'
            }
          }
        )
        .then(res => {
          this.tokenResponse = res.data
        })
        .catch(err => {
          this.tokenResponseError = err.response.data
        })
      this.$store.commit('oauth/removeVerifier')
    }
  }
}
</script>
  • Token Requestを送るGoバックエンド。この後、microservice的なデプロイも試してみたいので別リポジトリに分割。
func init() {
    tomlInBytes, err := ioutil.ReadFile("config.toml")
    if err != nil {
        log.Fatal().AnErr("Failed reading config file", err)
    }

    config, err := toml.LoadBytes(tomlInBytes)
    if err != nil {
        log.Fatal().AnErr("Failed parsing toml file", err)
    }

    // Maybe server config
    port = strconv.FormatInt(config.Get("env.dev.port").(int64), 10)

    oauthConfig = oauth2.Config{
        ClientID: config.Get("env.dev.as.okta.client_id").(string),
        ClientSecret: os.Getenv("CLIENT_SECRET"),
        RedirectURL: config.Get("env.dev.as.okta.callback").(string),
        Endpoint: oauth2.Endpoint {TokenURL: config.Get("env.dev.as.okta.token_endpoint").(string)},
    } //ConfigFromJSONの ConfigFromJSONが参考になる
}

func handleTokenRequest(w http.ResponseWriter, r *http.Request) {
    defer r.Body.Close()
    var b struct {
        AuthzCode string `json:"authz_code"`
        CodeVerifier string `json:"code_verifier"`
    }

    if err := json.NewDecoder(r.Body).Decode(&b); err != nil {
        w.WriteHeader(http.StatusInternalServerError)
        fmt.Fprintln(w, err.Error()) ★
        return
    }

    opt := oauth2.SetAuthURLParam("code_verifier", b.CodeVerifier)★PKCEのパラメタはOption指定すればおk
    token, err := oauthConfig.Exchange(context.Background(), b.AuthzCode, opt) ★OAuth2パッケージ便利
    if err != nil {
        w.WriteHeader(http.StatusBadRequest) 
        fmt.Fprintln(w, err) ★本当はrfc6749 sec5.2の通りパースしたかったが無理そう。だってこれだし↓。PRが出てるのは確認ずみ
                 //oauth2: cannot fetch token: 401 Unauthorized
        //Response: {"error":"invalid_client","error_description":"Client authentication failed. Either the client or the client credentials are invalid."}
        return
    }

    tokenForFront := &struct {
        AccessToken  string    `json:"access_token"`
        TokenType    string    `json:"token_type"`
        RefreshToken string    `json:"refresh_token,omitempty"`
        Expiry       time.Time `json:"expiry"`
    }{
        AccessToken: token.AccessToken,
        TokenType: token.TokenType,
        Expiry: token.Expiry,
    }

    w.Header().Set("Content-Type", "application/json;charset=UTF-8")
    w.Header().Set("Cache-Control", "no-store")
    w.Header().Set("Pragma", "no-cache")
    w.WriteHeader(http.StatusOK)
    json.NewEncoder(w).Encode(tokenForFront)
}

メモ・疑問・所感

response_type の話

最新ドラフトの「OAuth2.0 Security Best Current Practice」*2によると、クライアントはImplicitグラントタイプや"token id_token"および"code token id_token"といった認可レスポンス内にアクセストークンを入れるものを実装すべきでないと書いてある。このベストプラクティスに従うと、FrontendとBackendで分けたHybrid Flow構成の場合*3、"code id_token"くらいしかオプションが残らない。まあ、それは構わないのだが、もし"code id_token"を使うのであれば、Backend側にしかアクセストークンが保持されないため、Implicit GrantなどによるFrontend上で起きやすかったアクセストークンの漏洩について気にするポイントは減らすことができる。その場合、アクセストークンを sender-constrained するMTLSやToken Bindingが果たしてFAPIのRead & Write Profileでも必要なのだろうか?という疑問はある。それを踏まえたうえで必要なのかもしれない。よくわからん。気にするより、実装を進めることにした。

PKCE の話

「OAuth2.0 Security Best Current Practice」ではImplicit Grantを使うべきではない(Should Not)と書いてあるので、基本はCode Grantになる。その場合、PKCEが必須になる*4ため、実装した。ネイティブアプリではカスタムスキーマによって認可コード横取りされる経路が明示的にあるもののhttpsスキーマ使うクライアントアプリなら大丈夫じゃない??とも思ったが、PKCEは実装も管理も比較的ライト(だと思う)なので、特に違和感はなかった。

あと、code_verifierってどこに保存するのがベストなんだろう。クッキーにいれるのもちゃうし、ローカルストレージはWindow閉じても消えないし...で、ライフサイクルとしては短い(と思う)ため、XSSなどによる攻撃を受けるリスクは少ないということから、一旦 Session Storageに格納。

セッションとトークンの話

ネット上でJWTをセッション管理にすればいいじゃん!いやだめだ!という議論をよく見るが、そもそもJWT*5ってクレームのフォーマットであり、どうも議論自体の論点がよくわからん...という気持ちが強かった。今でもよくわかってないのが正直なところだが...で、いざOAuth2.0クライアントを実装してみると、別物という結論に自分の中で達し、Cookieを使う方針にした。ID Tokenを取得して検証が成功したら、クッキーを足せばいいんじゃないだろうか。

golang/oauth2/について

便利。最初はクライアントもスクラッチで書いちゃろ!と思ってたが、Exchangeの中身が素直だったのと、ASをOktaだけ使う場合は単純なhttp requestにしかならないので辞めた。個人的にはToken RequestのエラーがError型で帰ってくるのは辛かった。パースしてえ。まあ、ASによって仕様が違うからしょうがない。ただ、それに関するPRが4月に作成されているが反応がない。ほかのissue/prへの反応が2019年にはいってから低速化してるように見えるが、気のせいだろうか。あと、ASによって異なる仕様を拾うための仕組みが参考になった。ただし、全体的な設計は知識不足からかよくわからない。なぜ、ここにInterfaceを配置したのか、なぜモジュールを分けたのかなど....

github.com

フロント

フロント何もわからん。技術書典で、SPA構成を使ったWebアプリの本をパクり、Nuxtでやってみた。 お守りに猫本も脇において読んだが、完全にFront初心者の自分には無理で、猫本どころか公式docもさっぱりだった。 次の本でことなきをえた。公式docも「あーあれね、知ってる知ってる(汗」というところまでは理解できるようになった。

いちばんやさしい Vue.js 入門教室

いちばんやさしい Vue.js 入門教室

ところで現時点だと、フロントにもClient IDをもたせているが、これはフロントの起動のタイミングでバックエンドに取得させにいったほうがいいと思う。が、コンテナオーケストレーションしないとハードル高そうなので、後回し。

インフラ的な構成

RedisとかDBをたててセッション情報やトークンの管理をしたり、Docker-Composeでデプロイしようと思ったが、後回し。 とりあえず、OAuth/OIDCをやりきるのを優先する。

アプリケーションの適切な設計・コード

弱い。エラーハンドリング、テスト等...

フロントサーバーとバックエンドサーバーの認証・認可

認証はx509でSPIFFEEを使うつもり。認可だが、Microservice in OAuthのイメージがわからない。European Identity Conferenceで「ユーザーが使うOAuthとMicroservice間で使うOAuthの違いはなんだ」と聞いたところ、「そこに違いはない」と言われた。ずっと考えていたが、やはり意味がわからない。この答えは10月のIIWで求める必用がありそうだ。

次は

認可サーバー作成する

GCP Next 2019で発表されたセキュリティ機能をAWSのそれとマッピングする

cloud.google.com

Twitterでは収まりきらなそうなので、手を動かすとき用のメモを残しておきます。 カテゴリはしてるけどMECEさは気にしてません

Access Transparency

IaaSプロバイダーによるアクセス履歴のトラッキング。この度、Approvalがでることで承認制にすることもできるようになった。

  • GCP: Access Transparency, Access Approval
  • AWS: NA (CloudTrailじゃないよなぁ...)

DLP

Data Loss Prevention。AWS Macieはまだ東京regionにはまだきてないハズ

  • GCP:
    • VPC Service Control: GCS/BigQueryのデータをVPC内に留めるデータにおける境界予防(Prevention)  * DLP User Interface
  • AWS: Macie

SOC

GCPは基本Cloud Security Command Centerに集約

Prevention

  • GCP
    • Security Health Analytics
    • Cloud Security Scanner: XSSのようなアプリレイヤーにおける脆弱性スキャナー
  • AWS
    • AWS Config
    • AWS Control Tower
    • ※Cloud Security Scanner相当はない(ハズ)

Detection/Response

  • GCP
    • Event Threat Detection
  • AWS
    • GuardDuty

IR Orchestration

  • GCP: Stackdriver Incident Response and Management
  • AWS: Security Hub

API Security

クラウドネイティブセキュリティ (コンテナセキュリティ)

クラウドネイティブセキュリティは @ken5scal が勝手に読んでるだけ。冒頭に紹介したブログだとSupply Chainと書かれていたが、個人的に本質はアプリをコンテナでデプロイする際のライフサイクル全体におけるセキュリティだと思っている。 ので、次の記事から用語を参照してマッピングした

www.cncf.io

コンテナレジストリのセキュリティ

Deployment (deploy-time security Control)

  • GCP: Binary Authorization
  • AWS: NA

ランタイムセキュリティ

  • GCP: GKE Sandbox (gVisorベース)
  • AWS: NA

コンテナホストのセキュリティ(Runtime Environment Hardening?)

CIAM (Customer IAM)

  • GCP: Identity Platform (Firebase Authenticationとは統合しなさそう)
  • AWS: NA (Cognito?)