aws-asmr 0.0.4 → 0.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9804add634c49c85745aa41741d6558b7dc5e0cab20426aaa9827eb06ae6808
4
- data.tar.gz: 8ebaaabd5a6a65c5609ff08a2723a4fa135298667b842cd7175549dec6069b96
3
+ metadata.gz: 6cc279ebf6f98696525bd333297e3d9e07f4546235685e4cc56ede2f6e5936df
4
+ data.tar.gz: 5e8ba9aa25320658bbe860b0733141d4bda426a75330bd52603922bb6e0274fb
5
5
  SHA512:
6
- metadata.gz: 0345bb76d7e174c1a18debd29a262d85506cb95f4e2c76c7f4aa86a1ca9bd6de6c09c5a040d12794cae2af1ea18582d311789bf458fb935a1525d0c30dd5ab37
7
- data.tar.gz: 4fb26c3b1aeb378ff4da4e47b0bf5450c70223d781f1993c1dbb03353755eb59781d8ba7a61e5798d19e003491cca53d6a3307afd2adff7312e04b52d9374fd4
6
+ metadata.gz: 3ac9215a9c0ddb69a23eb94e7aa30eb91b07ea2e4bdf1fe2be1f03f2ec4fdbb4890bd329432e443044c70360b4aba568f03e5294a9f311779aac95ec334273f2
7
+ data.tar.gz: 52d2bf0b95b0f377a2507ed9e72372edce4a6dcd1aeacc1e231558bf1f4b2b228a9798c9e6d63f54f18b02c28d77c572b405aa0272ce134aaa2417f84d76254f
data/README.md CHANGED
@@ -69,6 +69,7 @@ Here is the example of alias file. `arn` is the only required attribute.
69
69
  [awesome-app-staging]
70
70
  arn = arn:aws:iam::0001:role/AwesomeRole
71
71
  profile = custodian
72
+ region = ap-northeast-1
72
73
 
73
74
  [awesome-app-production]
74
75
  arn = arn:aws:iam::0002:role/AwesomeRole
@@ -79,6 +80,8 @@ secret_access_key = yyyy
79
80
  # arn = test
80
81
  ```
81
82
 
83
+ `region` is optional and is only used by `asmr-login` (see below) to pick the console landing region.
84
+
82
85
  Then, you can choose one of the alias.
83
86
 
84
87
  ```
@@ -91,3 +94,40 @@ Or you can specify alias name.
91
94
  ```
92
95
  asmr --name=awesome-app-staging aws sts get-caller-identity
93
96
  ```
97
+
98
+ ## Web Login (AWS Management Console)
99
+
100
+ The companion command `asmr-login` opens the **AWS Management Console** in your browser as the assumed role, using the [AWS federation endpoint](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html). This is handy when you want a *browser* session for a role you normally only use from the CLI.
101
+
102
+ ```
103
+ asmr-login --name=awesome-app-staging
104
+ ```
105
+
106
+ It assumes the role exactly like `asmr` does — sharing the same alias resolution (`--name`/`-n`), MFA prompt and credential cache — then exchanges the temporary credentials for a sign-in token at `https://signin.aws.amazon.com/federation` and opens the resulting console URL in your default browser. On a headless host where no browser opener is available, the URL is printed instead (it is valid for 15 minutes — treat it as a secret).
107
+
108
+ ### Landing region
109
+
110
+ The console page you land on is derived from the `region` of the chosen alias. Add `region` to the alias:
111
+
112
+ ```
113
+ [my-awesome-project]
114
+ arn = arn:aws:iam::xxxx:role/AdminRole
115
+ profile = smcdk-prejp
116
+ region = ap-northeast-1
117
+ ```
118
+
119
+ Then `asmr-login --name=my-awesome-project` opens the console home of `ap-northeast-1`. When the alias has no `region` (or you pass an ARN directly), it falls back to the global console home (`https://console.aws.amazon.com/`).
120
+
121
+ ### Session duration
122
+
123
+ The console session duration (seconds, 900-43200) is set via the alias's optional `session_duration`. When omitted, it defaults to 43200 (12h).
124
+
125
+ ```
126
+ [my-awesome-project]
127
+ arn = arn:aws:iam::xxxx:role/AdminRole
128
+ profile = smcdk-prejp
129
+ region = ap-northeast-1
130
+ session_duration = 3600
131
+ ```
132
+
133
+ Note: the requested duration must be **less than the assumed role's maximum session duration** (1 hour by default). When the federation endpoint rejects it (e.g. the role's max is shorter, or you reached the role via role chaining), `asmr-login` automatically retries *without* it, falling back to the lifetime of the temporary credentials. To get a full 12-hour console session, raise the role's *Maximum session duration* in IAM accordingly.
data/bin/asmr CHANGED
@@ -1,41 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "aws/asmr"
4
- require "aws/asmr/options"
5
- require "aws/asmr/prompt"
6
-
7
- asmr_args, command_args = Aws::ASMR::Options.partition(ARGV)
8
- options = Aws::ASMR::Options.parse(asmr_args)
9
-
10
- if options[:version]
11
- require "aws/asmr/version"
12
- puts Aws::ASMR::VERSION
13
- exit(0)
14
- elsif options[:clear]
15
- Aws::ASMR::Cache.destroy!
16
- exit(0)
17
- else
18
- end
19
-
20
- prompt = Aws::ASMR::Prompt.safe
21
-
22
- name = if options[:name]
23
- options[:name]
24
- else
25
- alias_keys = Aws::ASMR::Alias.base.keys
26
- if alias_keys.empty?
27
- STDERR.puts "Please specify --name=ARN to assume_role or make alias at #{Aws::ASMR::ROOT}/alias"
28
- exit(1)
29
- end
30
- prompt.select("Choose a role you're going to assume:", alias_keys)
31
- end
32
- asmr_alias = Aws::ASMR::Alias.get(name)
33
- assume_role_arn = if asmr_alias
34
- asmr_alias.set_environment_variables!
35
- asmr_alias.arn
36
- else
37
- name
38
- end
3
+ require "aws/asmr/cli"
39
4
 
