@govuk-pay/cli 0.0.2 → 0.0.4

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.
Files changed (89) hide show
  1. package/package.json +1 -1
  2. package/resources/legacy-ruby-cli/.rspec +1 -0
  3. package/resources/legacy-ruby-cli/.rubocop.yml +26 -0
  4. package/resources/legacy-ruby-cli/.ruby-version +1 -0
  5. package/resources/legacy-ruby-cli/Gemfile +24 -0
  6. package/resources/legacy-ruby-cli/Gemfile.lock +1431 -0
  7. package/resources/legacy-ruby-cli/README.md +142 -0
  8. package/resources/legacy-ruby-cli/bin/pay +31 -0
  9. package/resources/legacy-ruby-cli/config/generate-secrets.yml +9 -0
  10. package/resources/legacy-ruby-cli/config/secrets.yml +581 -0
  11. package/resources/legacy-ruby-cli/config/service_secrets.yml +174 -0
  12. package/resources/legacy-ruby-cli/lib/pay_cli/aws/document.rb +23 -0
  13. package/resources/legacy-ruby-cli/lib/pay_cli/aws/services.rb +47 -0
  14. package/resources/legacy-ruby-cli/lib/pay_cli/aws/tokens.rb +161 -0
  15. package/resources/legacy-ruby-cli/lib/pay_cli/commands/aws.rb +51 -0
  16. package/resources/legacy-ruby-cli/lib/pay_cli/commands/browse.rb +31 -0
  17. package/resources/legacy-ruby-cli/lib/pay_cli/commands/doctor.rb +154 -0
  18. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/app_client.rb +216 -0
  19. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/config.rb +138 -0
  20. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/config.yaml +192 -0
  21. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/docker.rb +36 -0
  22. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/docker-compose.erb +270 -0
  23. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/end-to-end.erb +30 -0
  24. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/localstack/init-aws.sh +70 -0
  25. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/naxsi/readme.md +1 -0
  26. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/postgres/docker-entrypoint-initdb.d/make_payments_databases.sql +26 -0
  27. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/adminusers.env +49 -0
  28. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/cardid.env +2 -0
  29. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/connector.env +70 -0
  30. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/demo-service.env +10 -0
  31. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/frontend.env +12 -0
  32. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/java_app.env +1 -0
  33. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ledger.env +7 -0
  34. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/products-ui.env +14 -0
  35. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/products.env +25 -0
  36. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/publicapi.env +13 -0
  37. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/publicauth.env +13 -0
  38. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/selfservice.env +21 -0
  39. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/certs/frontend-proxy.crt +18 -0
  40. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/certs/products-ui-proxy.crt +20 -0
  41. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/certs/selfservice-proxy.crt +20 -0
  42. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/certs/stubs-proxy.crt +18 -0
  43. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/keys/frontend-proxy.key +28 -0
  44. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/keys/products-ui-proxy.key +28 -0
  45. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/keys/selfservice-proxy.key +28 -0
  46. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/keys/stubs-proxy.key +28 -0
  47. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/make-selfsigned.sh +2 -0
  48. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/stubs.env +12 -0
  49. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/toolbox.env +5 -0
  50. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/webhooks.env +9 -0
  51. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/image_extractor.rb +20 -0
  52. package/resources/legacy-ruby-cli/lib/pay_cli/commands/local.rb +430 -0
  53. package/resources/legacy-ruby-cli/lib/pay_cli/commands/schema.rb +36 -0
  54. package/resources/legacy-ruby-cli/lib/pay_cli/commands/secrets.rb +114 -0
  55. package/resources/legacy-ruby-cli/lib/pay_cli/commands/ssm.rb +111 -0
  56. package/resources/legacy-ruby-cli/lib/pay_cli/commands/tf.rb +90 -0
  57. package/resources/legacy-ruby-cli/lib/pay_cli/commands/tunnel/services.yml +49 -0
  58. package/resources/legacy-ruby-cli/lib/pay_cli/config.rb +27 -0
  59. package/resources/legacy-ruby-cli/lib/pay_cli/ec2.rb +38 -0
  60. package/resources/legacy-ruby-cli/lib/pay_cli/entry_point.rb +52 -0
  61. package/resources/legacy-ruby-cli/lib/pay_cli/environment.rb +25 -0
  62. package/resources/legacy-ruby-cli/lib/pay_cli/logger.rb +3 -0
  63. package/resources/legacy-ruby-cli/lib/pay_cli/logs.rb +248 -0
  64. package/resources/legacy-ruby-cli/lib/pay_cli/naming.rb +44 -0
  65. package/resources/legacy-ruby-cli/lib/pay_cli/secrets.rb +276 -0
  66. package/resources/legacy-ruby-cli/lib/pay_cli/stop_yubico_authenticator.rb +10 -0
  67. package/resources/legacy-ruby-cli/lib/pay_cli/ykman_oath_credential_config.rb +70 -0
  68. package/resources/legacy-ruby-cli/lib/zeitwerk_setup.rb +5 -0
  69. package/resources/legacy-ruby-cli/package-lock.json +6 -0
  70. package/resources/legacy-ruby-cli/rds_access/connect.sh +149 -0
  71. package/resources/legacy-ruby-cli/spec/.rubocop.yml +2 -0
  72. package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.complex +34 -0
  73. package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.complex_differing_froms +33 -0
  74. package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.no_from +3 -0
  75. package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.simple +5 -0
  76. package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.simple_no_tag +5 -0
  77. package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.with_sha +5 -0
  78. package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.with_sha_no_tag +5 -0
  79. package/resources/legacy-ruby-cli/spec/lib/pay_cli/commands/local/image_extractor_spec.rb +55 -0
  80. package/resources/legacy-ruby-cli/spec/naming_spec.rb +83 -0
  81. package/resources/legacy-ruby-cli/spec/spec_helper.rb +106 -0
  82. package/resources/legacy-ruby-cli/vulnerability_scan/.nvmrc +1 -0
  83. package/resources/legacy-ruby-cli/vulnerability_scan/generate_vulnerability_report.js +91 -0
  84. package/resources/legacy-ruby-cli/vulnerability_scan/reports/.gitkeep +0 -0
  85. package/resources/legacy-ruby-cli/vulnerability_scan/scan.sh +57 -0
  86. package/src/commands/browse.js +2 -2
  87. package/src/commands/legacy.js +5 -2
  88. package/src/core/constants.js +7 -10
  89. package/src/util/payCliExec.js +18 -1
