sty 0.0.1

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.
@@ -0,0 +1,9 @@
1
+ require 'psych'
2
+
3
+ class Config
4
+
5
+ def self.yaml(file)
6
+ Psych.load_file("#{file}.yaml")
7
+ end
8
+
9
+ end
@@ -0,0 +1,106 @@
1
+ require_relative 'functions'
2
+
3
+ require 'cgi'
4
+ require 'json'
5
+ require 'net/http'
6
+ require 'open3'
7
+
8
+ class Console
9
+
10
+ CONSOLE_URL = 'https://ap-southeast-2.console.aws.amazon.com/'
11
+ SIGN_IN_URL = 'https://signin.aws.amazon.com/federation'
12
+ SIGN_OUT_URL = 'https://ap-southeast-2.console.aws.amazon.com/console/logout!doLogout'
13
+
14
+ SESSION_DURATION_SECONDS = 43200
15
+
16
+ BROWSERS = %w(chrome firefox vivaldi safari)
17
+
18
+ def go(url, browser = '', incognito = false)
19
+ url = "'#{url}'"
20
+ run = %w(open)
21
+ run << '-n' if incognito
22
+ case browser.downcase
23
+ when 'safari'
24
+ run << "-a 'Safari'"
25
+ run << url
26
+ when 'chrome'
27
+ run << "-a 'Google Chrome'"
28
+ incognito ? run << "--args --incognito #{url}" : run << url
29
+ when 'firefox'
30
+ run << '-a Firefox'
31
+ incognito ? run << "--args -private-window #{url}" : run << url
32
+ when 'vivaldi'
33
+ run << '-a Vivaldi'
34
+ incognito ? run << "--args --incognito #{url}" : run << url
35
+ else
36
+ run << url
37
+ end
38
+
39
+ puts run.join(' ')
40
+ system(run.join(' '))
41
+ end
42
+
43
+ def signin_params
44
+ return @signin_params if @signin_params
45
+
46
+ creds_string = {'sessionId' => ENV['AWS_ACCESS_KEY_ID'],
47
+ 'sessionKey' => ENV['AWS_SECRET_ACCESS_KEY'],
48
+ 'sessionToken' => ENV['AWS_SESSION_TOKEN']}.to_json
49
+
50
+ uri = URI(SIGN_IN_URL)
51
+ params = {'Action' => 'getSigninToken',
52
+ 'Session' => creds_string}
53
+ uri.query = URI.encode_www_form(params)
54
+
55
+ begin
56
+ res = Net::HTTP.get_response(uri)
57
+ rescue Exception => e
58
+ puts red("ERROR! Unable to obtain signin token.")
59
+ puts white(e.message)
60
+ exit 1
61
+ end
62
+
63
+ unless res.is_a?(Net::HTTPSuccess)
64
+ puts red("ERROR! Unable to obtain signin token.")
65
+ puts white("#{SIGN_IN_URL} returns #{res.code}")
66
+ puts res.body
67
+ exit 1
68
+ end
69
+
70
+ @signin_params = JSON.parse(res.body)
71
+ end
72
+
73
+ def action(browser, incognito, logout)
74
+ if logout
75
+ logout(browser, incognito)
76
+ else
77
+ login(browser, incognito)
78
+ end
79
+ end
80
+
81
+ def logout(browser, incognito)
82
+ go(SIGN_OUT_URL, browser || '', incognito)
83
+ end
84
+
85
+ def login(browser, incognito)
86
+ act_acc
87
+
88
+ if remained_minutes <= 0
89
+ puts red("Session expired")
90
+ exit 1
91
+ end
92
+
93
+ signin_params['Action'] = 'login'
94
+ signin_params['Destination'] = CONSOLE_URL
95
+
96
+ params_str = signin_params.map do |k, v|
97
+ "#{k}=#{CGI.escape(v.to_s)}"
98
+ end.join('&')
99
+
100
+ console_url = "#{SIGN_IN_URL}?#{params_str}"
101
+
102
+ go(console_url, browser || '', incognito)
103
+ end
104
+
105
+
106
+ end
@@ -0,0 +1,22 @@
1
+ module RubyDig
2
+ def dig(key, *rest)
3
+ value = self[key]
4
+ if value.nil? || rest.empty?
5
+ value
6
+ elsif value.respond_to?(:dig)
7
+ value.dig(*rest)
8
+ else
9
+ fail TypeError, "#{value.class} does not have #dig method"
10
+ end
11
+ end
12
+ end
13
+
14
+ if RUBY_VERSION < '2.3'
15
+ class Array
16
+ include RubyDig
17
+ end
18
+
19
+ class Hash
20
+ include RubyDig
21
+ end
22
+ end
@@ -0,0 +1,94 @@
1
+ require 'psych'
2
+ require 'time'
3
+
4
+ def sty_home
5
+ home = File.expand_path('~')
6
+ "#{home}/.sty"
7
+ end
8
+
9
+ def colorize(string, color_code)
10
+ "\e[#{color_code}m#{string}\e[0m"
11
+ end
12
+
13
+ def red(string)
14
+ colorize(string, 31)
15
+ end
16
+
17
+ def green(string)
18
+ colorize(string, 32)
19
+ end
20
+
21
+ def yellow(string)
22
+ colorize(string, 33)
23
+ end
24
+
25
+ def magenta(string)
26
+ colorize(string, 35)
27
+ end
28
+
29
+ def white(string)
30
+ colorize(string, 97)
31
+ end
32
+
33
+ def to_path(fqn)
34
+ [fqn].flatten.map { |a| a.split("/") }.flatten
35
+ end
36
+
37
+ def to_fqn(path)
38
+ path.join('/')
39
+ end
40
+
41
+ def dir
42
+ @dir = @dir || sty_home
43
+ end
44
+
45
+ def cache_file(path, identity)
46
+ "#{dir}/auth-cache/#{path.join('-')}-#{identity}.yaml"
47
+ end
48
+
49
+ def yaml(file)
50
+ Psych.load_file("#{dir}/#{file}.yaml")
51
+ end
52
+
53
+ def dump(hash, file)
54
+ File.open("#{dir}/#{file}.yaml", 'w') do |f|
55
+ f.write(Psych.dump(hash))
56
+ end
57
+ end
58
+
59
+ def remained_minutes
60
+ ((Time.parse(ENV['AWS_SESSION_EXPIRY']) - Time.now) / 60).to_i
61
+ end
62
+
63
+ def region
64
+ ENV['AWS_REGION'] || DEFAULT_REGION
65
+ end
66
+
67
+ def act_acc
68
+ act_acc = ENV['AWS_ACTIVE_ACCOUNT']
69
+ unless act_acc
70
+ puts red('ERROR! AWS_ACTIVE_ACCOUNT variable is not set. Is your session authenticated ?')
71
+ exit 1
72
+ end
73
+ act_acc
74
+ end
75
+
76
+ def matches(container, value)
77
+ container = container.to_s
78
+ if /#{value}/i =~ container.to_s
79
+ match = $&
80
+ container.split(match).insert(1,white(match)).join
81
+ end
82
+ end
83
+
84
+ def deep_find(obj, value, result = {}, path = [])
85
+ if obj.is_a?(Hash)
86
+ obj.each do |k,v|
87
+ deep_find(v, value, result, path + [k])
88
+ end
89
+ return result
90
+ else
91
+ m = matches(obj, value)
92
+ result[to_fqn(path)] = m if m
93
+ end
94
+ end
@@ -0,0 +1,42 @@
1
+ require_relative 'functions'
2
+
3
+ class Info
4
+
5
+ def session_info
6
+ if ENV['AWS_ACTIVE_ACCOUNT']
7
+ puts "Active account: #{white(ENV['AWS_ACTIVE_ACCOUNT'])}"
8
+ else
9
+ puts red("You are not authenticated with sty")
10
+ end
11
+
12
+ if ENV['AWS_ACTIVE_IDENTITY']
13
+ puts "Active identity: #{white(ENV['AWS_ACTIVE_IDENTITY'])}"
14
+ end
15
+
16
+ if ENV['AWS_SESSION_EXPIRY']
17
+ if remained_minutes > 0
18
+ puts "Session active, expires in #{white(remained_minutes)} min."
19
+ else
20
+ puts red("Session expired")
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ def account_info(partial)
27
+
28
+ config = yaml('auth')
29
+
30
+ puts "Searching config for '#{partial}':"
31
+ result = deep_find(config, partial)
32
+
33
+ if result.any?
34
+ result.each do |k, v|
35
+ puts "#{k}: #{v}"
36
+ end
37
+ else
38
+ puts 'Nothing found'
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,3 @@
1
+ class Keychain > Store
2
+
3
+ end
@@ -0,0 +1,55 @@
1
+ require_relative 'functions'
2
+
3
+ class Proxy
4
+
5
+ def config
6
+ @config = @config || yaml('proxy')
7
+ end
8
+
9
+ def proxy_vars
10
+ ENV.select { |e| e =~ /(https?|no)_proxy/i }
11
+ end
12
+
13
+ def unset
14
+ proxy_vars.keys.map do |e|
15
+ "unset #{e}"
16
+ end
17
+ end
18
+
19
+ def set(px)
20
+ proxy = config[px]
21
+
22
+ unless proxy
23
+ STDERR.puts red("ERROR! Proxy #{px} was not found in the config file.")
24
+ exit 1
25
+ end
26
+
27
+ STDERR.puts "Proxy is set to #{proxy['proxy']}"
28
+ ["export http_proxy=#{proxy['proxy']}",
29
+ "export https_proxy=#{proxy['proxy']}",
30
+ "export no_proxy=#{proxy['no-proxy']}"]
31
+ end
32
+
33
+ def output(strings)
34
+ puts "#EVAL#"
35
+ strings.each do |s|
36
+ puts "#{s};"
37
+ end
38
+ end
39
+
40
+ def action(px)
41
+ case
42
+ when px.nil?
43
+ STDERR.puts "Current proxy vars:"
44
+ proxy_vars.each do |k,v|
45
+ STDERR.puts "#{k}=#{v}"
46
+ end
47
+ when px =~ /off/i
48
+ STDERR.puts "Proxy vars unset"
49
+ output(unset)
50
+ else
51
+ output(unset + set(px))
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,270 @@
1
+ require_relative 'functions'
2
+ require 'uri'
3
+
4
+ class Ssh
5
+
6
+ def initialize
7
+ require 'aws-sdk-ec2'
8
+ Aws.config.update(:http_proxy => ENV['https_proxy'])
9
+ end
10
+
11
+ def all_instances
12
+ instances = []
13
+ options = {}
14
+
15
+ loop do
16
+ resp = ec2.describe_instances(options)
17
+ instances += resp.reservations.map { |r| r.instances }.flatten
18
+ break unless resp.next_token
19
+ options[:next_token] = resp.next_token
20
+ end
21
+
22
+ instances
23
+ end
24
+
25
+ def valid_key_file?(key_name, key_file)
26
+ aws_fp = ec2.describe_key_pairs(key_names: [key_name]).key_pairs[0].key_fingerprint
27
+ calculated_fp = `openssl pkcs8 -in #{key_file} -nocrypt -topk8 -outform DER | openssl sha1 -c`.chomp
28
+ aws_fp == calculated_fp
29
+ end
30
+
31
+ def instance_id(instance)
32
+ instance.instance_id
33
+ end
34
+
35
+ def instance_name(instance)
36
+ name_tag = instance.tags.select { |t| t.key == 'Name' }.first
37
+ name_tag ? name_tag.value : "None"
38
+ end
39
+
40
+ def instance_ips(instance)
41
+ instance.network_interfaces.map { |n| n.private_ip_address }
42
+ end
43
+
44
+ def extract_fields(inst)
45
+ [instance_name(inst), instance_id(inst)] + instance_ips(inst)
46
+ end
47
+
48
+ def prepare_regex(names)
49
+ names = ['.'] if names.empty?
50
+ names.map { |n| Regexp.new(Regexp.quote(n), Regexp::IGNORECASE) }
51
+ end
52
+
53
+ def find(instances, names)
54
+ re = prepare_regex(names)
55
+ instances.select do |i|
56
+ i.state.name == 'running' &&
57
+ extract_fields(i).any? { |f| re.all? { |r| f =~ r } }
58
+ end.sort{|a,b| instance_name(a) <=> instance_name(b)}
59
+ end
60
+
61
+ def win?(instance)
62
+ instance.platform =~ /windows/i
63
+ end
64
+
65
+ def platform(instance)
66
+ win?(instance) ? " [Win] " : ""
67
+ end
68
+
69
+ def print_refine_instances(instances, type)
70
+ puts "Please refine #{type} instance:"
71
+ instances.each_with_index do |inst, idx|
72
+ puts "#{idx}: #{instance_id(inst)} [#{instance_name(inst)}] #{platform(inst)}(#{instance_ips(inst).join(', ')}) "
73
+ end
74
+ end
75
+
76
+ def print_refine_ips(ips)
77
+ puts 'Please refine IP address:'
78
+ ips.each_with_index do |ip, idx|
79
+ puts "#{idx}: #{ip}"
80
+ end
81
+ end
82
+
83
+ #Not used anymore
84
+ def print_refine_keys(keys)
85
+ puts 'Please refine key:'
86
+ keys.each_with_index do |key, idx|
87
+ puts "#{idx}: #{key}"
88
+ end
89
+ end
90
+
91
+ def find_path(hash, path)
92
+ path.split('/').reduce(hash) { |memo, path| memo[path] if memo}
93
+ end
94
+
95
+ def path?(str)
96
+ str =~ /\//
97
+ end
98
+
99
+ def path_error(path)
100
+ puts red("ERROR ! No jumphost found for #{path}")
101
+ exit 1
102
+ end
103
+
104
+ def config
105
+ @config = @config || yaml('ec2')
106
+ end
107
+
108
+ def ec2
109
+ @ec2 = @ec2 || Aws::EC2::Client.new
110
+ end
111
+
112
+ def auth_config
113
+ @auth_config = @auth_config || yaml('auth-keys')
114
+ end
115
+
116
+ def username
117
+ auth_config['ec2-username'] || auth_config['username']
118
+ end
119
+
120
+ def key_dir
121
+ ssh_keys = auth_config['ssh-keys']
122
+ ssh_keys = "#{dir}/#{ssh_keys}" unless ssh_keys =~ /^\/|^~/
123
+ ssh_keys
124
+ end
125
+
126
+ def refine(found)
127
+ if found.size > 1
128
+ refine = nil
129
+ loop do
130
+ yield(found)
131
+ refine = Integer(STDIN.gets.chomp) rescue false
132
+ break if refine && refine < found.size
133
+ end
134
+ target = found[refine]
135
+ else
136
+ target = found.first
137
+ end
138
+ target
139
+ end
140
+
141
+ def find_jumphost_from_config(act_acc, platform)
142
+ jumphosts = find_path(config, act_acc)
143
+
144
+ unless jumphosts
145
+ puts red("No jumphost configured for #{act_acc}")
146
+ exit 1
147
+ end
148
+
149
+ path_error(act_acc) unless jumphosts
150
+
151
+ if path?(jumphosts)
152
+ jumphost_path = jumphosts
153
+ jumphosts = find_path(config, jumphost_path)
154
+ end
155
+
156
+ path_error(jumphost_path) unless jumphosts
157
+
158
+ jumphosts[platform]
159
+ end
160
+
161
+ def find_instance(args, type)
162
+ found = find(all_instances, args)
163
+ if found.empty?
164
+ puts "No instances found for search terms: #{args.join(', ')}"
165
+ exit 1
166
+ end
167
+ target = refine(found) {|found| print_refine_instances(found, type)}
168
+ ip = refine(instance_ips(target)) {|found| print_refine_ips(found)}
169
+ OpenStruct.new(ip: ip,
170
+ platform: win?(target) ? 'windows' : 'linux',
171
+ key_name: target.key_name)
172
+ end
173
+
174
+ def find_key(key_name)
175
+
176
+ keys_glob = Dir.glob("#{key_dir}/**/#{key_name}.pem")
177
+
178
+ if keys_glob.size < 1
179
+ puts red("ERROR! Unable to find key #{key_name}.pem within #{key_dir}")
180
+ exit 1
181
+ end
182
+
183
+ key = keys_glob.detect do |key_file|
184
+ valid_key_file?(key_name,key_file)
185
+ end
186
+
187
+ unless key
188
+ puts red("ERROR! There is no key file with matching fingerprint")
189
+ exit 1
190
+ end
191
+
192
+ key
193
+ end
194
+
195
+ def print_command(cmd)
196
+ puts "Generated command:\n------------------------\n#{cmd}"
197
+ puts '------------------------'
198
+ end
199
+
200
+ def connect(search, no_jumphost, jumphost_override, target_key)
201
+
202
+ no_strict = "-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no"
203
+ server_alive = "-o ServerAliveInterval=25"
204
+ ec2_user = 'ec2-user'
205
+
206
+ opts = []
207
+ opts << 'no_jumphost' if no_jumphost
208
+ opts << 'select_jumphost' if jumphost_override
209
+ opts << 'use_key' if target_key
210
+ puts "Enabled options: #{opts.join(', ')}"
211
+ puts "Requested search terms: #{search.join(', ')}"
212
+
213
+ puts "Active account: #{act_acc}"
214
+
215
+ instance = find_instance(search, 'target')
216
+
217
+ if instance.platform == 'windows'
218
+
219
+ # username=s:#{username}
220
+ #
221
+ jumphost = find_jumphost_from_config(act_acc, instance.platform)
222
+ cmd = %W(
223
+ rdp://gatewayusagemethod:i:1
224
+ gatewaycredentialssource:i:4
225
+ gatewayprofileusagemethod:i:1
226
+ promptcredentialonce:i:0
227
+ gatewaybrokeringtype:i:0
228
+
229
+ gatewayhostname=s:#{jumphost}
230
+ full\ address=s:#{instance.ip}
231
+ )
232
+ cmd = URI.escape(cmd.join('&'))
233
+ cmd = "open \"#{cmd}\""
234
+
235
+ else
236
+
237
+ proxy = ''
238
+ unless no_jumphost
239
+
240
+ if jumphost_override
241
+ puts 'Type jumphost search terms:'
242
+ jumphost_query = STDIN.gets.chomp
243
+ jh_instance = find_instance([jumphost_query], 'jumphost')
244
+ jh_key = find_key(jh_instance.key_name)
245
+ jh_string = "-i #{jh_key} #{ec2_user}@#{jh_instance.ip}"
246
+ else
247
+ jumphost = find_jumphost_from_config(act_acc, instance.platform)
248
+ jh_string = "#{username}@#{jumphost}"
249
+ end
250
+
251
+ proxy = "-o ProxyCommand='ssh #{server_alive} #{no_strict} #{jh_string} nc #{instance.ip} 22'"
252
+ end
253
+
254
+
255
+ if target_key
256
+ key = find_key(instance.key_name)
257
+ target_string = "-i #{key} #{ec2_user}@#{instance.ip}"
258
+ else
259
+ target_string = "#{username}@#{instance.ip}"
260
+ end
261
+
262
+ cmd = "ssh #{proxy} #{no_strict} #{server_alive} #{target_string}"
263
+
264
+ end
265
+ print_command(cmd)
266
+ exec cmd
267
+
268
+ end
269
+ end
270
+