40
5
  def run(command, shell_variables=[])
41
6
  if command.empty?
@@ -57,33 +22,8 @@ def run(command, shell_variables=[])
57
22
  end
58
23
  exec([*shell_variables, *command].join(' '))
59
24
  end
60
- end
61
-
62
- if cache = Aws::ASMR::Cache.get(assume_role_arn)
63
- run(command_args, cache.shell_variables)
64
25
  end
65
26
 
66
-
67
- begin
68
- serial_number = Aws::ASMR.detect_mfa_device_serial_number
69
- assume_role_args = asmr_alias ? asmr_alias.assume_role_args : {}
70
- assume_role_args = if serial_number
71
- token_code = prompt.ask("Type MFA token code:")
72
- assume_role_args.merge({serial_number: serial_number, token_code: token_code})
73
- else
74
- assume_role_args
75
- end
76
-
77
- res = Aws::ASMR.assume_role(assume_role_arn, **assume_role_args)
78
- cache = Aws::ASMR::Cache.new(**res.credentials.to_h)
79
- cache.save!(assume_role_arn)
27
+ Aws::ASMR::CLI.main(ARGV) do |cache, command_args, _options|
80
28
  run(command_args, cache.shell_variables)
81
- rescue => e
82
- STDERR.puts e.message
83
- if options[:verbose]
84
- STDERR.puts e.backtrace
85
- else
86
- STDERR.puts "Add --verbose for more error information."
87
- end
88
- exit(1)
89
- end
29
+ end
data/bin/asmr-login ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "aws/asmr/cli"
4
+
5
+ # Session duration is read from the alias only (a raw, possibly empty string).
6
+ # Returns nil to let WebLogin fall back to its default.
7
+ def alias_session_duration(asmr_alias)
8
+ raw = asmr_alias&.session_duration
9
+ raw.to_i if raw && !raw.empty?
10
+ end
11
+
12
+ Aws::ASMR::CLI.main(ARGV) do |cache, _command_args, _options, asmr_alias|
13
+ args = { region: asmr_alias&.region }
14
+ duration = alias_session_duration(asmr_alias)
15
+ args[:session_duration] = duration if duration
16
+ url = Aws::ASMR::WebLogin.signin_url(cache, **args)
17
+ Aws::ASMR::WebLogin.open_browser(url)
18
+ end
@@ -3,7 +3,7 @@ require 'aws/asmr'
3
3
 
4
4
  module Aws
5
5
  module ASMR
6
- class Alias < Struct.new(:arn, :access_key_id, :secret_access_key, :profile, :external_id, :role_session_name, keyword_init: true)
6
+ class Alias < Struct.new(:arn, :access_key_id, :secret_access_key, :profile, :external_id, :role_session_name, :region, :session_duration, keyword_init: true)
7
7
 
8
8
  PATH = "#{Aws::ASMR::ROOT}/alias"
9
9
 
