pg-aws_rds_iam 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+ require "rubocop/rake_task"
6
+ require "yard"
7
+
8
+ namespace :test do
9
+ Rake::TestTask.new :acceptance do |t|
10
+ t.description = "Run acceptance tests"
11
+ t.libs << "test"
12
+ t.test_files = ["test/acceptance/test.rb"]
13
+ end
14
+
15
+ Rake::TestTask.new :unit do |t|
16
+ t.description = "Run unit tests"
17
+ t.libs << "test"
18
+ t.test_files = FileList["test/**/*_test.rb"]
19
+ end
20
+ end
21
+
22
+ desc "Run all tests"
23
+ task :test => ["test:unit", "test:acceptance"]
24
+
25
+ RuboCop::RakeTask.new do |t|
26
+ t.formatters = ["fuubar"]
27
+ end
28
+
29
+ desc "Generate documentation"
30
+ YARD::Rake::YardocTask.new
31
+
32
+ namespace :yard do
33
+ desc "Run documentation server"
34
+ task :server do
35
+ exec "bin/yard", "server", "--reload"
36
+ end
37
+ end
38
+
39
+ task :default => ["test:unit", :rubocop]
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'bundle' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "rubygems"
12
+
13
+ m = Module.new do
14
+ module_function
15
+
16
+ def invoked_as_script?
17
+ File.expand_path($0) == File.expand_path(__FILE__)
18
+ end
19
+
20
+ def env_var_version
21
+ ENV["BUNDLER_VERSION"]
22
+ end
23
+
24
+ def cli_arg_version
25
+ return unless invoked_as_script? # don't want to hijack other binstubs
26
+ return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27
+ bundler_version = nil
28
+ update_index = nil
29
+ ARGV.each_with_index do |a, i|
30
+ if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31
+ bundler_version = a
32
+ end
33
+ next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34
+ bundler_version = $1 || ">= 0.a"
35
+ update_index = i
36
+ end
37
+ bundler_version
38
+ end
39
+
40
+ def gemfile
41
+ gemfile = ENV["BUNDLE_GEMFILE"]
42
+ return gemfile if gemfile && !gemfile.empty?
43
+
44
+ File.expand_path("../../Gemfile", __FILE__)
45
+ end
46
+
47
+ def lockfile
48
+ lockfile =
49
+ case File.basename(gemfile)
50
+ when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51
+ else "#{gemfile}.lock"
52
+ end
53
+ File.expand_path(lockfile)
54
+ end
55
+
56
+ def lockfile_version
57
+ return unless File.file?(lockfile)
58
+ lockfile_contents = File.read(lockfile)
59
+ return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60
+ Regexp.last_match(1)
61
+ end
62
+
63
+ def bundler_version
64
+ @bundler_version ||= begin
65
+ env_var_version || cli_arg_version ||
66
+ lockfile_version || "#{Gem::Requirement.default}.a"
67
+ end
68
+ end
69
+
70
+ def load_bundler!
71
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
72
+
73
+ # must dup string for RG < 1.8 compatibility
74
+ activate_bundler(bundler_version.dup)
75
+ end
76
+
77
+ def activate_bundler(bundler_version)
78
+ if Gem::Version.correct?(bundler_version) && Gem::Version.new(bundler_version).release < Gem::Version.new("2.0")
79
+ bundler_version = "< 2"
80
+ end
81
+ gem_error = activation_error_handling do
82
+ gem "bundler", bundler_version
83
+ end
84
+ return if gem_error.nil?
85
+ require_error = activation_error_handling do
86
+ require "bundler/version"
87
+ end
88
+ return if require_error.nil? && Gem::Requirement.new(bundler_version).satisfied_by?(Gem::Version.new(Bundler::VERSION))
89
+ warn "Activating bundler (#{bundler_version}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_version}'`"
90
+ exit 42
91
+ end
92
+
93
+ def activation_error_handling
94
+ yield
95
+ nil
96
+ rescue StandardError, LoadError => e
97
+ e
98
+ end
99
+ end
100
+
101
+ m.load_bundler!
102
+
103
+ if m.invoked_as_script?
104
+ load Gem.bin_path("bundler", "bundle")
105
+ end
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "pg/aws_rds_iam"
6
+ require "pry"
7
+
8
+ Pry.start
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+ set -o errexit
3
+ set -o nounset
4
+
5
+ bundler_version=$(ruby -e 'puts /\n\nBUNDLED WITH\n\s{2,}(?<version>#{Gem::Version::VERSION_PATTERN})\n/.match(File.read("Gemfile.lock"))[:version]')
6
+ gem install bundler --version "${bundler_version}"
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rake' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rake", "rake")
@@ -0,0 +1,6 @@
1
+ #!/bin/sh
2
+ set -o errexit
3
+ set -o nounset
4
+
5
+ bin/install-bundler
6
+ bin/bundle install
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'yard' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("yard", "yard")
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "aws-sdk-rds"
4
+ require "pg"
5
+ require "strscan"
6
+ require "uri"
7
+
8
+ require_relative "aws_rds_iam/auth_token_generator"
9
+ require_relative "aws_rds_iam/auth_token_generator_registry"
10
+ require_relative "aws_rds_iam/auth_token_injector"
11
+ require_relative "aws_rds_iam/connection"
12
+ require_relative "aws_rds_iam/connection_info"
13
+ require_relative "aws_rds_iam/version"
14
+
15
+ # The top-level [PG](https://deveiate.org/code/pg/PG.html) namespace.
16
+ module PG
17
+ # The top-level AWS RDS IAM plugin namespace.
18
+ module AWS_RDS_IAM
19
+ @auth_token_generators = AuthTokenGeneratorRegistry.new
20
+
21
+ # Registry of available {AuthTokenGenerator}s.
22
+ #
23
+ # @return [AuthTokenGeneratorRegistry]
24
+ def self.auth_token_generators
25
+ @auth_token_generators
26
+ end
27
+
28
+ PG::Connection.singleton_class.prepend Connection
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PG
4
+ module AWS_RDS_IAM
5
+ # Generates short-lived authentication tokens for connecting to Amazon RDS instances.
6
+ #
7
+ # @see https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html
8
+ class AuthTokenGenerator
9
+ # Creates a new authentication token generator.
10
+ #
11
+ # @param credentials [Aws::CredentialProvider] the IAM credentials with which to sign the token
12
+ # @param region [String] the AWS region in which the RDS instances are running
13
+ def initialize(credentials:, region:)
14
+ @generator = Aws::RDS::AuthTokenGenerator.new(credentials: credentials)
15
+ @region = region
16
+ end
17
+
18
+ # Generates an authentication token for connecting to an Amazon RDS instance.
19
+ #
20
+ # @param host [String] the host name of the RDS instance that you want to access
21
+ # @param port [String] the port number used for connecting to your RDS instance
22
+ # @param user [String] the database account that you want to access
23
+ # @return [String] the generated authentication token
24
+ def call(host:, port:, user:)
25
+ @generator.auth_token(region: @region, endpoint: "#{host}:#{port}", user_name: user)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PG
4
+ module AWS_RDS_IAM
5
+ # Registers {AuthTokenGenerator}s to be used to generate authentication tokens for `PG::Connection`s that have the
6
+ # `aws_rds_iam_auth_token_generator` connection parameter set to the registered name.
7
+ class AuthTokenGeneratorRegistry
8
+ # Creates a new `AuthTokenRegistry`.
9
+ #
10
+ # @param default_auth_token_generator_class [Class] the class to register as the default {AuthTokenGenerator}
11
+ def initialize(default_auth_token_generator_class: AuthTokenGenerator)
12
+ @default_auth_token_generator_class = default_auth_token_generator_class
13
+ reset
14
+ end
15
+
16
+ # Registers an {AuthTokenGenerator}.
17
+ #
18
+ # @param name [String, Symbol]
19
+ # @return [void]
20
+ # @yieldreturn [AuthTokenGenerator]
21
+ def add(name, &block)
22
+ @registry[name.to_s] = Memoizer.new(&block)
23
+ end
24
+
25
+ # Looks up an {AuthTokenGenerator} by name.
26
+ #
27
+ # @param name [String, Symbol]
28
+ # @return [AuthTokenGenerator]
29
+ def fetch(name)
30
+ @registry.fetch(name.to_s).call
31
+ end
32
+
33
+ # Unregisters an {AuthTokenGenerator}.
34
+ #
35
+ # @param name [String, Symbol]
36
+ # @return [void]
37
+ def remove(name)
38
+ @registry.delete name.to_s
39
+ end
40
+
41
+ # Unregisters all {AuthTokenGenerator}s and re-registers the default {AuthTokenGenerator}.
42
+ #
43
+ # @return [void]
44
+ def reset
45
+ @registry = {}
46
+
47
+ add :default do
48
+ config = Aws::RDS::Client.new.config
49
+ @default_auth_token_generator_class.new(credentials: config.credentials, region: config.region)
50
+ end
51
+ end
52
+
53
+ class Memoizer # rubocop:disable Style/Documentation
54
+ def initialize(&block)
55
+ @block = block
56
+ end
57
+
58
+ def call
59
+ return @auth_token_generator if defined?(@auth_token_generator)
60
+
61
+ @auth_token_generator = @block.call
62
+ end
63
+ end
64
+
65
+ private_constant :Memoizer
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PG
4
+ module AWS_RDS_IAM
5
+ class AuthTokenInjector
6
+ def self.call(connection_string, auth_token_generators: AWS_RDS_IAM.auth_token_generators)
7
+ new(connection_string, auth_token_generators: auth_token_generators).call
8
+ end
9
+
10
+ def initialize(connection_string, auth_token_generators:)
11
+ @connection_string = connection_string
12
+ @connection_info = ConnectionInfo.new(connection_string)
13
+ @connection_defaults = PG::Connection.conndefaults_hash
14
+ @auth_token_generators = auth_token_generators
15
+ end
16
+
17
+ def call
18
+ return @connection_string unless generate_auth_token?
19
+
20
+ @connection_info.password = generate_auth_token
21
+
22
+ @connection_info.to_s
23
+ end
24
+
25
+ private
26
+
27
+ def generate_auth_token?
28
+ @connection_info.auth_token_generator_name
29
+ end
30
+
31
+ def generate_auth_token
32
+ @auth_token_generators
33
+ .fetch(@connection_info.auth_token_generator_name)
34
+ .call(
35
+ user: @connection_info.user || default(:user),
36
+ host: @connection_info.host || default(:host),
37
+ port: @connection_info.port || default(:port)
38
+ )
39
+ end
40
+
41
+ def default(key)
42
+ @connection_defaults.fetch(key)
43
+ end
44
+ end
45
+
46
+ private_constant :AuthTokenInjector
47
+ end
48
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PG
4
+ module AWS_RDS_IAM
5
+ module Connection
6
+ def conndefaults
7
+ super + [{
8
+ keyword: "aws_rds_iam_auth_token_generator",
9
+ envvar: nil,
10
+ compiled: nil,
11
+ val: nil,
12
+ label: "AWS-RDS-IAM-auth-token-generator",
13
+ dispchar: "",
14
+ dispsize: 64
15
+ }]
16
+ end
17
+
18
+ def parse_connect_args(*)
19
+ AuthTokenInjector.call(super)
20
+ end
21
+ end
22
+
23
+ private_constant :Connection
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "connection_info/keyword_value_string"
4
+ require_relative "connection_info/parse_error"
5
+ require_relative "connection_info/uri"
6
+
7
+ module PG
8
+ module AWS_RDS_IAM
9
+ module ConnectionInfo
10
+ def self.new(connection_string)
11
+ if URI.match?(connection_string)
12
+ URI.new(connection_string)
13
+ else
14
+ KeywordValueString.new(connection_string)
15
+ end
16
+ end
17
+ end
18
+
19
+ private_constant :ConnectionInfo
20
+ end
21
+ end