はじめに
Enterpriseにおいて「お前は誰か?」を確認する手段は非常に多岐にわたる。
セキュリティと絡んで手段は拡大傾向にあり、新しい認証手段への追従が求められるケースは多い。
自前で認証情報を保有、管理し、セキュリティの保証を担保した手順を用意するのは不可能に近い。
現実的には認証情報の保有と管理、および認証手段を専用のプラットフォームに移譲させたい。
実際、認証の泥臭いプロセスはIdP(Identity Provider)が面倒を見てくれる。
SnowflakeはIdPと薄く関係して、IdPによる認証結果を使い回すことができる。
SnowflakeはIdPがどういったプロセスで認証したのかは一切関与しない。
認証後、「お前にこの権限を与えて良いか?」を実装しなければならない場合、
アプリ側に機能サポートがなければ、コードでそれを保証しなければならない。
Snowflakeは、ここをExternal OAuth統合として汎化しフルにサポートしている。
具体的には、SnowflakeはExternal OAuth統合として汎化していて、
OAuth2.0認可サーバと統合し、RBACとの紐付けまでを面倒みてくれる。
RBACの最小範囲であるスキーマより細かい粒度を区別する場合でなければ、
RBACだけで区別が完了することとなり、大幅な工数削減と品質安定化を達成できる。
昔Fitbit APIのOAuth2.0フローを実装した時から始まり、
過去に何件かWebアプリ開発で認証認可まわりの実装をしたと思う。
Webアプリの認証認可F/Wはかなり枯れていて、正直中身を知らなくても書けてしまう。
開発者人口が少ないSaaSサービスであるSnowflakeがブラックボックス化した
認証認可の仕組みを読み解くのは、Webアプリのそれとは次元の違う大変さがある。
(こと認証認可の文脈では安全性の保証がセットとなるため)
Snowflake External OAuthについて厳密に調べる機会があったので、
生成AIを使わず100%自分の思考と言葉で記事を起こしていく。
【目次】
- ∨はじめに
- ∨認証(AuthN)
- ∨認可(AuthZ)
- ∨External OAuth
- ∨External OAuthの認証部分の基本フロー
- ∨External OAuthの認可部分、スコープ
- ∨External OAuth特有のセキュリティの抜け穴と対策
- ∨カスタム認証サーバーの構成・トークンペイロード要件
- ∨カスタム認証サーバーの構成・セキュリティ統合の作成
- ∨カスタム認証サーバーの構成・テスト
- ∨公式対応認証サーバーと非公式(カスタム対応)の違い
- ∨まとめ
認証(AuthN)
認証、つまり、Authenticationは、「お前は誰か」を確認すること。
IdPにID/PWを登録しておきID/PWを入力したりMFAを通ることで「確かに〇〇さんだ」と確認すること。
単一要素認証(SFA)、多要素認証(MFA)、パスキー認証、FIDO2認証、他、多様な認証方式がある。
またシングルサインオン(SSO)、により組織を跨ぐ連携を行うことができる。
サービス間のSSO方式としてSAML2.0、API等のSSO方式としてOIDC2.0が広く使われている。
顧客管理のIdPによる認証を本IdPに引き継ぐIDフェデレーションにより組織間認証連携を実現できる。
認可(AuthZ)
一方認可、つまり、Authorizationは、「お前にこの権限を与えて良いか」を確認すること。
認可とは「誰がどのデータにどんなルールでアクセスして良いか」をコントロールする設計パターン。
「ルール作りの設計思想」と「システム間で権限をやり取りする技術規格」がごっちゃに扱われがち
だが、レイヤが異なる2つの話を分けておくと少しわかりやすくなる。
「ルール作りの設計思想」
例えば以下のようにルールを定める。
- ロールベースアクセス制御/Role Based Access Control
- ユーザ個人ではなく役割に対して権限を付与しユーザをそのロールに所属させる方式。管理者権限のユーザには作成・削除を与え、一般権限のユーザには閲覧のみを与えるなど、一般的な認可方式。SnowflakeのロールモデルはまさにRBACに基づく。
- 属性ベースアクセス制御/Attribute Based Access Control
- ロールだけでなくユーザの所属、勤務地、アクセスする時間帯、デバイスの種類など、複数の属性(コンテキスト)を組み合わせて動的に認可を判断する方式。
「システム間で権限をやり取りする技術規格」
例えば以下のようにルールを実現する技術規格を表す。
- OAuth2.0
- 現在のWebで最も普及している「トークンベース」の認可フレームワーク。認可サーバーが発行した「アクセストークン(時限式のカードキー)」をアプリが提示し、リソースサーバー(Snowflakeなど)がそれを検証してアクセスを許可する。「権限の証明書」としてJWT(JSON Web Token)が実際にやり取りされる。JWTは、SON形式のデータを暗号論的に署名したもので、中身に「ユーザー名」「有効期限」、「付与されたロール(権限スコープ)」などが書き込まれている。
- ケルベロス認証・認可 (Kerberos)
- 主に一昔前からの 社内ネットワーク(Active Directory)環境などで広く使われている方式。チケット」と呼ばれる暗号化されたデータをやり取りすることで一度のログインで社内のファイルサーバーやプリンタなどの利用権限(認可)をシームレスに得る。
あああ
External OAuth
External OAuthは顧客のOAuth2.0認可サーバを統合してシームレスなSSOを実現する。
認証プロセスはサービス側が気にするものではなく、本機能は認可の統合であることに注意すること。
なお公式(外部 OAuth の概要)は間違いなく認証・認可と言う言葉をごっちゃにしている。
OAuth2.0はRFC6749でThe OAuth2.0 Authorization Frameworkと定義されている。
受け渡しされるトークンはOIDCのような認証トークンではなく、OAuth2.0の認可トークンである。
外部OAuthという(認可の)仕組みをSnowflakeに設定しておくことで、
「外部のIdPが認証したという証明書」をSnowflakeが安全に受け取ってデータアクセス認可する仕組みだ。
公式(外部 OAuth の概要)によると、以下に公式に対応している。
公式にない場合は、外部 OAuth 用のカスタム認証サーバーを構成するで構成できる。
なお「公式」でないからといって「非対応」ではない。「公式」になくても汎用OAuth2.0用のカスタム認証サーバーとして構成できる。
- Okta – 外部OAuth用Oktaの構成
- Auth0はOktaファミリーだが↑では構成できない。カスタム認証サーバーとして構成が必要
- Microsoft EntraID – 外部 OAuth 用 Microsoft Entra ID の構成
- Ping Identity PingFederate – 外部 OAuth 用 Microsoft Entra ID の構成
- Microsoft PowerBI – Power BI SSO からSnowflakeへ
公式にはExternal OAuthのメリットとして以下が挙げられている。
- トークンの発行を認証サーバーに委任し、発行されたトークンの管理に集中できるようになる。
- ログイン時のセキュリティルール(MFAやIP制限、承認フローなど)を、Prj IdP側に統合できる。
- ユーザがその認証と許可に関する厳しいルール(テスト)をクリアしない限り、IdPはトークンを発行しない。
- 怪しいユーザはSnowflakeの入り口にすら辿り着けず、データは完璧に守られる。
- 認証をIdPに持たせることでSnowflake側から認証情報を除去できるためセキュアになる。
一見して認証のことしか書かれていないようだが、implicitに認可が書かれている。
Snowflakeは認可をIdPに完全に移譲し、認証とセットで認可が行われたトークンを確認するだけ、
ということは、Snowflake側に認可コードを一切書くことなしに認可を実現することと同義。
External OAuthの認証部分の基本フロー
公式に基本フローの図が貼ってある。ステップ1だけ構成時にのみ行う。他は都度実行される。
最初にセキュリティ統合の構成と、アプリ内の実装が開発者側の責務となる。
ベスプラに従ってルールから逸脱しないように構成することで、後はSaaSサービス間の自動連携となる。
- 外部OAuth認証サーバとSnowflakeのセキュリティ統合を構成し信頼性を確立する
- ユーザはアプリを介してSnowflakeにアクセスしようとする。アプリはユーザを確認しようとする
- 認証サーバはOAuthトークンをアプリに返す
- SnowflakeドライバはOAuthトークンを使用して接続文字列をSnowflakeに渡す
- SnowflakeはOAuthトークンを検証する
- Snowflakeはユーザ検索を実行する
- Snowflakeはユーザのロールに基づいてセッションをインスタンス化する
External OAuthの認可部分、スコープ
いきなり「スコープ」というワードが出てくるが、これ、JWTの”scope”キー/バリューのこと。
OAuth2.0においてJWTで認可範囲を設定するのだ、という理解と記憶がなければ読めない。
JWTは以下のような構成となっておりscopeを格納する場所がある。
認可サーバ側で何らかの許可処理の結果、ユーザのスコープが決まり、Snowflakeに送られる。
このトークンがSnowflakeに届くと、Snowflakeはscopeキーのバリューを読み取り、
「このユーザにはST_USER_ROLEというロール(権限)を適用してセッションを始めるべき」と判断する。
{
"iss": "https://your-project-idp.auth0.com/",
"sub": "user_12345",
"email": "user@client.com",
"exp": 1719100000,
"scope": "session:role:ST_USER_ROLE" <-- 🌟これが「スコープ」
}
Okta, PingFederate, カスタムの場合は以下のパターンを使用しなければならない。
| スコープ | 説明 |
|---|---|
| session:role:<custom_role> | Snowflakeのカスタムロールにマップする。例えばsession:role:ST_USER_ROLEで、ST_USER_ROLEにマップ |
| session:role:public | Snowflakeの PUBLIC ロールにマップ |
| session:role-any | 外部OAuthサーバでのSnowflakeロール管理を行わない場合これを渡す。特定のロールを固定せず、そのユーザに付与されているロールであれば、ログイン後に自由に切り替えて(USE ROLEして)使って良い、という少し緩めの認可 |
なお、以下のビルトインロールはデフォルトではブロックされる。
- ACCOUNTADMIN
- GLOBALORGADMIN
- ORGADMIN
- SECURITYADMIN
Snowflake OAuthは、セッション中のロールのセカンダリロールへの切り替えをサポートしていないが、
External OAuthでのセカンダリロールの使用はサポートしている。
External OAuth特有のセキュリティの抜け穴と対策
Snowflakeにおいて、アカウントレベルでネットワークポリシーによりIP制限をかけていたとしても、
External OAuthと合わせて構成するSecurity Integrationを経由してログインしてくる場合、
そのユーザ個人のIP制限が無視されてしまう、という仕様がある。
つまり、IdP側のIP制限が破られたり、トークンが盗まれたりした場合、
攻撃者はどこからでもSnowflakeのデータにアクセスできてしまう状態になる。
Snowflakeは、External OAuth自体にもネットワークポリシーを直接紐づけることを推奨している。
具体的にはSecurity Integrationにネットワークポリシーを直接紐づける。
これによりIdPから届いたトークンであっても、ネットワークポリシーで許可されたIPアドレス以外からの
リクエストであれば、Snowflakeはセッションを開始しない。
これはIdPフェデレーション等で複雑化したユーザ組織の通信経路を全て把握する必要性を言っている。
こういうの、デフォルトで安全側に振って欲しいなとは思う。
カスタム認証サーバーの構成・トークンペイロード要件
カスタム認証サーバーがSnowflakeに送信するアクセストークンには、下表が含まれている必要がある。
| クレーム | 説明 |
|---|---|
| scp | Snowflake のカスタムロールを指定する文字列が含まれていること。値として session:role:ST_USER_ROLE のような Snowflake 指定の形式の文字列を、配列またはスペース区切りの文字列で必ず埋め込まなければならない。 |
| scope | 同上。IdPプロダクトによりscpかscopeのどちらかを入れる。 |
| aud | Snowflake アカウントの完全な URL(https://<アカウント識別子>.snowflakecomputing.com)が含まれている必要がある。 |
| exp | 有効時間。トークンの有効期限が UNIX タイムスタンプ(エポック秒)で刻まれている必要がある。Snowflake はトークンを受け取った瞬間の時刻とこの exp を比較します。有効期限が過去の時刻になっている(期限切れ)場合は、その時点で認可を即座に拒否する。 |
| iss | 発行者。アクセストークンを発行したプリンシパルを文字列 URI として識別。つまりトークンを発行した IdPのアイデンティティ(例: https://your-project-idp.auth0.com/)。最後のスラッシュ(/)の有無まで1文字違わず一致させる必要がある。Snowflake 側の EXTERNAL_OAUTH_ISSUER で指定した文字列と完全に一致する必要がある。 |
| iat | 発行時刻。必須。JWT が発行された時刻を識別 |
カスタム認証サーバーの構成・セキュリティ統合の作成
External OAuth を実現する Snowflakeのリソースの実体。
カスタム認証サーバからのアクセストークンと安全に通信して検証し、アクセストークンに
関連付けられたユーザーロールに基づいてSnowflakeへのアクセスをユーザに提供する。
create security integration external_oauth_custom
type = external_oauth
enabled = true
external_oauth_type = custom
external_oauth_issuer = ''
external_oauth_rsa_public_key = ''
external_oauth_audience_list = ('', '')
external_oauth_token_user_mapping_claim = 'upn'
external_oauth_snowflake_user_mapping_attribute = 'login_name';
それぞれの内容は下表の通り。
| パラメータ | 説明 |
|---|---|
| EXTERNAL_OAUTH_ISSUER | 外部認証サーバー(IdP)を一意に識別するURL(発行元URL)を指定する。IdPから発行されるアクセストークン(JWT)の iss クレームの値と完全に一致する必要がある。 |
| EXTERNAL_OAUTH_JWS_KEYS_URL | 外部認証サーバーが公開している、デジタル署名の検証に必要な公開鍵(JWKS)が配置されたURLを指定する。SnowflakeはこのURLにアクセスしてトークンの妥当性を検証する。 |
| EXTERNAL_OAUTH_TOKEN_USER_MAPPING_CLAIM | 外部認証サーバーが発行するアクセストークン(JWT)の中で、ユーザーの識別情報(メールアドレスやユーザーIDなど)が格納されている「キー(クレーム名)」を指定する。 |
| EXTERNAL_OAUTH_SNOWFLAKE_USER_MAPPING_ATTRIBUTE | トークンから抽出したユーザー識別情報を、Snowflake側の USER オブジェクトのどの属性(EMAIL_ADDRESS または LOGIN_NAME)と一致させるかを指定する。 |
カスタム認証サーバーの構成・テスト
公式では、最短パスで構成を検証するため、curl で HTTP Post を送る手順が書かれている。
- IdP側にテストユーザを作成しておく。テストユーザはパスワードを持つ必要がある
- Snowflake側にも、上記と同じメールアドレス(または識別子)を持つ USER オブジェクトを事前に作っておく。login_name, または emailでマッピングする
- IdP側の画面でこのテスト用のアカウントを作成し、専用のClientID, ClinetSecretを取得する
- 次のように、 OAuth 2.0クライアントがカスタムトークンエンドポイントに POST リクエストすることを許可
- OAuth 2.0の用語でいう grant_type = password(Resource Owner Password Credentials Grant)方式を使うこと。すなわち「リソース所有者に設定された付与タイプ」であり、アプリ画面を介さず、ユーザーのID/PWを直接リクエストに含めてトークンを即時発行してもらう、テスト専用の最短ルートを構築する。
- 準備で用意したclientID と clientSecretをHTTP Basic認証ヘッダーに含めること
- リクエストのBody(送信データ)には、FORM形式(application/x-www-form-urlencoded)で、テストユーザーのID/PWと、Snowflakeに渡したいスコープを指定すること
curl -X POST -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" \
--user : \
--data-urlencode "username=" \
--data-urlencode "password=" \
--data-urlencode "grant_type=password" \
--data-urlencode "scope=session:role:analyst" \
公式対応認証サーバーと非公式(カスタム対応)の違い
公式対応認証サーバーと、非公式(カスタム対応)の違いをまとめてみる。
ケース1:IdPの「署名用公開鍵」がローテーション(変更)されたとき
JWT(トークン)が偽造されていないかを証明するための「公開鍵」は、
セキュリティ担保のために数ヶ月ごとに自動で新しいものにローテーションするのが一般的。
公式対応の場合、SnowflakeがOkta側の鍵更新スケジュールや新しい公開鍵の
取得先をあらかじめ知っているため、Snowflake側が自動で追従する。
開発者は何のアクションも起こす必要はなく、システムは止まらない。
カスタム、つまり非公式の場合であっても基本的には指定したURL
(.well-known/jwks.json)を見に行ってくれるので自動追従するが、
もしIdP側のメジャーアップデート等で「公開鍵を配置するURLの仕様そのもの」
が変わった場合は、Snowflakeの設定パラメータ(EXTERNAL_OAUTH_JWS_KEYS_URL)
を開発者が手動で新しいURLに書き換えるまで、認証・認可がすべてエラーになってシステムが停止する。
IdP側のセキュリティ仕様やエンドポイントの仕様が変更されたとき
近年、サイバー攻撃の高度化に伴い、IdP側(OktaやMicrosoftなど)がトークンの発行ルールや、
検証用APIの仕様(プロトコル)をより安全なものへ強制アップデートすることがある。
SnowflakeはOktaやMicrosoftと強固な技術パートナーシップを結んでいるため、
IdP側の仕様変更がリリースされる前に、Snowflake側の「特急レーン(専用プログラム)」を
事前にアップデートして追従させる。そのため、開発者がコードや設定を修正することなく、
シームレスに新しいセキュリティ基準へ移行できる。
カスタム、つまり非マネージドの場合、Snowflakeは「汎用的なOAuth 2.0の標準規格(RFC)」に
準拠していることしか保証しない。そのため、IdP側が独自のセキュリティ拡張を行ったり、
標準規格の解釈を変更したりした場合、トークンのペイロード構造(キー・バリュー)が変わり、
Snowflakeがトークンを解読できなくなるリスクがある。
この場合、開発者がIdP側の設定を手動で修正して追従する必要がある。
まとめ
SnowflakeにおけるExternal OAuth統合の仕組みを「認証」と「認可」のレイヤを分離して読んでみた。
認証・認可を完全にIdPに移譲し、Snowflakeアプリケーション内で一切の認可コードを書かずに済む。
数あるIdPのうち、いくつかについてはSnowflakeが公式対応している。
公式IdP構成はテクノロジーパートナーシップに基づき、Snowflakeのマネージド構成の一部として、
Snowflake側がIdP側の変更に自動追従する可能性が高い。結果としてダウンタイムの発生を回避できる。
公式対応IdPでなくても、OAuth2.0 RFC準拠の認証サーバとしてカスタム連携することができるが、
SnowflakeがIdP側の変更に自動追従する性質ではなく、運用者・開発者がIdP側の変更に適用する必要がある。