aws-asmr 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 295a3e23217a96e62d009c06284c2523d9397479c300c686dd2f3a750ed37a5f
4
+ data.tar.gz: 22138144c91d1cce17ebfc50c0f537954996995350483f22984ade1c380d7a91
5
+ SHA512:
6
+ metadata.gz: b4a733b089f9c05ff0e7dbeca84f3c647180e40347733385fd008b0ca23a590b7f5939a1aeaf7cab55875391e3be3ca8db3ba4e2d2618ded656acb3f8148912e
7
+ data.tar.gz: 94ece634442510edaaf7f5d2ab4e3a3dc11bc586fe60fa85c019adb9fac2247292f6cc36d8b140b19e158aea1207ad958322548119e6442157b241d98849f25f
data/README.md ADDED
@@ -0,0 +1,102 @@
1
+ # Aws::ASMR
2
+
3
+ `ASMR` stands for "Assume Role", obviously!! This is a command line utility for people in the hell of aws assume_role.
4
+
5
+ ## Install
6
+
7
+ Only gem(ruby package) install is supported now, sorry!
8
+
9
+ ```
10
+ gem install aws-asmr
11
+ gem which aws/asmr
12
+
13
+ # Set `PATH` generated from command below
14
+ gem which aws/asmr | sed -e "s/lib\/aws\/asmr.rb/bin/" | sed "s/^/PATH=/" | sed "s/$/:\$PATH/"
15
+
16
+ # Only in current shell
17
+ . <(gem which aws/asmr | sed -e "s/lib\/aws\/asmr.rb/bin/" | sed "s/^/PATH=/" | sed "s/$/:\$PATH/")
18
+
19
+ # Set PATH everytime started zsh session
20
+ gem which aws/asmr | sed -e "s/lib\/aws\/asmr.rb/bin/" | sed "s/^/PATH=/" | sed "s/$/:\$PATH/" >> ~/.zshrc
21
+
22
+ asmr --version
23
+ ```
24
+
25
+ ## Command Example
26
+
27
+ In the example below, you can run command `aws sts get-caller-identity` with assumed role `arn:aws:iam::0000:role/AwesomeRole` on specified aws account `custodian`.
28
+
29
+ If active *MFA* device detected on the IAM account(`custodian`), it'll prompt `MFA token code`. Please check and type the successful code and you'll see the process goes on. Once you went through the MFA, the credentials to assume role are cached on local. At the next command on the same *ARN*, you can skip MFA unless cache is expired.
30
+
31
+ Regardless that MFA is enabled or not, temporary credentials `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SECRET_TOKEN` are set in the current command when assume_role was successful, without `export` environment variables.
32
+ In the case below, you'll run a command like `AWS_ACCESS_KEY_ID=xxxx AWS_SECRET_ACCESS_KEY=yyyy AWS_SECRET_TOKEN=zzzz aws sts get-caller-identity`
33
+ This means those variables are only effective for the subsequential command(`aws sts get-caller-identity`). So it is safe and you can run commands idempotently (If you export those environment variables, the same command for assume_role would never be successful in the same shell session).
34
+
35
+ ```
36
+ AWS_PROFILE=custodian asmr --name=arn:aws:iam::0000:role/AwesomeRole aws sts get-caller-identity
37
+ ```
38
+
39
+ Of course you can set `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` respectively to perform assume_role, instead of profile.
40
+
41
+ ```
42
+ AWS_ACCESS_KEY_ID=xxxx AWS_SECRET_ACCESS_KEY=yyyy asmr --name=arn:aws:iam::0000:role/AwesomeRole aws sts get-caller-identity
43
+ ```
44
+
45
+ To specify ARN (or alias name of assumed role), you MUST set `name` option with a form like `--name=<arn>` NOT a form like `--name <arn>`. For short version, `-n<arn>` works, `-n <arn>` doesn't. You must be wasting time for this pitfall, sorry!
46
+ This is due to a development circumstance. This tool is supposed to run 2 commands. One is assume_role, and the other is subsequential(this is main though) command. To safely separate options for assume_role and subsequential commands, all components of the `asmr` args must be start with `-`. Curse my programming ability!
47
+
48
+ ```
49
+ asmr --name=arn:aws:iam::0000:role/AwesomeRole
50
+ asmr -narn:aws:iam::0000:role/AwesomeRole
51
+ ```
52
+
53
+ Of course you can set options for subsequential command.
54
+
55
+ ```
56
+ asmr --name=arn:aws:iam::0000:role/AwesomeRole aws ec2 describe-instances --filter '[{"Name":"instance-state-name","Values":["stopped"]}]'
57
+ ```
58
+
59
+ Unfortunatelly you need quote and appropriate escape to run piped command as subsequential.
60
+
61
+ ```
62
+ asmr --name=arn:aws:iam::0000:role/AwesomeRole "aws sts get-caller-identity | grep Arn"
63
+ ```
64
+
65
+ Without subsequential command, it just prints environment variables for assume_role.
66
+
67
+ ```
68
+ AWS_PROFILE=custodian asmr --name=arn:aws:iam::0000:role/AwesomeRole
69
+ # AWS_ACCESS_KEY_ID=xxxx
70
+ # AWS_SECRET_ACCESS_KEY=yyyy
71
+ # AWS_SECRET_TOKEN=zzzz
72
+ ```
73
+
74
+ You can define aliases as you like at `~/.aws-asmr/alias` (default).
75
+ Here is the example of alias file. `arn` is the only required attribute.
76
+
77
+ ```
78
+ [awesome-app-staging]
79
+ arn = arn:aws:iam::0001:role/AwesomeRole
80
+ profile = custodian
81
+
82
+ [awesome-app-production]
83
+ arn = arn:aws:iam::0002:role/AwesomeRole
84
+ access_key_id = xxxx
85
+ secret_access_key = yyyy
86
+
87
+ # [commented-awesome-app-test]
88
+ # arn = test
89
+ ```
90
+
91
+ Then, you can choose one of the alias.
92
+
93
+ ```
94
+ asmr aws sts get-caller-identity
95
+ # Choose alias listed on the shell
96
+ ```
97
+
98
+ Or you can specify alias name.
99
+
100
+ ```
101
+ asmr --name=awesome-app-staging aws sts get-caller-identity
102
+ ```
data/bin/asmr ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "tty-prompt"
4
+ require "aws/asmr"
5
+ require "aws/asmr/options"
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 = TTY::Prompt.new
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
39
+
40
+ def run(command, shell_variables=[])
41
+ if command.empty?
42
+ exec("echo \"#{shell_variables.join($/)}\"")
43
+ else
44
+ command = if command.length == 1
45
+ # Ex: asmr "aws sts get-caller-identity | grep Arn"
46
+ command
47
+ else
48
+ # Ex: asmr aws ec2 describe-instances --filter '[{"Name":"instance-state-name","Values":["stopped"]}]'
49
+ command.map{|plain|
50
+ if plain.match?(/[\"\'\ ]/)
51
+ quoted = plain.gsub("'"){"\\'"}
52
+ "'#{quoted}'"
53
+ else
54
+ plain
55
+ end
56
+ }
57
+ end
58
+ exec([*shell_variables, *command].join(' '))
59
+ end
60
+ end
61
+
62
+ if cache = Aws::ASMR::Cache.get(assume_role_arn)
63
+ run(command_args, cache.shell_variables)
64
+ end
65
+
66
+
67
+ begin
68
+ serial_number = Aws::ASMR.detect_mfa_device_serial_number
69
+ assume_role_args = if serial_number
70
+ token_code = prompt.ask("Type MFA token code:")
71
+ {serial_number: serial_number, token_code: token_code}
72
+ else
73
+ {}
74
+ end
75
+
76
+ res = Aws::ASMR.assume_role(assume_role_arn, **assume_role_args)
77
+ cache = Aws::ASMR::Cache.new(**res.credentials.to_h)
78
+ cache.save!(assume_role_arn)
79
+ run(command_args, cache.shell_variables)
80
+ rescue => e
81
+ STDERR.puts e.message
82
+ exit(1)
83
+ end
@@ -0,0 +1,61 @@
1
+ require 'fileutils'
2
+ require 'aws/asmr'
3
+
4
+ module Aws
5
+ module ASMR
6
+ class Alias < Struct.new(:arn, :access_key_id, :secret_access_key, :profile, keyword_init: true)
7
+
8
+ PATH = "#{Aws::ASMR::ROOT}/alias"
9
+
10
+ class << self
11
+ def parse(lines)
12
+ lines = lines.map(&:strip).select{! _1.start_with?('#')}
13
+ arr = lines.reduce([]) do |acc,line|
14
+ m = line.match(/\A\[([^\[\]]+)\]\z/)
15
+ if m
16
+ acc << [m[1], []]
17
+ else
18
+ alias_name, alias_props = acc.last
19
+ if alias_name
20
+ k, v = line.split('=').map(&:strip)
21
+ if k and v
22
+ alias_props << [k, v]
23
+ end
24
+ end
25
+ end
26
+ acc
27
+ end
28
+ arr.map{|k,v| [k,v.to_h]}.select{|k,v| v["arn"] and !v["arn"].empty?}.to_h
29
+ end
30
+
31
+ def base
32
+ @base ||= begin
33
+ if File.exists?(PATH)
34
+ parse(File.readlines(PATH))
35
+ else
36
+ {}
37
+ end
38
+ end
39
+ end
40
+
41
+ def first
42
+ k,v = base.first
43
+ get(k)
44
+ end
45
+
46
+ def get(alias_name)
47
+ base[alias_name] && new(base[alias_name])
48
+ end
49
+ end
50
+
51
+ def set_environment_variables!
52
+ if access_key_id and !access_key_id.empty?
53
+ ENV["AWS_ACCESS_KEY_ID"] = access_key_id
54
+ ENV["AWS_SECRET_ACCESS_KEY"] = secret_access_key
55
+ elsif profile and !profile.empty?
56
+ ENV["AWS_PROFILE"] = profile
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,64 @@
1
+ require 'fileutils'
2
+ require 'aws/asmr'
3
+
4
+ module Aws
5
+ module ASMR
6
+ class Cache < Struct.new(:access_key_id, :secret_access_key, :session_token, :expiration, keyword_init: true)
7
+
8
+ PATH = "#{Aws::ASMR::ROOT}/cache"
9
+
10
+ class << self
11
+ def base
12
+ @base ||= begin
13
+ if File.exists?(PATH)
14
+ JSON.parse(File.read(PATH))
15
+ else
16
+ {}
17
+ end
18
+ end
19
+ end
20
+
21
+ def first
22
+ k,v = base.first
23
+ get(k)
24
+ end
25
+
26
+ def get(assume_role_arn)
27
+ cache = base[assume_role_arn] && new(base[assume_role_arn])
28
+ return nil unless cache
29
+ cache.expired? ? nil : cache
30
+ end
31
+
32
+ def destroy!
33
+ File.exists?(PATH) && File.delete(PATH)
34
+ end
35
+ end
36
+
37
+ def expiration_time
38
+ return nil unless expiration
39
+ expiration.is_a?(Time) ? expiration : Time.parse(expiration)
40
+ end
41
+
42
+ def expired?
43
+ return true unless expiration_time
44
+ expiration_time < (Time.now+60*5)
45
+ end
46
+
47
+ def save!(assume_role_arn)
48
+ FileUtils.mkdir_p(Pathname.new(PATH).dirname)
49
+ self.class.base[assume_role_arn] = to_h
50
+ File.open(PATH, 'w') do |f|
51
+ f.puts(JSON.pretty_generate(self.class.base))
52
+ end
53
+ end
54
+
55
+ def shell_variables
56
+ {
57
+ AWS_ACCESS_KEY_ID: access_key_id,
58
+ AWS_SECRET_ACCESS_KEY: secret_access_key,
59
+ AWS_SESSION_TOKEN: session_token,
60
+ }.map{|k,v| "#{k}=#{v}"}
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,52 @@
1
+ require "optparse"
2
+ require "aws/asmr"
3
+
4
+ module Aws::ASMR
5
+ module Options
6
+ def partition(args)
7
+ _idx, asmr_args, command_args = args.reduce([1, [], []]) do |acc,i|
8
+ idx, _, _ = acc
9
+ unless i.start_with?('-')
10
+ idx = 2
11
+ acc[0] = idx
12
+ end
13
+ acc[idx] << i
14
+ acc
15
+ end
16
+ [asmr_args, command_args]
17
+ end
18
+
19
+ def parse(args)
20
+ options = {}
21
+ OptionParser.new do |opts|
22
+ # opts.banner = "Usage: asmr [options]"
23
+ opts.banner = <<~EOS
24
+ You can use ALIAS to shortcut name input by setting it at #{Aws::ASMR::ROOT}/alias
25
+
26
+ Usage: asmr [options] [command] [arg...]
27
+ EOS
28
+
29
+ opts.on("-nNAME", "--name=NAME", "Name to perform assume role with ARN or ALIAS") do |name|
30
+ options[:name] = name
31
+ end
32
+
33
+ opts.on("-h", "--help", "Prints this help") do
34
+ puts opts
35
+ exit(0)
36
+ end
37
+ opts.on("--version", "Prints version") do
38
+ options[:version] = true
39
+ end
40
+ opts.on("--clear", "Clear cache") do
41
+ options[:clear] = true
42
+ # require "aws/asmr/version"
43
+ # puts Aws::ASMR::VERSION
44
+ # exit(0)
45
+ end
46
+ end.parse(args)
47
+ options
48
+ end
49
+
50
+ module_function :partition, :parse
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ module Aws
2
+ module ASMR
3
+ VERSION = "0.0.0"
4
+ end
5
+ end
data/lib/aws/asmr.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'aws-sdk-iam'
2
+ require 'aws-sdk-sts'
3
+ require 'fileutils'
4
+
5
+ module Aws
6
+ module ASMR
7
+ ROOT = if ENV["AWS_ASMR_ROOT"] && !ENV["AWS_ASMR_ROOT"].empty?
8
+ Pathname.new(ENV["AWS_ASMR_ROOT"]).join('').to_s
9
+ else
10
+ Pathname.new(ENV['HOME']).join('.aws-asmr').to_s
11
+ end
12
+
13
+ # assume_role('arn', serial_number: 'serial-number', token_code: '012345')
14
+ def assume_role(assume_role_arn, **args)
15
+ sts = Aws::STS::Client.new(region: 'us-east-1')
16
+ res = sts.assume_role(
17
+ role_arn: assume_role_arn,
18
+ role_session_name: "aws-asmr",
19
+ **args
20
+ )
21
+ end
22
+
23
+ def detect_mfa_device_serial_number
24
+ iam_client = Aws::IAM::Client.new(region: 'us-east-1')
25
+ res = iam_client.list_mfa_devices
26
+ res.mfa_devices.detect(&:serial_number)&.serial_number
27
+ end
28
+
29
+ module_function :assume_role, :detect_mfa_device_serial_number
30
+ end
31
+ end
32
+
33
+ require 'aws/asmr/cache'
34
+ require 'aws/asmr/alias'
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws-asmr
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - metheglin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-01-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tty-prompt
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-sts
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: aws-sdk-iam
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ description: ''
56
+ email: pigmybank@gmail.com
57
+ executables:
58
+ - asmr
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - README.md
63
+ - bin/asmr
64
+ - lib/aws/asmr.rb
65
+ - lib/aws/asmr/alias.rb
66
+ - lib/aws/asmr/cache.rb
67
+ - lib/aws/asmr/options.rb
68
+ - lib/aws/asmr/version.rb
69
+ homepage: https://rubygems.org/gems/aws-asmr
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '2.7'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 3.1.4
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: aws command line tool for ASMR. ASMR stands for AssumeRole, obviously!
92
+ test_files: []