使用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。
首先,我们将从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,它应该能正常运行。
给予IAM角色
我会按照画像的样子进行制作。
请将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的需求,就会像本次实现的方式一样。
感谢您一直以来的观看。
我们愿意接受补充和建议,欢迎留下您的评论!