prx-ruby-aws-creds 0.0.35

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/prx-ruby-aws-creds.rb +157 -0
  3. metadata +43 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 7cad954221929f68817be8f590c4325fdd8a27a07d9d4211da6ffd46638218d8
4
+ data.tar.gz: b86bda7e8969566f8a1326ec5474de5d1fc36a5cea83ccb6ffe8fb4e5cb720a8
5
+ SHA512:
6
+ metadata.gz: f967ee46512a111bcdbfc1399ed763c1f2694e40cf1d27d44720f29bd790374f4061d03f1cf4f7747122df56200fea15b3383e21d6d770029f8eb12860e39bb9
7
+ data.tar.gz: f70427461271f3a1bfafdef651af893632fc6495a5b32b1c5852d5f5c9907ec9970902a2c5f7a57cb5353f9aeb2782ef791629669d4153623e7991f977eee8cb
@@ -0,0 +1,157 @@
1
+ AWS_CONFIG_FILE = ENV["AWS_CONFIG_FILE"] || "#{Dir.home}/.aws/config"
2
+
3
+ class PrxRubyAwsCreds
4
+ class << self
5
+ def cache_directory
6
+ "#{Dir.home}/.aws/ruby/cache"
7
+ end
8
+
9
+ def cache_key_path(assume_role_options)
10
+ # The cache key is based on the parameters used for the AssumeRole call.
11
+ # The role session name is removed if it's randomly generated (which it
12
+ # always is for us). If the options were ever to include a policy document,
13
+ # that should get sorted before hashing.
14
+ # https://github.com/boto/botocore/blob/88d780dea1684da00689f2eef388fa4c782ced08/botocore/credentials.py#L700
15
+ key_opts = assume_role_options.clone
16
+ key_opts.delete(:role_session_name)
17
+ cache_key = Digest::SHA1.hexdigest(JSON.dump(key_opts))
18
+
19
+ "#{cache_directory}/#{cache_key}.json"
20
+ end
21
+
22
+ # Returns the options passed to SSO#get_role_credentials. This is used when the
23
+ # profile uses an SSO, rather than a key/secret. If the selected profile is
24
+ # not configured for SSO, returns nil.
25
+ def sso_get_role_options
26
+ aws_config_file = IniFile.load(AWS_CONFIG_FILE)
27
+ aws_config_file_section = aws_config_file["profile #{OPTS[:profile]}"]
28
+
29
+ if aws_config_file_section["sso_start_url"]
30
+ profile_start_url = aws_config_file_section["sso_start_url"]
31
+
32
+ sso_access_token = nil
33
+ Dir["#{Dir.home}/.aws/sso/cache/*.json"].each do |path|
34
+ data = JSON.parse(File.read(path))
35
+ if data["startUrl"] && data["startUrl"] == profile_start_url
36
+ sso_access_token = data["accessToken"]
37
+ break
38
+ end
39
+ end
40
+
41
+ if !sso_access_token
42
+ puts "No SSO access token was found for this profile."
43
+ puts "Press RETURN to request a token in a web browser."
44
+ puts "You can do this manually with: 'aws sso login --profile #{OPTS[:profile]}'"
45
+ inp = $stdin.gets.chomp
46
+ `aws sso login --profile #{OPTS[:profile]}` if inp.empty?
47
+ end
48
+
49
+ {
50
+ role_name: aws_config_file_section["sso_role_name"],
51
+ account_id: aws_config_file_section["sso_account_id"].to_s,
52
+ access_token: sso_access_token
53
+ }
54
+ end
55
+ end
56
+
57
+ # Returns the options passed to AssumeRole. This is used when the profile uses
58
+ # a key/secret. If the selected profile is not configured for key/secret,
59
+ # returns nil.
60
+ def assume_role_options
61
+ aws_config_file = IniFile.load(AWS_CONFIG_FILE)
62
+ aws_config_file_section = aws_config_file["profile #{OPTS[:profile]}"]
63
+ role_arn = aws_config_file_section["role_arn"]
64
+ role_name = role_arn.split("role/")[1]
65
+ account_id = role_arn.split(":")[4]
66
+
67
+ {
68
+ role_arn: "arn:aws:sts::#{account_id}:role/#{role_name}",
69
+ role_session_name: "ruby-sdk-session-#{Time.now.to_i}",
70
+ duration_seconds: 3600
71
+ }
72
+ end
73
+
74
+ def get_and_cache_credentials
75
+ FileUtils.mkdir_p cache_directory
76
+
77
+ aws_config_file = IniFile.load(AWS_CONFIG_FILE)
78
+ aws_config_file_section = aws_config_file["profile #{OPTS[:profile]}"]
79
+
80
+ if aws_config_file_section["sso_role_name"]
81
+ opts = sso_get_role_options
82
+
83
+ sso = Aws::SSO::Client.new(region: aws_config_file_section["region"])
84
+ credentials = sso.get_role_credentials(opts)
85
+
86
+ File.write(cache_key_path(opts), JSON.dump({"credentials" => {
87
+ "access_key_id" => credentials.role_credentials.access_key_id,
88
+ "secret_access_key" => credentials.role_credentials.secret_access_key,
89
+ "session_token" => credentials.role_credentials.session_token
90
+ }}))
91
+
92
+ Aws::Credentials.new(credentials.role_credentials.access_key_id, credentials.role_credentials.secret_access_key, credentials.role_credentials.session_token)
93
+ elsif aws_config_file_section["mfa_serial"]
94
+ mfa_serial = aws_config_file_section["mfa_serial"]
95
+
96
+ mfa_code = $stdin.getpass("Enter MFA code for #{mfa_serial}: ")
97
+ credentials = Aws.shared_config.assume_role_credentials_from_config(profile: OPTS[:profile], token_code: mfa_code.chomp)
98
+ sts = Aws::STS::Client.new(
99
+ region: "us-east-1",
100
+ credentials: credentials
101
+ )
102
+ _id = sts.get_caller_identity
103
+
104
+ opts = assume_role_options
105
+ cacheable_role = sts.assume_role(assume_role_options)
106
+ File.write(cache_key_path(opts), JSON.dump(cacheable_role.to_h))
107
+
108
+ Aws::Credentials.new(cacheable_role["credentials"]["access_key_id"], cacheable_role["credentials"]["secret_access_key"], cacheable_role["credentials"]["session_token"])
109
+ end
110
+ rescue Aws::SSO::Errors::UnauthorizedException
111
+ raise "The SSO access token for this profile is invalid. Run 'aws sso login --profile #{OPTS[:profile]}' to fetch a valid token."
112
+ end
113
+
114
+ def load_and_verify_cached_credentials
115
+ # Look up the cache file based on the options for the seleted profile.
116
+ options = sso_get_role_options || assume_role_options
117
+
118
+ cached_role_json = File.read(cache_key_path(options))
119
+ cached_role = JSON.parse(cached_role_json)
120
+
121
+ credentials = Aws::Credentials.new(cached_role["credentials"]["access_key_id"], cached_role["credentials"]["secret_access_key"], cached_role["credentials"]["session_token"])
122
+
123
+ # Verify that the credentials still work; this will raise an error if they're
124
+ # bad, which we can catch
125
+ sts = Aws::STS::Client.new(region: "us-east-1", credentials: credentials)
126
+ sts.get_caller_identity
127
+
128
+ credentials
129
+ rescue Aws::STS::Errors::ExpiredToken
130
+ get_and_cache_credentials
131
+ rescue Aws::STS::Errors::InvalidClientTokenId
132
+ get_and_cache_credentials
133
+ rescue Errno::ENOENT
134
+ get_and_cache_credentials
135
+ end
136
+
137
+ # Returns temporary client credentials for the profile selected with --profile
138
+ # when the command was run.
139
+ def client_credentials
140
+ # For the selected profile, get the appropriate set of options.
141
+ options = sso_get_role_options || assume_role_options
142
+
143
+ return if !options
144
+
145
+ # Check for a cache file with a name derived from those options.
146
+ if !File.file?(cache_key_path(options))
147
+ # When no cache exists for these options, fetch new credentials, cache them
148
+ # and return them.
149
+ get_and_cache_credentials
150
+ else
151
+ # When there is a cache for these options, return them if they are still
152
+ # valid, otherwise refresh them and return the new credentials.
153
+ load_and_verify_cached_credentials
154
+ end
155
+ end
156
+ end
157
+ end
metadata ADDED
@@ -0,0 +1,43 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: prx-ruby-aws-creds
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.35
5
+ platform: ruby
6
+ authors:
7
+ - Christopher Kalafarski
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-04-04 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: tktk
14
+ email: chris.kalafarski@prx.org
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/prx-ruby-aws-creds.rb
20
+ homepage: https://github.com/PRX/homebrew-dev-tools/tree/main/lib/prx-ruby-aws-creds
21
+ licenses:
22
+ - MIT
23
+ metadata: {}
24
+ post_install_message:
25
+ rdoc_options: []
26
+ require_paths:
27
+ - lib
28
+ required_ruby_version: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ required_rubygems_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ requirements: []
39
+ rubygems_version: 3.4.10
40
+ signing_key:
41
+ specification_version: 4
42
+ summary: tktk
43
+ test_files: []