prx-ruby-aws-creds 0.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/prx-ruby-aws-creds.rb +157 -0
- 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: []
|