LoginSignup
0
0

RailsとGoogle OAuthで他者のAWS環境を操作する方法

Posted at

こんにちは!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取得

スクリーンショット 2023-11-05 18.38.17.png

まず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にアクセスしたら動いてくれるはずです。

こんな感じの画面が出たら成功です。
スクリーンショット 2023-11-05 19.15.53.png

IAMロールの発行

FireShot Capture 005 - ロールを作成 - IAM - Global - us-east-1.console.aws.amazon.com.png

画像のように作成します。

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を操作するような要件があったりすると今回やったような実装になると思います。

最後まで見てくださってありがとうございます。

補足やアドバイス等受け付けておりますのでぜひコメントしてください!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0