使用Rails和Google OAuth操作他人的AWS环境的方法

你好!我是hagurere。这次在个人开发中,我有一个需求是从Rails服务器操作终端用户的AWS环境,所以我进行了一些方法的研究并实现了。因为这是一个不太常见的需求,我没有找到类似的文档,所以在这里留下来备忘。

概括

根据用户输入的IAM角色和由Google OAuth2生成的id_token,通过AWS::STS::Client.assume_role_with_web_identity发行临时的access_key,并使用该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 'omniauth'
gem 'omniauth-rails_csrf_protection'
gem 'omniauth-google-oauth2'
gem 'redis'
gem 'redis-actionpack'

运行”bundle install”命令,创建”omniauth.rb”文件。

为了获得id_token,我将openid作为scope加入。

# 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

我会按照画像的样子进行制作。

请将Google的client_id放入Audience中.

在添加许可后,您可以附加您喜欢的政策,并给其起一个合适的名称和标签,这样就完成了。

通过IAM角色和id_token生成临时的access_key,并访问S3。

我们将从这里开始使用Rails控制台进行验证。Rails控制台真是方便呢!

我认为在实际实现时,我们需要在库的内部创建一个类来实现。

请将在认证过程中生成的id_token放入。

irb(main):001:0> google_access_token = 'eyJhbGから始まるid_token'

使用Aws::STS调用assume_role创建角色。

请将刚才创建的IAM角色的ARN放入role_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">

只需要使用创建的凭据像往常一样获取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的需求,就会像本次实现的方式一样。

感谢您一直以来的观看。

我们愿意接受补充和建议,欢迎留下您的评论!

广告
将在 10 秒后关闭
bannerAds