@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.
- package/package.json +1 -1
- package/resources/legacy-ruby-cli/.rspec +1 -0
- package/resources/legacy-ruby-cli/.rubocop.yml +26 -0
- package/resources/legacy-ruby-cli/.ruby-version +1 -0
- package/resources/legacy-ruby-cli/Gemfile +24 -0
- package/resources/legacy-ruby-cli/Gemfile.lock +1431 -0
- package/resources/legacy-ruby-cli/README.md +142 -0
- package/resources/legacy-ruby-cli/bin/pay +31 -0
- package/resources/legacy-ruby-cli/config/generate-secrets.yml +9 -0
- package/resources/legacy-ruby-cli/config/secrets.yml +581 -0
- package/resources/legacy-ruby-cli/config/service_secrets.yml +174 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/aws/document.rb +23 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/aws/services.rb +47 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/aws/tokens.rb +161 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/aws.rb +51 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/browse.rb +31 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/doctor.rb +154 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/app_client.rb +216 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/config.rb +138 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/config.yaml +192 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/docker.rb +36 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/docker-compose.erb +270 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/end-to-end.erb +30 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/localstack/init-aws.sh +70 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/naxsi/readme.md +1 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/postgres/docker-entrypoint-initdb.d/make_payments_databases.sql +26 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/adminusers.env +49 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/cardid.env +2 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/connector.env +70 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/demo-service.env +10 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/frontend.env +12 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/java_app.env +1 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ledger.env +7 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/products-ui.env +14 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/products.env +25 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/publicapi.env +13 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/publicauth.env +13 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/selfservice.env +21 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/certs/frontend-proxy.crt +18 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/certs/products-ui-proxy.crt +20 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/certs/selfservice-proxy.crt +20 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/certs/stubs-proxy.crt +18 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/keys/frontend-proxy.key +28 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/keys/products-ui-proxy.key +28 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/keys/selfservice-proxy.key +28 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/keys/stubs-proxy.key +28 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/ssl/make-selfsigned.sh +2 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/stubs.env +12 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/toolbox.env +5 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/files/services/webhooks.env +9 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local/image_extractor.rb +20 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/local.rb +430 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/schema.rb +36 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/secrets.rb +114 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/ssm.rb +111 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/tf.rb +90 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/commands/tunnel/services.yml +49 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/config.rb +27 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/ec2.rb +38 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/entry_point.rb +52 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/environment.rb +25 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/logger.rb +3 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/logs.rb +248 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/naming.rb +44 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/secrets.rb +276 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/stop_yubico_authenticator.rb +10 -0
- package/resources/legacy-ruby-cli/lib/pay_cli/ykman_oath_credential_config.rb +70 -0
- package/resources/legacy-ruby-cli/lib/zeitwerk_setup.rb +5 -0
- package/resources/legacy-ruby-cli/package-lock.json +6 -0
- package/resources/legacy-ruby-cli/rds_access/connect.sh +149 -0
- package/resources/legacy-ruby-cli/spec/.rubocop.yml +2 -0
- package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.complex +34 -0
- package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.complex_differing_froms +33 -0
- package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.no_from +3 -0
- package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.simple +5 -0
- package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.simple_no_tag +5 -0
- package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.with_sha +5 -0
- package/resources/legacy-ruby-cli/spec/fixtures/dockerfile_examples/Dockerfile.with_sha_no_tag +5 -0
- package/resources/legacy-ruby-cli/spec/lib/pay_cli/commands/local/image_extractor_spec.rb +55 -0
- package/resources/legacy-ruby-cli/spec/naming_spec.rb +83 -0
- package/resources/legacy-ruby-cli/spec/spec_helper.rb +106 -0
- package/resources/legacy-ruby-cli/vulnerability_scan/.nvmrc +1 -0
- package/resources/legacy-ruby-cli/vulnerability_scan/generate_vulnerability_report.js +91 -0
- package/resources/legacy-ruby-cli/vulnerability_scan/reports/.gitkeep +0 -0
- package/resources/legacy-ruby-cli/vulnerability_scan/scan.sh +57 -0
- package/src/commands/browse.js +2 -2
- package/src/commands/legacy.js +5 -2
- package/src/core/constants.js +7 -10
- 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
|