prx-ruby-aws-creds 0.0.35
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|