sty 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+