こんにちは!hagurereです。今回は個人開発中にエンドユーザーのAWS環境をRailsサーバーから操作する用件があったので方法を調べて実装してみました。なかなかない要件で同じようなことをやっているドキュメントが見当たらなかったのでここに残しておきます
概要
google-oauth2で発行されるid_tokenとユーザーから入力してもらったIAMロールを元にAWS::STS::Client.assume_role_with_web_identityから一時的なaccess_keyを発行し、それを使ってs3からオブジェクトを取得します。
※ 検証用の簡易的な実装ですので細かいことは考慮してません
環境
rails(7.0.4)
ruby(3.1.4)
aws-sdk-s3 (1.136.0)
omniauth(2.1.1)
omniauth-google-oauth2(1.1.1)
redis(4.8.1)
google認証とid_token取得
まずomniauth-google-oauth2でのOIDC認証から作っていきます。
適当なrailsサーバーを立ち上げてsessionを使えるようにしておいてください。自分はredis使ってます。
google apiからoauth2の設定をしてclient_idを発行しておいてください。
google apiコンソール側の設定の仕方は公式ドキュメント見てください (てきとーですみません)
gemを追加します。
gem 'omniauth'
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-google-oauth2'
gem 'redis'
gem 'redis-actionpack'
bundle installしてomniauth.rbを作成します。
id_tokenを発行してもらうためにscopeにopenidを入れてます。
# config/initializes/omniauth.rb
Rails.application.config.middleware.use OmniAuth::Builder do
provider :google_oauth2,
ENV['GOOGLE_OAUTH_CLIENT_ID'],
ENV['GOOGLE_OAUTH_CLIENT_SECRET'],
{
scope: 'openid,profile,email,offline',
prompt: 'select_account consent',
access_type: 'offline',
provider_ignores_state: Rails.env.development? # セッションストアのドメイン問題でCSRFエラーが出るため開発環境のみtrue
}
end
OmniAuth.config.allowed_request_methods = [:post, :get]
session_store.rbを作成します。
host = ENV.fetch("SESSION_STORE_REDIS_HOST")
port = ENV.fetch("SESSION_STORE_REDIS_PORT")
db = ENV.fetch("SESSION_STORE_REDIS_DB")
servers = ["redis://#{host}:#{port}/#{db}/session"]
Rails.application.config.session_store :redis_store,
servers:,
key: '_chocozap_mypage_session',
secure: Rails.env.production?,
httponly: true,
same_site: :strict
routes.rbはこんな感じです。
Rails.application.routes.draw do
get '/auth/:provider/callback', to: 'sessions#create'
get 'auth/failure', to: 'sessions#failure'
end
sessions_controllerを作成します。検証用なので超てきとーです。render inlineってめっちゃ使いやすくないですか?笑笑
※ 実際に実装する場合はaccess_token, id_token, refresh_tokenの扱いには注意してください
class SessionsController < ApplicationController
def create
info = request.env['omniauth.auth']['info']
session[:name] = info['name']
session[:email] = info['email']
session[:image] = info['image']
session[:access_token] = request.env['omniauth.auth']['credentials']['token']
session[:refresh_token] = request.env['omniauth.auth']['credentials']['refresh_token']
session[:id_token] = request.env['omniauth.auth'].extra[:id_token]
render inline: "name: #{session[:name]}, email: #{session[:email]}, id_token: #{session[:id_token]}"
end
def failure
render inline: 'failure', status: 401
end
end
.envはこんな感じです。
SESSION_STORE_DISABLE_REDIS_CLUSTER=true
SESSION_STORE_REDIS_HOST=redis
SESSION_STORE_REDIS_PORT=6379
SESSION_STORE_REDIS_DB=0
GOOGLE_OAUTH_CLIENT_ID=xxxxxxxxxxxxx.apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET=xxxxxxxxx
これで/auth/google-oauth-2にアクセスしたら動いてくれるはずです。
IAMロールの発行
画像のように作成します。
AudienceにはGoogleのclient_idを入れてください
許可を追加で好きなポリシーをアタッチして適当な名前,タグをつけたら完成です。
IAMロールとid_tokenから一時的なaccess_keyを発行してs3にアクセス
ここからはrails consoleで検証していきます。rails consoleって便利ですよねー
実際に実装する際にはlibの下にクラス作って実装する形になると思います
認証時に発行されるid_tokenを入れてください
irb(main):001:0> google_access_token = 'eyJhbGから始まるid_token'
Aws::STSを呼び出してassume_roleを作成します。
role_arnには先ほど作成したIAMロールのARNを入れてください。
irb(main):002:0> sts_client = Aws::STS::Client.new(region: 'us-east-1')
irb(main):003:2* resp = sts_client.assume_role_with_web_identity({
irb(main):004:2* role_arn: "arn:aws:iam::111111111111:role/aws-assumerole-test",
irb(main):005:2* role_session_name: "適当な文字列",
irb(main):006:2* web_identity_token: google_access_token
irb(main):007:0> })
=>
#<struct Aws::STS::Types::AssumeRoleWithWebIdentityResponse
先ほど作成したassume_roleを使ってAws::Credentialsオブジェクトを作成します
irb(main):008:1* credentials = Aws::Credentials.new(
irb(main):009:1* resp.credentials.access_key_id,
irb(main):010:1* resp.credentials.secret_access_key,
irb(main):011:1* resp.credentials.session_token
irb(main):012:0> )
=> #<Aws::Credentials access_key_id="ASIA3GTOZJWOHDDUPQKS">
あとは作成したcredentialを使っていつも通りs3オブジェクトを取得するだけです。
あらかじめs3に検証用のファイルを置いておいてください
17行目でhogeという文字列が出力されています。s3に置いたファイルの中身なので成功です。
irb(main):013:1* response = s3.get_object(
irb(main):014:1* bucket: 'aws-assumerole-test',
irb(main):015:1* key: 'hoge.txt'
irb(main):016:0> )
=>
#<struct Aws::S3::Types::GetObjectOutput
...
irb(main):017:0> puts response.body.read
hoge
=> nil
まとめ
ユーザーのAWS環境を操作というのはあまりないと思いますが管理画面からAWSを操作するような要件があったりすると今回やったような実装になると思います。
最後まで見てくださってありがとうございます。
補足やアドバイス等受け付けておりますのでぜひコメントしてください!