From aec9457276242ec4e7ab3c4a9b77ae180b479e6d Mon Sep 17 00:00:00 2001 From: AWS SDK for Ruby Date: Tue, 3 Dec 2024 19:09:12 +0000 Subject: [PATCH] Merge customizations for DSQL --- gems/aws-sdk-dsql/CHANGELOG.md | 4 + gems/aws-sdk-dsql/lib/aws-sdk-dsql.rb | 22 +++++ .../lib/aws-sdk-dsql/customizations.rb | 3 + .../customizations/auth_token_generator.rb | 70 ++++++++++++++++ .../spec/auth_token_generator_spec.rb | 81 +++++++++++++++++++ gems/aws-sdk-dsql/spec/spec_helper.rb | 18 +++++ gems/aws-sdk-rds/CHANGELOG.md | 2 + .../customizations/auth_token_generator.rb | 30 +++---- .../spec/auth_token_generator_spec.rb | 69 +++++++--------- 9 files changed, 246 insertions(+), 53 deletions(-) create mode 100644 gems/aws-sdk-dsql/CHANGELOG.md create mode 100644 gems/aws-sdk-dsql/lib/aws-sdk-dsql.rb create mode 100644 gems/aws-sdk-dsql/lib/aws-sdk-dsql/customizations.rb create mode 100644 gems/aws-sdk-dsql/lib/aws-sdk-dsql/customizations/auth_token_generator.rb create mode 100644 gems/aws-sdk-dsql/spec/auth_token_generator_spec.rb create mode 100644 gems/aws-sdk-dsql/spec/spec_helper.rb diff --git a/gems/aws-sdk-dsql/CHANGELOG.md b/gems/aws-sdk-dsql/CHANGELOG.md new file mode 100644 index 00000000000..4543654fca5 --- /dev/null +++ b/gems/aws-sdk-dsql/CHANGELOG.md @@ -0,0 +1,4 @@ +Unreleased Changes +------------------ + +* Feature - Add an `AuthTokenGenerator` to generate auth tokens for `DbConnect` and `DbConnectAdmin` actions. diff --git a/gems/aws-sdk-dsql/lib/aws-sdk-dsql.rb b/gems/aws-sdk-dsql/lib/aws-sdk-dsql.rb new file mode 100644 index 00000000000..c1fd64f46f4 --- /dev/null +++ b/gems/aws-sdk-dsql/lib/aws-sdk-dsql.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +# WARNING ABOUT GENERATED CODE +# +# This file is generated. See the contributing guide for more information: +# https://github.com/aws/aws-sdk-ruby/blob/version-3/CONTRIBUTING.md +# +# WARNING ABOUT GENERATED CODE + + +require 'aws-sdk-core' +require 'aws-sigv4' + +Aws::Plugins::GlobalConfiguration.add_identifier(:dsql) + +module Aws::DSQL + # this should get replaced by build + + GEM_VERSION = '1.0.0' +end + +require_relative 'aws-sdk-dsql/customizations' diff --git a/gems/aws-sdk-dsql/lib/aws-sdk-dsql/customizations.rb b/gems/aws-sdk-dsql/lib/aws-sdk-dsql/customizations.rb new file mode 100644 index 00000000000..ea6a8569510 --- /dev/null +++ b/gems/aws-sdk-dsql/lib/aws-sdk-dsql/customizations.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +require 'aws-sdk-dsql/customizations/auth_token_generator' \ No newline at end of file diff --git a/gems/aws-sdk-dsql/lib/aws-sdk-dsql/customizations/auth_token_generator.rb b/gems/aws-sdk-dsql/lib/aws-sdk-dsql/customizations/auth_token_generator.rb new file mode 100644 index 00000000000..cb37f1b2d05 --- /dev/null +++ b/gems/aws-sdk-dsql/lib/aws-sdk-dsql/customizations/auth_token_generator.rb @@ -0,0 +1,70 @@ +# frozen_string_literal: true + +require 'aws-sigv4' + +module Aws + module DSQL + # A utility class that generates an auth token that supports database + # logins for DSQL clusters. IAM credentials are used for authentication + # instead of the database password. + class AuthTokenGenerator + # @option options [Credentials] :credentials An object that + # responds to `#credentials` returning another object that responds to + # `#access_key_id`, `#secret_access_key`, and `#session_token`. + def initialize(options = {}) + @credentials = options.fetch(:credentials) + end + + # Generates an auth token for the DbConnect action. + # + # @param [Hash] options + # @option options [String] :region The AWS region where the DSQL Cluster + # is hosted. Defaults to the region of the client. + # @option options [String] :endpoint The DSQL endpoint host name. + # @option options [Integer] :expires_in (900) The number of seconds the + # presigned URL is valid for. + # @return [String] + def generate_db_connect_auth_token(options = {}) + presigned_token(options, 'DbConnect') + end + + # Generates an auth token for the DbConnectAdmin action. + # + # @param [Hash] options + # @option options [String] :region The AWS region where the DSQL Cluster + # is hosted. Defaults to the region of the client. + # @option options [String] :endpoint The DSQL endpoint host name. + # @option options [Integer] :expires_in (900) The number of seconds the + # token is valid for. + # @return [String] + def generate_db_connect_admin_auth_token(options = {}) + presigned_token(options, 'DbConnectAdmin') + end + + private + + def presigned_token(options, action) + region = options.fetch(:region) + endpoint = options.fetch(:endpoint) + + param_list = Aws::Query::ParamList.new + param_list.set('Action', action) + + signer = Aws::Sigv4::Signer.new( + service: 'dsql', + region: region, + credentials_provider: @credentials + ) + + presigned_url = signer.presign_url( + http_method: 'GET', + url: "https://#{endpoint}/?#{param_list}", + body: '', + expires_in: options[:expires_in] + ).to_s + # Remove extra scheme for token + presigned_url[8..-1] + end + end + end +end diff --git a/gems/aws-sdk-dsql/spec/auth_token_generator_spec.rb b/gems/aws-sdk-dsql/spec/auth_token_generator_spec.rb new file mode 100644 index 00000000000..a65b9352d44 --- /dev/null +++ b/gems/aws-sdk-dsql/spec/auth_token_generator_spec.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require_relative 'spec_helper' + +module Aws + module DSQL + describe AuthTokenGenerator do + let(:generator) do + AuthTokenGenerator.new( + credentials: Credentials.new('akid', 'skid') + ) + end + + describe 'initialize' do + it 'requires :credentials' do + expect { AuthTokenGenerator.new }.to raise_error(KeyError) + end + end + + describe 'generate_db_connect_auth_token' do + it 'requires region and endpoint' do + expect do + generator.generate_db_connect_auth_token(region: 'us-west-2') + end.to raise_error(KeyError) + expect do + generator.generate_db_connect_auth_token( + endpoint: 'peccy.dsql.us-east-1.on.aws' + ) + end.to raise_error(KeyError) + end + + it 'generates a valid token' do + now = Time.parse('20240827T000000Z') + allow(Time).to receive(:now).and_return(now) + + region = 'us-east-1' + endpoint = 'peccy.dsql.us-east-1.on.aws' + token = generator.generate_db_connect_auth_token( + region: region, + endpoint: endpoint, + expires_in: 450 + ) + expect(token).to match(/#{endpoint}\/\?Action=DbConnect/) + expect(token).to match(/X-Amz-Credential=akid%2F#{now.utc.strftime('%Y%m%d')}%2F#{region}%2Fdsql%2Faws4_request/) + expect(token).to match(/X-Amz-Expires=450/) + expect(token).not_to match(/http[s?]:\/\//) + end + end + + describe 'db_connect_admin_auth_token' do + it 'requires region and endpoint' do + expect do + generator.generate_db_connect_admin_auth_token(region: 'us-west-2') + end.to raise_error(KeyError) + expect do + generator.generate_db_connect_admin_auth_token( + endpoint: 'peccy.dsql.us-east-1.on.aws' + ) + end.to raise_error(KeyError) + end + + it 'generates a valid token' do + now = Time.parse('20240827T000000Z') + allow(Time).to receive(:now).and_return(now) + + region = 'us-east-1' + endpoint = 'peccy.dsql.us-east-1.on.aws' + token = generator.generate_db_connect_admin_auth_token( + region: region, + endpoint: endpoint, + expires_in: 450 + ) + expect(token).to match(/#{endpoint}\/\?Action=DbConnectAdmin/) + expect(token).to match(/X-Amz-Credential=akid%2F#{now.utc.strftime('%Y%m%d')}%2F#{region}%2Fdsql%2Faws4_request/) + expect(token).to match(/X-Amz-Expires=450/) + expect(token).not_to match(/http[s?]:\/\//) + end + end + end + end +end diff --git a/gems/aws-sdk-dsql/spec/spec_helper.rb b/gems/aws-sdk-dsql/spec/spec_helper.rb new file mode 100644 index 00000000000..c449bbc276f --- /dev/null +++ b/gems/aws-sdk-dsql/spec/spec_helper.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +# WARNING ABOUT GENERATED CODE +# +# This file is generated. See the contributing guide for more information: +# https://github.com/aws/aws-sdk-ruby/blob/version-3/CONTRIBUTING.md +# +# WARNING ABOUT GENERATED CODE + +require_relative '../../aws-sdk-core/spec/shared_spec_helper' + +$:.unshift(File.expand_path('../../lib', __FILE__)) +$:.unshift(File.expand_path('../../../aws-sdk-core/lib', __FILE__)) +$:.unshift(File.expand_path('../../../aws-sigv4/lib', __FILE__)) + +require 'rspec' +require 'webmock/rspec' +require 'aws-sdk-dsql' diff --git a/gems/aws-sdk-rds/CHANGELOG.md b/gems/aws-sdk-rds/CHANGELOG.md index 2b982083bf4..17a3106085c 100644 --- a/gems/aws-sdk-rds/CHANGELOG.md +++ b/gems/aws-sdk-rds/CHANGELOG.md @@ -1,6 +1,8 @@ Unreleased Changes ------------------ +* Feature - Support `expires_in` option for the `AuthTokenGenerator`. + 1.261.0 (2024-12-02) ------------------ diff --git a/gems/aws-sdk-rds/lib/aws-sdk-rds/customizations/auth_token_generator.rb b/gems/aws-sdk-rds/lib/aws-sdk-rds/customizations/auth_token_generator.rb index b373b94ae38..78b2273a9f7 100644 --- a/gems/aws-sdk-rds/lib/aws-sdk-rds/customizations/auth_token_generator.rb +++ b/gems/aws-sdk-rds/lib/aws-sdk-rds/customizations/auth_token_generator.rb @@ -10,7 +10,7 @@ module RDS # # @see https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html class AuthTokenGenerator - # @option options [required, Credentials] :credentials An object that + # @option options [Credentials] :credentials An object that # responds to `#credentials` returning another object that responds to # `#access_key_id`, `#secret_access_key`, and `#session_token`. def initialize(options = {}) @@ -19,19 +19,20 @@ def initialize(options = {}) # Creates an auth login token. # - # @param [Hash] params The parameters for auth token creation. - # @option params [required, String] :region Region where the database - # is located. - # @option params [required, String] :endpoint Hostname of the database - # with a port number. - # For example: my-instance.us-west-2.rds.amazonaws.com:3306 - # @option params [required, String] :user_name Username to login as. - # + # @param [Hash] options The options for auth token creation. + # @option options [String] :region The region where the database + # is located. + # @option options [String] :endpoint The hostname of the database + # with a port number. + # For example: my-instance.us-west-2.rds.amazonaws.com:3306 + # @option options [String] :user_name The username to login as. + # @option options [Integer] :expires_in (900) The number of seconds the + # token is valid for. # @return [String] - def auth_token(params) - region = params.fetch(:region) - endpoint = params.fetch(:endpoint) - user_name = params.fetch(:user_name) + def generate_auth_token(options) + region = options.fetch(:region) + endpoint = options.fetch(:endpoint) + user_name = options.fetch(:user_name) param_list = Aws::Query::ParamList.new param_list.set('Action', 'connect') @@ -47,11 +48,12 @@ def auth_token(params) http_method: 'GET', url: "https://#{endpoint}/?#{param_list}", body: '', - expires_in: 900 + expires_in: options[:expires_in] ).to_s # Remove extra scheme for token presigned_url[8..-1] end + alias_method :auth_token, :generate_auth_token end end end diff --git a/gems/aws-sdk-rds/spec/auth_token_generator_spec.rb b/gems/aws-sdk-rds/spec/auth_token_generator_spec.rb index a71611efa15..507aef65eb7 100644 --- a/gems/aws-sdk-rds/spec/auth_token_generator_spec.rb +++ b/gems/aws-sdk-rds/spec/auth_token_generator_spec.rb @@ -4,76 +4,67 @@ module Aws module RDS - describe AuthTokenGenerator do - - let(:generator) { + let(:generator) do AuthTokenGenerator.new( - credentials: Credentials.new('akid', 'skid') + credentials: Credentials.new('akid', 'secret') ) - } + end describe 'initialize' do - - it 'requires :credentials parameter' do - expect { - AuthTokenGenerator.new - }.to raise_error( - KeyError - ) + it 'requires :credentials' do + expect { AuthTokenGenerator.new }.to raise_error(KeyError) end - end - describe 'auth_token' do - + describe 'generate_auth_token' do it 'requires region, endpoint, username to create a token' do - expect { - generator.auth_token( + expect do + generator.generate_auth_token( region: 'us-west-2', user_name: 'user' ) - }.to raise_error( - KeyError - ) - expect { - generator.auth_token( + end.to raise_error(KeyError) + expect do + generator.generate_auth_token( region: 'us-west-2', endpoint: 'prod-instance.us-east-1.rds.amazonaws.com:3306' ) - }.to raise_error( - KeyError - ) - expect { - generator.auth_token( + end.to raise_error(KeyError) + expect do + generator.generate_auth_token( endpoint: 'prod-instance.us-east-1.rds.amazonaws.com:3306', user_name: 'user' ) - }.to raise_error( - KeyError - ) + end.to raise_error(KeyError) end it 'generates a valid token' do - now = Time.now + now = Time.parse('20240827T000000Z') allow(Time).to receive(:now).and_return(now) - region = 'us-west-2' + region = 'us-east-1' endpoint = 'prod-instance.us-east-1.rds.amazonaws.com:3306' - user_name = 'mySQLUser' - token = generator.auth_token( + user_name = 'peccy' + token = generator.generate_auth_token( region: region, endpoint: endpoint, - user_name: user_name + user_name: user_name, + expires_in: 450 ) - expect(token).to match(/#{endpoint}\/\?Action=connect/) - expect(token).to match(/DBUser=#{user_name}/) + expect(token).to match(/#{endpoint}\/\?Action=connect&DBUser=#{user_name}/) expect(token).to match(/X-Amz-Credential=akid%2F#{now.utc.strftime('%Y%m%d')}%2F#{region}%2Frds-db%2Faws4_request/) + expect(token).to match(/X-Amz-Expires=450/) + expect(token).not_to match(/http[s?]:\/\//) end - end + describe 'auth_token' do + it 'is an alias for generate_auth_token' do + expect(generator.method(:auth_token)) + .to eq(generator.method(:generate_auth_token)) + end + end end - end end