@@ -0,0 +1,92 @@
1
+ require "aws/asmr"
2
+ require "aws/asmr/options"
3
+ require "aws/asmr/prompt"
4
+
5
+ module Aws
6
+ module ASMR
7
+ # The parts shared by every asmr executable: option parsing, --version /
8
+ # --clear, resolving the role (alias or ARN), and obtaining temporary
9
+ # credentials via assume_role (with MFA prompt and caching).
10
+ #
11
+ # Each executable is just a thin wrapper that decides what to do with the
12
+ # resolved credentials:
13
+ #
14
+ # Aws::ASMR::CLI.main(ARGV) do |cache, command_args, options, asmr_alias|
15
+ # # ... use cache.shell_variables / build a login URL / etc.
16
+ # end
17
+ module CLI
18
+ # Parses asmr-level args, handles --version/--clear, resolves credentials
19
+ # and yields (cache, command_args, options, asmr_alias) to the block; the
20
+ # resolved alias is nil when an ARN was given directly. Top-level errors
21
+ # are reported here (with a backtrace under --verbose) and exit non-zero.
22
+ def main(argv)
23
+ asmr_args, command_args = Options.partition(argv)
24
+ options = Options.parse(asmr_args)
25
+
26
+ if options[:version]
27
+ require "aws/asmr/version"
28
+ puts VERSION
29
+ exit(0)
30
+ elsif options[:clear]
31
+ Cache.destroy!
32
+ exit(0)
33
+ end
34
+
35
+ prompt = Prompt.safe
36
+ begin
37
+ cache, asmr_alias = resolve_credentials(options, prompt)
38
+ yield cache, command_args, options, asmr_alias
39
+ rescue => e
40
+ STDERR.puts e.message
41
+ if options[:verbose]
42
+ STDERR.puts e.backtrace
43
+ else
44
+ STDERR.puts "Add --verbose for more error information."
45
+ end
46
+ exit(1)
47
+ end
48
+ end
49
+
50
+ # Resolves the role to assume (from --name/-n or an interactive alias
51
+ # selection) and returns [cache, asmr_alias]: a Cache holding temporary
52
+ # credentials (reusing a valid cache entry or performing assume_role with an
53
+ # MFA prompt), and the resolved Alias (nil when an ARN was given directly).
54
+ def resolve_credentials(options, prompt)
55
+ name = options[:name] || begin
56
+ alias_keys = Alias.base.keys
57
+ if alias_keys.empty?
58
+ STDERR.puts "Please specify --name=ARN to assume_role or make alias at #{ROOT}/alias"
59
+ exit(1)
60
+ end
61
+ prompt.select("Choose a role you're going to assume:", alias_keys)
62
+ end
63
+
64
+ asmr_alias = Alias.get(name)
65
+ assume_role_arn = if asmr_alias
66
+ asmr_alias.set_environment_variables!
67
+ asmr_alias.arn
68
+ else
69
+ name
70
+ end
71
+
72
+ if cache = Cache.get(assume_role_arn)
73
+ return [cache, asmr_alias]
74
+ end
75
+
76
+ serial_number = Aws::ASMR.detect_mfa_device_serial_number
77
+ assume_role_args = asmr_alias ? asmr_alias.assume_role_args : {}
78
+ if serial_number
79
+ token_code = prompt.ask("Type MFA token code:")
80
+ assume_role_args = assume_role_args.merge(serial_number: serial_number, token_code: token_code)
81
+ end
82
+
83
+ res = Aws::ASMR.assume_role(assume_role_arn, **assume_role_args)
84
+ cache = Cache.new(**res.credentials.to_h)
85
+ cache.save!(assume_role_arn)
86
+ [cache, asmr_alias]
87
+ end
88
+
89
+ module_function :main, :resolve_credentials
90
+ end
91
+ end
92
+ end
@@ -1,5 +1,5 @@
1
1
  module Aws
2
2
  module ASMR
3
- VERSION = "0.0.4"
3
+ VERSION = "0.0.5"
4
4
  end
5
5
  end