@@ -0,0 +1,430 @@
1
+ require 'English'
2
+ require 'erb'
3
+ require 'yaml'
4
+ require 'rest-client'
5
+ require 'rotp'
6
+ require 'base32'
7
+ require 'securerandom'
8
+ require 'active_support'
9
+ require 'active_support/core_ext'
10
+ require 'digest'
11
+ require 'mkmf'
12
+ require 'pathname'
13
+
14
+ REQUIRES_COMPILATION = %w[frontend products-ui selfservice toolbox].freeze
15
+
16
+ class PayCLI::Commands::Local < Thor
17
+ map "up" => :launch
18
+ desc 'launch | up [--cluster <cluster>] [--local <app>] [--proxy] [--apps] [--rebuild]',
19
+ 'Launch a predefined cluster, or a user specified list of apps.' \
20
+ ' Specify a list of apps that should use your locally checked out code using --local'
21
+ option :local, :type => :array, :default => []
22
+ option :proxy, :type => :boolean, :default => false
23
+ option :cluster, :enum => ['admin', 'card', 'paymentlinks', 'webhooks', 'endtoend', 'java', 'toolbox']
24
+ option :apps, :type => :array
25
+ option :rebuild, :type => :boolean, :default => true
26
+ option :experimental, :type => :boolean, :default => false
27
+ option :pull, :type => :boolean, :default => true, :desc => "Pull the latest versions of containers prior to launching, override with --no-pull"
28
+
29
+ def launch()
30
+ all = options[:experimental] ? Config::all : Config.boring
31
+ if options[:cluster]
32
+ cluster = options[:cluster]
33
+ warn "🚀 Launching #{cluster} cluster 🚀\n\n"
34
+ apps = Config::cluster cluster, all
35
+ elsif options[:apps]
36
+ cluster = 'custom'
37
+ warn "🚀 Launching #{options[:apps]} 🚀\n\n"
38
+ apps = Config::filter options[:apps], all
39
+ else
40
+ cluster = 'all'
41
+ apps = all
42
+ end
43
+
44
+ @node_apps = Config::node apps
45
+ @java_apps = Config::java apps
46
+
47
+ local_app_names = options.fetch :local
48
+ @local_node_apps = Config.local local_app_names, @node_apps
49
+ @local_java_apps = Config.local local_app_names, @java_apps
50
+ @remote_node_apps = Config.remote local_app_names, @node_apps
51
+ @remote_java_apps = Config.remote local_app_names, @java_apps
52
+
53
+ @dbs = Config.db_configs Config.has_db apps
54
+ @queue_apps = Config.has_queue apps
55
+ @sns_topic_apps = Config.has_sns_topics apps
56
+
57
+ # Force restart of localstack (which mocks aws services)
58
+ Docker.remove('localstack') if Config.uses_localstack(apps)
59
+
60
+ @proxies = if options[:proxy] then Config::proxy_configs Config::has_proxy apps else [] end
61
+
62
+ @localstack_init_file = File.realpath(
63
+ File.join(__dir__, 'local', 'files', 'localstack', 'init-aws.sh')
64
+ )
65
+
66
+ if options[:rebuild]
67
+ check_for_workspace_env
68
+
69
+ threads = []
70
+ (@local_java_apps + @local_node_apps).each do |app|
71
+ check_for_local_repo("pay-#{app[:name]}")
72
+ check_cardid_data if app[:name] == 'cardid'
73
+
74
+ threads << Thread.new do
75
+ ws_dir = Pathname.new(ENV['WORKSPACE']).join("pay-#{app[:name]}")
76
+
77
+ if RUBY_PLATFORM.include? 'arm64'
78
+ arm_dockerfile = ws_dir.join('m1', 'arm64.Dockerfile')
79
+ dockerfile = arm_dockerfile.exist? ? arm_dockerfile : ws_dir.join('Dockerfile')
80
+
81
+ warn "🐋 generating arm64 governmentdigitalservice/pay-#{app[:name]}:local from #{dockerfile}"
82
+ `(cd #{ws_dir} && docker build -q . -f #{dockerfile} --load -t governmentdigitalservice/pay-#{app[:name]}:local)`
83
+ else
84
+ warn "🐋 generating governmentdigitalservice/pay-#{app[:name]}:local"
85
+ `(cd #{ws_dir} && docker build -q -t governmentdigitalservice/pay-#{app[:name]}:local .)`
86
+ end
87
+
88
+ report_status! $CHILD_STATUS, app[:name], 'loading image for'
89
+ end
90
+ end
91
+
92
+ threads.each(&:join)
93
+ end
94
+
95
+ Docker::write_compose_file cluster, binding
96
+
97
+ if cluster.include? 'endtoend'
98
+ warn "💁 Here are the endtoend environment variables\n\n#{Config::write_end_to_end_config binding}\n\n"
99
+ end
100
+
101
+ puts Docker::pull cluster if options[:pull]
102
+ puts Docker::up cluster
103
+
104
+ AppClient::wait_for_apps apps
105
+ AppClient::report_state apps
106
+ end
107
+
108
+ desc 'down --cluster <cluster>',
109
+ 'Bring down a cluster'
110
+ option :cluster, :enum => ['admin', 'card', 'paymentlinks', 'webhooks', 'endtoend']
111
+ def down()
112
+ cluster = options[:cluster]
113
+ warn "🛬 bringing down #{cluster} cluster"
114
+ puts Docker::down cluster
115
+ end
116
+
117
+ desc 'healthcheck <cluster>',
118
+ "Healthcheck"
119
+
120
+ def healthcheck(cluster)
121
+ AppClient::report_state Config::cluster cluster, Config::all
122
+ end
123
+
124
+ desc 'nuke',
125
+ "Kill and completely destroy all containers previously started by pay local"
126
+
127
+ def nuke
128
+ warn "💥 Nuking all containers previously started by pay local 💥"
129
+ warn "🚨 WARNING - this deletes the database files too, stop it now or accept your fate 🚨"
130
+
131
+ Config::all.each do |app|
132
+ Docker::remove("#{app[:name]}")
133
+ Docker::remove("#{app[:name]}-proxy") if app[:proxy]
134
+ Docker::remove("#{app[:name]}_db") if app[:db]
135
+ end
136
+ Docker::remove('localstack')
137
+ Docker::remove('localRedis')
138
+ end
139
+
140
+ desc 'restart',
141
+ 'Restart a container and waits for it to be healthy.'
142
+
143
+ def restart(app_name)
144
+ warn "😅 restarting #{app_name}"
145
+
146
+ puts `docker restart #{app_name}`
147
+ app = Config::app_by_name(app_name)
148
+ AppClient::wait_for_apps(app)
149
+ AppClient::report_state(app)
150
+ end
151
+
152
+ desc 'paymentlink',
153
+ 'Create a new payment link, requires an api token.'
154
+ option :api_key, :required => true
155
+
156
+ def paymentlink
157
+ product = AppClient::create_payment_link options.fetch :api_key
158
+
159
+ links = {}
160
+ product["_links"].each {|link| links[link["rel"]] = link["href"]}
161
+
162
+ warn links
163
+ `open #{links["pay"]}`
164
+ end
165
+
166
+ map :populate => :user
167
+ desc 'user',
168
+ 'create a local user'
169
+ option :create_payments, :type => :boolean, :default => true
170
+
171
+ def user
172
+ warn '🏗️🏗 Constructing useful things for you...'
173
+ gateway_account_ids = []
174
+
175
+ service = AppClient::create_service()
176
+ service_id = service.fetch('external_id')
177
+
178
+ if AppClient::is_app_up?('connector')
179
+ warn '💳 Card connector is up, creating card gateway account and API Token'
180
+ card_account = AppClient::create_account(service_id)
181
+ card_gateway_account_id = extract_gateway_account_id(card_account)
182
+ card_token = AppClient::create_token(card_gateway_account_id)
183
+ gateway_account_ids.push(card_gateway_account_id)
184
+ if options[:create_payments] and AppClient::is_app_up? 'publicapi'
185
+ warn '💸 Creating payments in card sandbox gateway account'
186
+ (1..11).to_a.each do
187
+ sleep 1
188
+ (1..5).to_a.each {AppClient::create_payment card_token}
189
+ end
190
+ end
191
+ end
192
+
193
+ warn '🤓 Creating admin user for service'
194
+ user = AppClient::create_user(gateway_account_ids: gateway_account_ids)
195
+ warn '😎 Creating read only user for service'
196
+ AppClient::create_user(gateway_account_ids: gateway_account_ids, role_name: 'view-only')
197
+
198
+ otp_key = user['otp_key']
199
+
200
+ warn TTY::Table.new([
201
+ ['📧 Email', user[:email]],
202
+ ['🛂 Password', user[:password]],
203
+ ['🔑 OTP key', otp_key],
204
+ ['📱 OTP token', generate_otp_token(otp_key)],
205
+ ['💁 Service ID', service_id],
206
+ ['💳 Card gateway account ID', card_gateway_account_id],
207
+ ['🎫 Card API token', card_token],
208
+ ]).render(:unicode, padding: 1, multiline: true)
209
+
210
+ pbcopy user[:email]
211
+
212
+ if AppClient::is_proxy_up? 'selfservice'
213
+ browse 'selfservice', true
214
+ elsif AppClient::is_app_up? 'selfservice'
215
+ browse 'selfservice'
216
+ end
217
+ end
218
+
219
+ desc 'account',
220
+ 'create a local gateway account'
221
+ option :service_id
222
+
223
+ def account
224
+ service_id = options.fetch(:service_id, AppClient::create_service().fetch('external_id'))
225
+ warn "#{AppClient::create_account(service_id).to_json}"
226
+ end
227
+
228
+
229
+ desc 'token',
230
+ 'create a token'
231
+ option :gateway_account_id
232
+
233
+ def token
234
+ service = AppClient::create_service()
235
+ service_id = service.fetch('external_id')
236
+ gateway_account_id = options.fetch(
237
+ :gateway_account_id,
238
+ extract_gateway_account_id( AppClient::create_account service_id)
239
+ )
240
+ newToken = AppClient::create_token gateway_account_id
241
+ warn newToken
242
+ pbcopy newToken
243
+ end
244
+
245
+ desc 'payment',
246
+ 'create a payment'
247
+ option :api_key
248
+ option :email_collection_mode, :default => 'MANDATORY', :enum => ['MANDATORY', 'OPTIONAL', 'OFF']
249
+
250
+ def payment
251
+ api_token = options.fetch(:api_key, false)
252
+
253
+ unless api_token
254
+ warn "👔 Creating gateway account and service"
255
+
256
+ email_settings = { email_collection_mode: "#{options.fetch(:email_collection_mode)}" }
257
+
258
+ service = AppClient::create_service()
259
+ service_id = service.fetch('external_id')
260
+
261
+ gateway_account_id = extract_gateway_account_id(AppClient::create_account(service_id, email_settings))
262
+
263
+ api_token = AppClient::create_token(gateway_account_id)
264
+
265
+ warn TTY::Table.new([
266
+ ['🆔 Gateway account ID', gateway_account_id],
267
+ ['💁 Service ID', service_id]
268
+ ]).render(:unicode, padding: 1, multiline: true)
269
+ end
270
+
271
+ warn "💸 Creating payment"
272
+
273
+ payment = AppClient::create_payment(api_token)
274
+ payment_id = payment.fetch('payment_id')
275
+
276
+ warn TTY::Table.new([
277
+ ['💷 Payment ID', payment_id],
278
+ ['🎫 API token', api_token]
279
+ ]).render(:unicode, padding: 1, multiline: true)
280
+
281
+ next_url = payment.dig('_links', 'next_url', 'href')
282
+ `open #{next_url}`
283
+ end
284
+
285
+ desc 'otp <key>',
286
+ 'create otp code'
287
+
288
+ def otp(key)
289
+ otp = generate_otp_token(key)
290
+ warn otp
291
+ pbcopy otp
292
+ end
293
+
294
+ desc 'db <app_name>',
295
+ 'Connect to <app_name> database, optionally using psql inside the apps database ' \
296
+ 'container (note this means you wont get, or save, your psql history)'
297
+ option :docker, type: :boolean, default: false
298
+
299
+ def db(app_name)
300
+ if options[:docker]
301
+ _db_with_docker(app_name)
302
+ elsif find_executable('psql').nil?
303
+ warn 'PSQL installation not found locally'
304
+ _db_with_docker(app_name)
305
+ else
306
+ _db_with_local_psql(app_name)
307
+ end
308
+ end
309
+
310
+ desc 'browse <service>',
311
+ 'browse to local service'
312
+
313
+ def browse(app_name, proxy = false)
314
+ url = base_url(app_name, proxy)
315
+ `open #{url}`
316
+ end
317
+
318
+ desc 'url <service>',
319
+ 'copy base URL of local service to clipboard'
320
+
321
+ def url(app_name, proxy = false)
322
+ pbcopy base_url(app_name, proxy)
323
+ end
324
+
325
+ no_commands do
326
+ def generate_otp_token(otp_key)
327
+ /^[A-Z2-7]+$/.match(otp_key) ? ROTP::TOTP.new(otp_key).now : ROTP::TOTP.new(Base32.encode(otp_key)).now
328
+ end
329
+
330
+ def pbcopy(input)
331
+ str = input.to_s
332
+ IO.popen('pbcopy', 'w') { |f| f << str }
333
+ warn "📋 “#{str}” copied to clipboard"
334
+ str
335
+ end
336
+
337
+ def extract_gateway_account_id(account)
338
+ account.fetch('gateway_account_external_id', account.fetch('gateway_account_id'))
339
+ end
340
+
341
+ def base_url(app_name, proxy)
342
+ if proxy then
343
+ AppClient::base_proxy_url app_name
344
+ else
345
+ AppClient::base_url app_name
346
+ end
347
+ end
348
+
349
+ def report_status!(child_status, name, action='building')
350
+ emoji = child_status.success? ? '✅' : '🔴'
351
+ status = child_status.success? ? 'success' : 'failure'
352
+
353
+ warn "#{emoji} #{action} #{name} was a #{status}"
354
+ end
355
+
356
+ def check_for_workspace_env
357
+ unless ENV.include? 'WORKSPACE'
358
+ abort '❌ Environment variable WORKSPACE must be set. ' \
359
+ 'It should be the directory which contains all your other pay-* repos'
360
+ end
361
+
362
+ unless Dir.exist?(ENV['WORKSPACE'])
363
+ abort "❌ Environment variable WORKSPACE is set to #{ENV['WORKSPACE']} which does not exist." \
364
+ 'It must point to the directory which contains all your other pay-* repos'
365
+ end
366
+ end
367
+
368
+ def check_for_local_repo(repo_name)
369
+ repo_path = File.join(ENV['WORKSPACE'], repo_name)
370
+
371
+ unless Dir.exist?(repo_path)
372
+ abort "❌ Repo #{repo_name} does not exist in #{ENV['WORKSPACE']}, "\
373
+ "you need to clone it with `git clone git@github.com:alphagov/#{repo_name}.git #{repo_path}"
374
+ end
375
+ end
376
+
377
+ def check_cardid_data
378
+ cardid_path = File.join(ENV['WORKSPACE'], 'pay-cardid')
379
+
380
+ unless Dir.exist?(File.join(cardid_path, 'data', 'sources'))
381
+ abort '❌ pay-cardid does not have the cardid-data submodule updated. '\
382
+ 'Run `git submodule update --init && git -C data pull origin master` in '\
383
+ "#{cardid_path} to init and update the cardid-data submodule"
384
+ end
385
+ end
386
+
387
+ def npm_install(repo_name)
388
+ repo_path = File.join(ENV['WORKSPACE'], repo_name)
389
+ `cd #{repo_path} && npm install >>/dev/null 2>&1`
390
+ end
391
+
392
+ def npm_run_compile(repo_name)
393
+ repo_path = File.join(ENV['WORKSPACE'], repo_name)
394
+ `cd #{repo_path} && npm run compile >>/dev/null 2>&1`
395
+ end
396
+
397
+ def get_image_from_dockerfile_in_repo(repo_name)
398
+ repo_path = File.join(ENV['WORKSPACE'], repo_name)
399
+ m1_dockerfile_path = File.join(repo_path, 'm1', 'arm64.Dockerfile')
400
+ default_dockerfile_path = File.join(repo_path, 'Dockerfile')
401
+
402
+ dockerfile_path =
403
+ if RUBY_PLATFORM.include?('arm64') && File.exist?(m1_dockerfile_path)
404
+ m1_dockerfile_path
405
+ else
406
+ default_dockerfile_path
407
+ end
408
+
409
+ PayCLI::Commands::Local::ImageExtractor.parse_image_without_sha(dockerfile_path)
410
+ end
411
+
412
+ def _db_with_docker(app_name)
413
+ normalised_app_name = app_name.tr('-', '_')
414
+
415
+ warn 'Running psql in running app db container.'
416
+ warn 'Note: This means you wont have a psql history, and one will not survive restarts of pay local'
417
+
418
+ exec "docker exec -ti -e PGPASSWORD=mysecretpassword #{normalised_app_name}_db \
419
+ psql --host localhost --user #{normalised_app_name} --dbname #{normalised_app_name}"
420
+ end
421
+
422
+ def _db_with_local_psql(app_name)
423
+ normalised_app_name = app_name.tr('-', '_')
424
+
425
+ port = Config.all.select { |app| app[:name] == app_name }.map { |app| app[:db_port] }.first
426
+ exec "PGPASSWORD=mysecretpassword \
427
+ psql -h localhost -p #{port} -U #{normalised_app_name} -d #{normalised_app_name}"
428
+ end
429
+ end
430
+ end
@@ -0,0 +1,36 @@
1
+ module PayCLI::Commands::Schema
2
+ def self.usage!
3
+ STDERR.puts <<~USAGE
4
+ pay schema ...
5
+
6
+ Use this command to generate metadata including ER diagrams for a microservice database.
7
+
8
+ Usage
9
+ - `pay schema [microservice]` : microservice [connector, adminusers, publicauth, products]
10
+
11
+ USAGE
12
+ exit
13
+ end
14
+
15
+ def self.start!
16
+ args = ARGV[1..-1] || []
17
+ usage! if args.empty?
18
+
19
+ service = args.first
20
+ generate_schema service
21
+ end
22
+
23
+ def self.generate_schema(service)
24
+ begin
25
+ pay_scripts_absolute_path = File.expand_path('../../../../../pay-scripts', __dir__)
26
+
27
+ scripts_dir = ENV['PAY_SCRIPTS_DIR'] || pay_scripts_absolute_path
28
+ exec "#{scripts_dir}/generate-db-design.sh #{service} --browse"
29
+ rescue => error
30
+ puts '`pay-scripts` repository is not found or not latest.'
31
+ puts 'Make sure repository is latest and is available in the location below or set $PAY_SCRIPTS_DIR environment for location'
32
+ puts "Location : " + pay_scripts_absolute_path
33
+ end
34
+ end
35
+
36
+ end
@@ -0,0 +1,114 @@
1
+ require 'tty-table'
2
+
3
+
4
+ class PayCLI::Commands::Secrets < Thor
5
+ desc 'fetch <env> <service> <secret_name> --use-ssm ',
6
+ 'Fetches a <named secret> for <service> for <env>, if use-ssm is set then get the secret from ssm'
7
+ method_option :use_ssm, :aliases => "-s", :desc => "Query ssm to get the value"
8
+ def fetch(env, service, secret_name)
9
+ PayCLI::StopYubicoAuthenticator.stop_yubico_authenticator!
10
+ secret_value = PayCLI::Secrets.fetch! env, service, secret_name, options[:use_ssm]
11
+
12
+ STDERR.puts "Found value for #{secret_name} in #{env} for #{service}"
13
+ print secret_value.chomp
14
+ end
15
+
16
+ desc 'audit <service> <env1> [<env2> ... <envn>]',
17
+ 'Audits secrets for <service> for the given <envs*>'
18
+ def audit(service, *envs)
19
+ PayCLI::StopYubicoAuthenticator.stop_yubico_authenticator!
20
+ STDERR.puts "Auditing secrets for #{service} in #{envs.inspect}"
21
+
22
+ secrets_for_env = PayCLI::Secrets.secrets_for_service service
23
+ secrets_in_envs = PayCLI::Secrets.secrets_in_envs_for_service envs, service
24
+
25
+ header = [service].concat(envs)
26
+ rows = secrets_for_env.map do |secret|
27
+ [secret].concat(
28
+ envs.map { |e| secrets_in_envs[e].include?(secret) ? '✓' : '' }
29
+ )
30
+ end
31
+ footer = ["Other"].concat(
32
+ envs.map { |e| (secrets_in_envs[e] - secrets_for_env).join("\n") }
33
+ )
34
+
35
+ table = TTY::Table.new [header] + rows + [footer]
36
+ STDERR.puts table.render(
37
+ :unicode,
38
+ alignment: :center,
39
+ padding: 1,
40
+ multiline: true,
41
+ )
42
+ end
43
+
44
+ desc 'copy <service> <src_env> <dest_env>',
45
+ 'Copies secrets for <service> from <src_env> to <dest_env>'
46
+ def copy(service, src_env, dest_env)
47
+ PayCLI::StopYubicoAuthenticator.stop_yubico_authenticator!
48
+ PayCLI::Secrets.secrets_in_envs_for_service(
49
+ [src_env],
50
+ service
51
+ )[src_env].each do |secret_name|
52
+ secret_value = PayCLI::Secrets::fetch_secret_for_service_from_env(
53
+ src_env, service, secret_name
54
+ )
55
+
56
+ old_value = PayCLI::Secrets::fetch_single_secret_from_env_for_service!(
57
+ dest_env,
58
+ service,
59
+ secret_name
60
+ )
61
+
62
+ if old_value == secret_value
63
+ STDERR.puts "✅ Copying #{secret_name} into #{dest_env} for #{service} would apply no change"
64
+ next
65
+ end
66
+
67
+ STDERR.puts PayCLI::Secrets.diff_table(
68
+ old_value,
69
+ secret_value
70
+ )
71
+
72
+ STDERR.puts "❓ Update value of #{secret_name} in #{dest_env} for #{service}?"
73
+ if (STDERR.print " Type 'yes' exactly > "; STDIN.gets.chomp == 'yes')
74
+
75
+ STDERR.puts "✅ Updated #{secret_name} in #{dest_env}"
76
+ PayCLI::Secrets::write_secret_for_service_in_env!(
77
+ dest_env,
78
+ service,
79
+ secret_name,
80
+ secret_value
81
+ )
82
+ end
83
+ end
84
+ end
85
+
86
+ desc 'provision <service> <env>',
87
+ 'Provisions secrets from config for <service> in <env>'
88
+ def provision(service, env)
89
+ PayCLI::StopYubicoAuthenticator.stop_yubico_authenticator!
90
+ PayCLI::Secrets.secrets_for_service(service).each do |secret_name|
91
+ old_value = PayCLI::Secrets::fetch_single_secret_from_env_for_service!(
92
+ env,
93
+ service,
94
+ secret_name
95
+ )
96
+ new_value = PayCLI::Secrets::fetch!(env, service, secret_name)
97
+
98
+ if old_value == new_value
99
+ STDERR.puts "✅ Provisioning #{secret_name} in #{env} for #{service} would apply no change"
100
+ next
101
+ end
102
+
103
+ STDERR.puts "❓ Update value of #{secret_name} in #{env} for #{service}?"
104
+ STDERR.puts PayCLI::Secrets.diff_table(
105
+ old_value,
106
+ new_value
107
+ )
108
+ if (STDERR.print " Type 'yes' exactly > "; STDIN.gets.chomp == 'yes')
109
+ PayCLI::Secrets.write_secret_for_service_in_env!(env,service,secret_name, new_value)
110
+ STDERR.puts "✅ Updated #{secret_name} for #{service} in #{env}"
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,111 @@
1
+ require 'aws-sdk-ec2'
2
+ require 'aws-sdk-autoscaling'
3
+ require 'aws-sdk-elasticloadbalancing'
4
+
5
+ module PayCLI::Commands::Ssm
6
+
7
+ def self.usage!
8
+ STDERR.puts <<~USAGE
9
+ pay ssm ...
10
+
11
+ - `pay ssm ci-5 build` - connect to build (jenkins) in ci-5
12
+ - `pay ssm test-12 i-123456` - connect to instance i-123456 in test-12
13
+ - `pay ssm staging-2 asg toolbox` - connect to a box in the toolbox asg
14
+
15
+ USAGE
16
+ exit
17
+ end
18
+
19
+ def self.start!
20
+ args = ARGV[1..-1] || []
21
+ usage! if args.empty?
22
+
23
+ PayCLI::StopYubicoAuthenticator.stop_yubico_authenticator!
24
+
25
+ env, *args = args
26
+ usage! if args.length < 1
27
+ PayCLI::Environment.setup! env
28
+
29
+ resource, resource_name, *args = args
30
+ server_roles = ['build', 'deploy', 'admin', 'bastion']
31
+
32
+ instance_id = case
33
+ when resource.match(/^i-/)
34
+ resource
35
+ when server_roles.include?(resource)
36
+ get_instance_id env, resource
37
+ when resource == 'asg'
38
+ get_asg_instance_id env, resource_name
39
+ when resource == 'app'
40
+ warn <<~WARN
41
+
42
+ `pay ssm app ...` no longer works since the move to fargate.
43
+
44
+ Please see usage below for other examples
45
+
46
+ WARN
47
+ usage!
48
+ else
49
+ abort "Unknown resource type #{resource}"
50
+ end
51
+
52
+ warn <<~WARN
53
+ \e[33m
54
+ ⚠️ WARNING: When using SSM, any and all activity you perform may be getting logged for security auditing purposes (think PCI).
55
+ Avoid sending or accessing \e[4manything\e[24m that could cause a security breach, such as:
56
+
57
+ • Secret API Keys or Tokens
58
+ • Credentials or Passwords
59
+ • Cardholder Data or Personally-Identifiable Information (PII)
60
+ • Anything else that may be protected by GDPR or PCI-DSS
61
+ • Anything classified as GSC 'Secret' or above
62
+
63
+ If you have a problem with this or aren't sure, use Ctrl-C \e[4mright now\e[24m and discontinue your SSM session.
64
+ \e[0m
65
+ WARN
66
+ sleep(5)
67
+ start_ssm_session(instance_id, env)
68
+ end
69
+
70
+ def self.get_instance_id(env, resource)
71
+ ec2_client = Aws::EC2::Client.new
72
+ instances = ec2_client.describe_instances({
73
+ filters: [
74
+ {name: 'tag:Environment', values: [env]},
75
+ {name: 'tag:Role', values: [resource == 'admin' ? 'bastion' : resource]}
76
+ ]
77
+ }).reservations.first.instances
78
+
79
+ abort "No healthy admin instances found" if instances.empty?
80
+
81
+ instances.first.instance_id
82
+ end
83
+
84
+ def self.get_asg_instance_id(env, service)
85
+ STDERR.puts "Getting instance_id from asg #{service} in #{env}"
86
+
87
+ asg_name = PayCLI::Naming.asg_name(env, service)
88
+ asg_client = Aws::AutoScaling::Client.new
89
+
90
+ asgs = asg_client.describe_auto_scaling_groups({
91
+ auto_scaling_group_names: [asg_name],
92
+ }).auto_scaling_groups
93
+ abort "No ASGs found for #{service} -> #{asg_name}" if asgs.empty?
94
+
95
+ instances = asgs.first.instances
96
+ abort "No instances found for #{service} -> #{asg_name}" if instances.empty?
97
+
98
+ instances.first.instance_id
99
+ end
100
+
101
+ def self.start_ssm_session(instance_id, env)
102
+ STDERR.puts "SSM to instance #{instance_id} in #{env}"
103
+
104
+ pid = spawn(
105
+ "aws ssm start-session --target \"#{instance_id}\"",
106
+ in: STDIN, out: STDOUT, err: STDERR
107
+ )
108
+ Process.wait pid
109
+ exit $CHILD_STATUS.exitstatus
110
+ end
111
+ end