@@ -0,0 +1,99 @@
1
+ require 'json'
2
+ require 'uri'
3
+ require 'net/http'
4
+ require 'rbconfig'
5
+ require 'aws/asmr'
6
+
7
+ module Aws
8
+ module ASMR
9
+ # Builds a sign-in URL for the AWS Management Console out of the temporary
10
+ # credentials obtained by assume_role, using the AWS federation endpoint.
11
+ # See: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_enable-console-custom-url.html
12
+ module WebLogin
13
+ ENDPOINT = "https://signin.aws.amazon.com/federation"
14
+ DEFAULT_DESTINATION = "https://console.aws.amazon.com/"
15
+ DEFAULT_ISSUER = "aws-asmr"
16
+ # Console session duration. Up to 43200 (12h), but it must be LESS than the
17
+ # max session duration setting of the role being assumed (default 1h), so
18
+ # request_signin_token falls back to omitting it when the endpoint rejects it.
19
+ DEFAULT_SESSION_DURATION = 43200
20
+
21
+ # Exchanges the temporary credentials for a sign-in token at the federation
22
+ # endpoint. Retries without SessionDuration when it is rejected (e.g. the
23
+ # role's max session duration is shorter, or the credentials come from role
24
+ # chaining, both of which make SessionDuration invalid).
25
+ def signin_token(cache, session_duration: DEFAULT_SESSION_DURATION)
26
+ res = request_signin_token(cache, session_duration)
27
+ if !res.is_a?(Net::HTTPSuccess) && session_duration
28
+ STDERR.puts "getSigninToken with SessionDuration=#{session_duration} was rejected (HTTP #{res.code}). Retrying without SessionDuration..."
29
+ res = request_signin_token(cache, nil)
30
+ end
31
+ unless res.is_a?(Net::HTTPSuccess)
32
+ raise "Federation endpoint returned HTTP #{res.code}: #{res.body}"
33
+ end
34
+ token = JSON.parse(res.body)["SigninToken"]
35
+ raise "Federation endpoint did not return a SigninToken: #{res.body}" unless token
36
+ token
37
+ end
38
+
39
+ # Builds the final console login URL. Valid for 15 minutes after creation.
40
+ # The landing page defaults to the given region's console home, or the
41
+ # global console home when no region is given (e.g. ARN passed directly).
42
+ def signin_url(cache, region: nil, issuer: DEFAULT_ISSUER, session_duration: DEFAULT_SESSION_DURATION)
43
+ token = signin_token(cache, session_duration: session_duration)
44
+ build_url(
45
+ "Action" => "login",
46
+ "Issuer" => issuer,
47
+ "Destination" => destination_for(region),
48
+ "SigninToken" => token,
49
+ )
50
+ end
51
+
52
+ # Console home URL to land on after sign-in.
53
+ def destination_for(region)
54
+ return DEFAULT_DESTINATION if region.nil? || region.empty?
55
+ "https://#{region}.console.aws.amazon.com/console/home?region=#{region}"
56
+ end
57
+
58
+ # Opens the given URL in the default browser. Falls back to printing it
59
+ # (e.g. on a headless host where no opener is available).
60
+ def open_browser(url)
61
+ opener = browser_opener
62
+ if opener && system(*opener, url)
63
+ STDERR.puts "Opened the AWS Management Console in your browser."
64
+ else
65
+ STDERR.puts "Open the following URL in your browser to sign in (valid for 15 minutes):"
66
+ puts url
67
+ end
68
+ end
69
+
70
+ def request_signin_token(cache, session_duration)
71
+ session = {
72
+ sessionId: cache.access_key_id,
73
+ sessionKey: cache.secret_access_key,
74
+ sessionToken: cache.session_token,
75
+ }.to_json
76
+
77
+ params = { "Action" => "getSigninToken", "Session" => session }
78
+ params["SessionDuration"] = session_duration.to_s if session_duration
79
+ Net::HTTP.get_response(URI(build_url(params)))
80
+ end
81
+
82
+ def build_url(params)
83
+ uri = URI(ENDPOINT)
84
+ uri.query = URI.encode_www_form(params)
85
+ uri.to_s
86
+ end
87
+
88
+ def browser_opener
89
+ case RbConfig::CONFIG['host_os']
90
+ when /darwin/ then ["open"]
91
+ when /mswin|mingw|cygwin/ then ["cmd", "/c", "start", ""]
92
+ else ["xdg-open"]
93
+ end
94
+ end
95
+
96
+ module_function :signin_token, :signin_url, :destination_for, :open_browser, :request_signin_token, :build_url, :browser_opener
97
+ end
98
+ end
99
+ end
data/lib/aws/asmr.rb CHANGED
@@ -34,3 +34,4 @@ end
34
34
 
35
35
  require 'aws/asmr/cache'
36
36
  require 'aws/asmr/alias'
37
+ require 'aws/asmr/web_login'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aws-asmr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - metheglin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-10 00:00:00.000000000 Z
11
+ date: 2026-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -70,17 +70,21 @@ description: ''
70
70
  email: pigmybank@gmail.com
71
71
  executables:
72
72
  - asmr
73
+ - asmr-login
73
74
  extensions: []
74
75
  extra_rdoc_files: []
75
76
  files:
76
77
  - README.md
77
78
  - bin/asmr
79
+ - bin/asmr-login
78
80
  - lib/aws/asmr.rb
79
81
  - lib/aws/asmr/alias.rb
80
82
  - lib/aws/asmr/cache.rb
83
+ - lib/aws/asmr/cli.rb
81
84
  - lib/aws/asmr/options.rb
82
85
  - lib/aws/asmr/prompt.rb
83
86
  - lib/aws/asmr/version.rb
87
+ - lib/aws/asmr/web_login.rb
84
88
  homepage: https://rubygems.org/gems/aws-asmr
85
89
  licenses:
86
90
  - MIT