@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,276 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
require 'pp'
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
require 'aws-sdk-ssm'
|
|
6
|
+
require 'tty-table'
|
|
7
|
+
|
|
8
|
+
module PayCLI::Secrets
|
|
9
|
+
SECRETS_FILE_PATH = File.join(PayCLI::Config::CONFIG_PATH, 'secrets.yml')
|
|
10
|
+
GENERATE_FILE_PATH = File.join(PayCLI::Config::CONFIG_PATH, 'generate-secrets.yml')
|
|
11
|
+
|
|
12
|
+
SECRETS_PROVIDERS = Proc.new do
|
|
13
|
+
# We should produce a mapping of env -> secret_name -> provider
|
|
14
|
+
mapping = {}
|
|
15
|
+
|
|
16
|
+
generate_secrets_info = YAML.load_file(GENERATE_FILE_PATH)
|
|
17
|
+
|
|
18
|
+
YAML.load_file(SECRETS_FILE_PATH).each do |provider, environments|
|
|
19
|
+
environments.map do |env, secrets|
|
|
20
|
+
generate_secrets_info.each do |service, service_generate_names|
|
|
21
|
+
mapping[env] ||= {}
|
|
22
|
+
service_generate_names.each do | generate_info |
|
|
23
|
+
mapping[env][service] ||= {}
|
|
24
|
+
mapping[env][service][generate_info.first] = {
|
|
25
|
+
provider: 'generate',
|
|
26
|
+
detail: generate_info.last
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
secrets.each do |service, service_secrets|
|
|
31
|
+
mapping[env] ||= {}
|
|
32
|
+
|
|
33
|
+
service_secrets.each do |secret_info|
|
|
34
|
+
mapping[env][service] ||= {}
|
|
35
|
+
|
|
36
|
+
if secret_info.class == Array
|
|
37
|
+
secret_name = secret_info.first
|
|
38
|
+
|
|
39
|
+
mapping[env][service][secret_name] = {
|
|
40
|
+
provider: provider,
|
|
41
|
+
detail: secret_info.last
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
mapping[env][service][secret_info] = {
|
|
45
|
+
provider: provider,
|
|
46
|
+
}
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
mapping
|
|
54
|
+
end.call
|
|
55
|
+
|
|
56
|
+
def self.fetch!(env, service, key, query_ssm=false)
|
|
57
|
+
if query_ssm
|
|
58
|
+
STDERR.puts "Using ssm to lookup secret"
|
|
59
|
+
ssm_val = self.fetch_single_secret_from_env_for_service!(env,service, key)
|
|
60
|
+
unless ssm_val
|
|
61
|
+
STDERR.puts "Could not find #{key} for #{service} in #{env} ssm"
|
|
62
|
+
exit 1
|
|
63
|
+
end
|
|
64
|
+
return ssm_val
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
unless PayCLI::Secrets::SECRETS_PROVIDERS.key? env
|
|
69
|
+
STDERR.puts "Could not find #{env} in secrets mapping #{SECRETS_FILE_PATH}"
|
|
70
|
+
exit 1
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
unless PayCLI::Secrets::SECRETS_PROVIDERS[env].key? service
|
|
74
|
+
STDERR.puts "Could not find #{service} in secrets mapping #{SECRETS_FILE_PATH} for #{env}"
|
|
75
|
+
exit 1
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
unless PayCLI::Secrets::SECRETS_PROVIDERS[env][service].key? key
|
|
79
|
+
STDERR.puts "Could not find #{key} in #{env} secrets mapping for #{service}"
|
|
80
|
+
exit 1
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
provider = PayCLI::Secrets::SECRETS_PROVIDERS[env][service][key][:provider]
|
|
84
|
+
STDERR.puts "Found provider #{provider} for key #{key} in #{env} for #{service}"
|
|
85
|
+
|
|
86
|
+
case provider
|
|
87
|
+
when 'local'
|
|
88
|
+
fetch_local! env, service, key
|
|
89
|
+
when 'generate'
|
|
90
|
+
generate! PayCLI::Secrets::SECRETS_PROVIDERS[env][service][key][:detail]
|
|
91
|
+
when 'value'
|
|
92
|
+
fetch_value! env, service, key
|
|
93
|
+
when 'pass'
|
|
94
|
+
fetch_pass! '', PayCLI::Secrets::SECRETS_PROVIDERS[env][service][key][:detail]
|
|
95
|
+
when /^pay-.*pass$/
|
|
96
|
+
pass_path = PayCLI::Secrets::SECRETS_PROVIDERS[env][service][key][:detail]
|
|
97
|
+
fetch_pay_pass! env, service, key, provider, pass_path
|
|
98
|
+
else
|
|
99
|
+
STDERR.puts "Provider #{provider} not supported"
|
|
100
|
+
exit 1
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.generate!(detail)
|
|
105
|
+
puts "Generating #{detail}"
|
|
106
|
+
detail_array = detail.split(":")
|
|
107
|
+
|
|
108
|
+
abort "Incorrect format of generate detail #{detail}" unless detail_array.length == 2
|
|
109
|
+
method, length = detail_array
|
|
110
|
+
case method
|
|
111
|
+
when 'random'
|
|
112
|
+
SecureRandom.urlsafe_base64(length.to_i)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def self.fetch_pass!(path, pass_path)
|
|
117
|
+
env = {}
|
|
118
|
+
env['PASSWORD_STORE_DIR'] = path if path
|
|
119
|
+
|
|
120
|
+
stdin, stdout, wait_thr = Open3.popen2(env, "pass #{pass_path}")
|
|
121
|
+
password = stdout.readline.chomp
|
|
122
|
+
stdin.close
|
|
123
|
+
stdout.close
|
|
124
|
+
pass_status = wait_thr.value
|
|
125
|
+
|
|
126
|
+
abort "Pass failed, error above" unless pass_status.success?
|
|
127
|
+
|
|
128
|
+
password
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def self.fetch_pay_pass!(env, service, key, provider, pass_path)
|
|
132
|
+
path = File.expand_path(File.join(
|
|
133
|
+
PayCLI::Config::PROJECT_PATH, '..', provider
|
|
134
|
+
))
|
|
135
|
+
|
|
136
|
+
abort "Path #{path} doesn't exist" unless File.exist? path
|
|
137
|
+
|
|
138
|
+
STDERR.puts "Pulling secret #{key} from #{path} for #{service}"
|
|
139
|
+
fetch_pass! path, pass_path
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.fetch_local!(env, service, key)
|
|
143
|
+
environments = YAML.load_file(PayCLI::Config::LOCAL_SECRETS_PATH)
|
|
144
|
+
|
|
145
|
+
value = environments.fetch(env, nil)&.fetch(service, nil)&.fetch(key, nil)
|
|
146
|
+
if value.nil?
|
|
147
|
+
STDERR.puts "Could not find #{key} in #{env} in local provider for #{service}"
|
|
148
|
+
exit 1
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
value
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def self.fetch_value!(env, service, key)
|
|
155
|
+
PayCLI::Secrets::SECRETS_PROVIDERS
|
|
156
|
+
.fetch(env)
|
|
157
|
+
.fetch(service)
|
|
158
|
+
.fetch(key)
|
|
159
|
+
.fetch(:detail)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def self.secrets_for_service(service)
|
|
163
|
+
secrets_definition_path = File.join(
|
|
164
|
+
PayCLI::Config::CONFIG_PATH, 'service_secrets.yml'
|
|
165
|
+
)
|
|
166
|
+
secrets_definitions = YAML.load_file(secrets_definition_path)
|
|
167
|
+
|
|
168
|
+
unless secrets_definitions.key? service
|
|
169
|
+
abort "Could not find secrets definition for #{service}"
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
secrets_definitions.fetch service
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def self.fetch_single_secret_from_env_for_service!(env, service, name)
|
|
176
|
+
PayCLI::Environment.setup! env
|
|
177
|
+
ssm = Aws::SSM::Client.new
|
|
178
|
+
|
|
179
|
+
begin
|
|
180
|
+
secret_value = ssm.get_parameter({
|
|
181
|
+
name: secret_name(env, service, name),
|
|
182
|
+
with_decryption: true
|
|
183
|
+
}).parameter.value
|
|
184
|
+
rescue Aws::SSM::Errors::ParameterNotFound
|
|
185
|
+
secret_value = nil
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
secret_value
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def self.secrets_in_envs_for_service(envs, service)
|
|
192
|
+
accounts = envs.map { |e| e.split('-').first }.uniq
|
|
193
|
+
all_parameters = []
|
|
194
|
+
|
|
195
|
+
accounts.each do |acc|
|
|
196
|
+
PayCLI::Environment.setup! acc
|
|
197
|
+
ssm = Aws::SSM::Client.new
|
|
198
|
+
|
|
199
|
+
next_token = nil
|
|
200
|
+
loop do
|
|
201
|
+
STDERR.puts "Making request to AWS SSM for #{acc}"
|
|
202
|
+
|
|
203
|
+
opts = {}
|
|
204
|
+
opts[:next_token] = next_token unless next_token.nil?
|
|
205
|
+
|
|
206
|
+
response = ssm.describe_parameters(opts)
|
|
207
|
+
|
|
208
|
+
next_token = response.next_token
|
|
209
|
+
all_parameters += response.parameters
|
|
210
|
+
|
|
211
|
+
break if next_token.nil?
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
secrets_per_env = envs.map do |env|
|
|
216
|
+
secrets = all_parameters
|
|
217
|
+
.select { |p| p.name =~ /^#{env}_#{service}\./ }
|
|
218
|
+
.map { |p| p.name }
|
|
219
|
+
.map { |p| p.sub(/^#{env}_#{service}\./, '') }
|
|
220
|
+
.map { |p| p.upcase }
|
|
221
|
+
[env, secrets]
|
|
222
|
+
end.to_h
|
|
223
|
+
|
|
224
|
+
secrets_per_env
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def self.secret_name(env, service, name)
|
|
228
|
+
return _concourse_secret_name(service, name) if %w[cd-pay-dev cd-pay-deploy cd-main].include? service
|
|
229
|
+
|
|
230
|
+
"#{env}_#{service}.#{name}".downcase
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def self._concourse_secret_name(service, name)
|
|
234
|
+
# Service name is `cd-pay-dev` but the ssm name is only `pay-dev` so remove the cd-
|
|
235
|
+
service = service[3..]
|
|
236
|
+
|
|
237
|
+
"/pay-cd/concourse/pipelines/#{service.downcase}/#{name}"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def self.write_secret_for_service_in_env!(env, service, name, value)
|
|
241
|
+
PayCLI::Environment.setup! env
|
|
242
|
+
ssm = Aws::SSM::Client.new
|
|
243
|
+
|
|
244
|
+
STDERR.puts "Updating value of #{name} in #{env} with #{value}"
|
|
245
|
+
ssm.put_parameter({
|
|
246
|
+
name: secret_name(env, service, name),
|
|
247
|
+
type: 'SecureString',
|
|
248
|
+
value: value,
|
|
249
|
+
overwrite: true,
|
|
250
|
+
})
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def self.fetch_secret_for_service_from_env(env, service, name)
|
|
254
|
+
PayCLI::Environment.setup! env
|
|
255
|
+
ssm = Aws::SSM::Client.new
|
|
256
|
+
|
|
257
|
+
STDERR.puts "Fetching secret #{name} from #{env} / #{service}"
|
|
258
|
+
|
|
259
|
+
ssm.get_parameter({
|
|
260
|
+
name: secret_name(env, service, name),
|
|
261
|
+
with_decryption: true
|
|
262
|
+
}).parameter.value
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def self.diff_table(old_value, new_value)
|
|
266
|
+
TTY::Table.new([
|
|
267
|
+
['Old', '=>', 'New'],
|
|
268
|
+
[old_value, '', new_value]
|
|
269
|
+
]).render(
|
|
270
|
+
:unicode,
|
|
271
|
+
alignment: :center,
|
|
272
|
+
padding: 1,
|
|
273
|
+
multiline: true,
|
|
274
|
+
)
|
|
275
|
+
end
|
|
276
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class PayCLI::StopYubicoAuthenticator
|
|
2
|
+
def self.stop_yubico_authenticator!
|
|
3
|
+
if system("killall -0 yubioath-desktop")
|
|
4
|
+
puts "❓ Yubico Authenticator appears to be running. It can interfere with establishing SSH connections."
|
|
5
|
+
if (STDERR.print " To stop Yubico Authenticator type 'yes' exactly > "; STDIN.gets.chomp == 'yes')
|
|
6
|
+
system("killall yubioath-desktop")
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require "tty-prompt"
|
|
2
|
+
require 'open3'
|
|
3
|
+
|
|
4
|
+
class PayCLI::YkmanOathCredentialConfig
|
|
5
|
+
attr_reader :config_file_path, :logger
|
|
6
|
+
|
|
7
|
+
def initialize(config_file_path: nil, logger: PayCLI::Logger)
|
|
8
|
+
@config_file_path = config_file_path || default_config_file_path
|
|
9
|
+
@logger = logger
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def lookup(account)
|
|
13
|
+
load!
|
|
14
|
+
if @config[:ykman_defaults].has_key?(account)
|
|
15
|
+
yk_name = @config[:ykman_defaults][account]
|
|
16
|
+
logger.info "Oath code name '#{yk_name}' is selected for '#{account}' in '#{config_file_path}'"
|
|
17
|
+
yk_name
|
|
18
|
+
else
|
|
19
|
+
prompt(account)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def save(account, yk_name)
|
|
24
|
+
load!
|
|
25
|
+
@config[:ykman_defaults][account] = yk_name
|
|
26
|
+
logger.info "Saved your preferred OATH code name for '#{account}' to '#{config_file_path}'"
|
|
27
|
+
save!
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def prompt(account)
|
|
31
|
+
output, err, status = Open3.capture3(%{ykman oath accounts list})
|
|
32
|
+
|
|
33
|
+
yk_names = output.lines.map {|line| line.gsub(/ +([0-9]+|\[Touch Credential\])$/,"").chomp }
|
|
34
|
+
|
|
35
|
+
prompt = TTY::Prompt.new
|
|
36
|
+
yk_name = prompt.select("Which code from your yubikey would you like to use for the '#{account}' account?", yk_names)
|
|
37
|
+
|
|
38
|
+
if prompt.yes?("Remember this choice?")
|
|
39
|
+
save(account, yk_name)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
yk_name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
def default_config_file_path
|
|
47
|
+
Pathname.new(Dir.home) + ".govuk_pay_cli_config.yml"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def load!
|
|
51
|
+
@config ||= YAML.load(File.read(config_file_path))
|
|
52
|
+
rescue Errno::ENOENT
|
|
53
|
+
@config ||= {ykman_defaults: {}}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def save!
|
|
57
|
+
File.write(config_file_path, yaml_preamble + YAML.dump(@config))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def yaml_preamble
|
|
61
|
+
<<-YAML_PREAMBLE
|
|
62
|
+
# This file is automatically generated by the GOV.UK Pay CLI
|
|
63
|
+
# You can edit settings here if you want
|
|
64
|
+
#
|
|
65
|
+
# For more info see https://github.com/alphagov/pay-infra/tree/master/cli
|
|
66
|
+
#
|
|
67
|
+
YAML_PREAMBLE
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Temporary script for tunneling to RDS during development of the bastion solution
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
function usage() {
|
|
8
|
+
echo "Usage: $0 <environment> <app>"
|
|
9
|
+
echo
|
|
10
|
+
echo "Examples:"
|
|
11
|
+
echo " $0 test-12 adminusers"
|
|
12
|
+
echo " $0 test-perf-1 ledger"
|
|
13
|
+
exit 1
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function is_help_arg() {
|
|
17
|
+
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
|
|
18
|
+
return 0
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
return 1
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if [ "$#" -ne 2 ]; then
|
|
25
|
+
usage
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
ENVIRONMENT="$1"
|
|
29
|
+
APP="$2"
|
|
30
|
+
if [[ -z $ENVIRONMENT ]] || [[ -z $APP ]] || is_help_arg "$ENVIRONMENT" || is_help_arg "$APP"; then
|
|
31
|
+
usage
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
for x in aws yq jq ssh-keygen aws-vault ssh
|
|
35
|
+
do
|
|
36
|
+
if ! command -v $x &> /dev/null
|
|
37
|
+
then
|
|
38
|
+
echo "$x is not installed, exiting."
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
ACCOUNT="${ENVIRONMENT%%-*}"
|
|
44
|
+
KEY_LOCATION="${TMPDIR}rds_tunnel_temp_key"
|
|
45
|
+
|
|
46
|
+
function cleanup() {
|
|
47
|
+
echo "cleaning up..."
|
|
48
|
+
if rm "${KEY_LOCATION}"*; then
|
|
49
|
+
echo "Removed temp key pair";
|
|
50
|
+
else
|
|
51
|
+
echo "Failed to remove key pair from ${KEY_LOCATION}"
|
|
52
|
+
fi
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
trap cleanup EXIT
|
|
56
|
+
|
|
57
|
+
echo "Finding bastion host"
|
|
58
|
+
read -r bastion_instance_id availability_zone <<< "$(aws-vault exec "$ACCOUNT" -- \
|
|
59
|
+
aws autoscaling describe-auto-scaling-groups | \
|
|
60
|
+
jq --arg env "$ENVIRONMENT" '.AutoScalingGroups[] | select(.AutoScalingGroupName == $env + "-bastion").Instances[0] | "\(.InstanceId) \(.AvailabilityZone)"' -r)"
|
|
61
|
+
|
|
62
|
+
if [[ -z $bastion_instance_id ]] || [[ -z $availability_zone ]]; then
|
|
63
|
+
echo "Failed to find bastion instance"
|
|
64
|
+
exit 1
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
echo "Found bastion $bastion_instance_id"
|
|
68
|
+
|
|
69
|
+
echo "Getting RDS endpoint"
|
|
70
|
+
rds_endpoint="$(aws-vault exec "$ACCOUNT" -- \
|
|
71
|
+
aws rds describe-db-instances |\
|
|
72
|
+
jq --arg rds_instance "${ENVIRONMENT}-${APP}" '.DBInstances[] | select(.DBInstanceIdentifier | startswith($rds_instance)).Endpoint.Address' -r)"
|
|
73
|
+
|
|
74
|
+
if [[ -z $rds_endpoint ]]; then
|
|
75
|
+
echo "Failed to find RDS endpoint"
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
echo "RDS endpoint is: ${rds_endpoint}"
|
|
79
|
+
|
|
80
|
+
echo "Getting RDS instance engine version"
|
|
81
|
+
engine_version=$(aws-vault exec "$ACCOUNT" -- \
|
|
82
|
+
aws rds describe-db-instances | \
|
|
83
|
+
jq -r --arg rds_instance "${ENVIRONMENT}-${APP}" '.DBInstances[] | select(.DBInstanceIdentifier | startswith($rds_instance)).EngineVersion')
|
|
84
|
+
echo "RDS engine_version is ${engine_version}"
|
|
85
|
+
|
|
86
|
+
echo "Generating ssh key pair, saving to ${KEY_LOCATION}"
|
|
87
|
+
if ! ssh-keygen -q -t rsa -b 4096 -f "$KEY_LOCATION" -N ''; then
|
|
88
|
+
echo "Failed to generate ssh key pair"
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo "Uploading public key to bastion"
|
|
93
|
+
if ! aws-vault exec "$ACCOUNT" -- \
|
|
94
|
+
aws ec2-instance-connect send-ssh-public-key \
|
|
95
|
+
--instance-id "$bastion_instance_id" \
|
|
96
|
+
--availability-zone "$availability_zone" \
|
|
97
|
+
--instance-os-user ec2-user \
|
|
98
|
+
--ssh-public-key "file://${KEY_LOCATION}.pub"; then
|
|
99
|
+
|
|
100
|
+
echo "Failed to upload public key to bastion"
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
yellow="\033[0;33m"
|
|
105
|
+
reset="\033[0m"
|
|
106
|
+
ul="\033[4m"
|
|
107
|
+
ulstop="\033[24m"
|
|
108
|
+
|
|
109
|
+
echo -e "${yellow} ${reset}"
|
|
110
|
+
echo -e "${yellow} ⚠️ WARNING: When using SSM, any and all activity you perform may be getting logged for security auditing purposes (think PCI).${reset}"
|
|
111
|
+
echo -e "${yellow} Avoid sending or accessing ${ul}anything${ulstop} that could cause a security breach, such as:${reset}"
|
|
112
|
+
echo -e "${yellow} ${reset}"
|
|
113
|
+
echo -e "${yellow} • Secret API Keys or Tokens${reset}"
|
|
114
|
+
echo -e "${yellow} • Credentials or Passwords${reset}"
|
|
115
|
+
echo -e "${yellow} • Cardholder Data or Personally-Identifiable Information (PII)${reset}"
|
|
116
|
+
echo -e "${yellow} • Anything else that may be protected by GDPR or PCI-DSS${reset}"
|
|
117
|
+
echo -e "${yellow} • Anything classified as GSC 'Secret' or above${reset}"
|
|
118
|
+
echo -e "${yellow} ${reset}"
|
|
119
|
+
echo -e "${yellow} If you have a problem with this or aren\'t sure, use Ctrl-C ${ul}right now${ulstop} and discontinue your SSM session.${reset}"
|
|
120
|
+
echo -e "${yellow} ${reset}"
|
|
121
|
+
|
|
122
|
+
echo "Opening tunnel to rds"
|
|
123
|
+
if ! aws-vault exec "$ACCOUNT" -- \
|
|
124
|
+
ssh -i "$KEY_LOCATION" -N -f -M -S temp-ssh.sock \
|
|
125
|
+
-L 65432:"$rds_endpoint":5432 ec2-user@"$bastion_instance_id" \
|
|
126
|
+
-o "UserKnownHostsFile=/dev/null" \
|
|
127
|
+
-o "StrictHostKeyChecking=no" \
|
|
128
|
+
-o IdentitiesOnly=yes \
|
|
129
|
+
-o ProxyCommand="aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters portNumber=%p"; then
|
|
130
|
+
echo "Failed to open tunnel to RDS"
|
|
131
|
+
exit 1
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
SOURCE_DIRECTORY=$(dirname "$BASH_SOURCE")
|
|
135
|
+
DB_USER=$(yq eval ".value.$ENVIRONMENT.$APP.DB_USER" < "${SOURCE_DIRECTORY}/../config/secrets.yml")
|
|
136
|
+
|
|
137
|
+
echo -e "Connected tunnel to $APP RDS database in $ENVIRONMENT on port 65432\n"
|
|
138
|
+
echo "Copy DB credentials to clipboard (in another window) using pay-low-pass:"
|
|
139
|
+
echo -e " pay-low-pass aws/rds/application_users/$ACCOUNT/$DB_USER | pbcopy\n"
|
|
140
|
+
echo "Alternatively, fetch credentials from pay secrets:"
|
|
141
|
+
echo -e " pay secrets fetch $ENVIRONMENT $APP DB_PASSWORD | pbcopy\n"
|
|
142
|
+
echo "Open psql with:"
|
|
143
|
+
echo -e " psql -h localhost -p 65432 -U $DB_USER -d $APP\n"
|
|
144
|
+
echo "Alternatively connect using docker instead of needing psql installed locally and set the password automatically using pay-low-pass:"
|
|
145
|
+
echo -e " docker run --rm -ti postgres:${engine_version}-alpine psql --host docker.for.mac.localhost --port 65432 --user $DB_USER --dbname $APP\n"
|
|
146
|
+
echo "Or even more conveniently connect using a docker container and set the password automatically using pay-low-pass:"
|
|
147
|
+
echo -e " docker run -e \"PGPASSWORD=\$(pay-low-pass aws/rds/application_users/${ACCOUNT}/${DB_USER})\" --rm -ti postgres:${engine_version}-alpine psql --host docker.for.mac.localhost --port 65432 --user $DB_USER --dbname $APP\n"
|
|
148
|
+
read -rsn1 -p "Press any key to close session."; echo
|
|
149
|
+
ssh -O exit -S temp-ssh.sock '*'
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Preceeding comment
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
FROM node:18.18.0-alpine3.18@sha256:619ce27eb37c7c0476bd518085bf1ba892e2148fc1ab5dbaff2f20c56e50444d as builder
|
|
5
|
+
|
|
6
|
+
WORKDIR /app
|
|
7
|
+
COPY package.json .
|
|
8
|
+
COPY package-lock.json .
|
|
9
|
+
RUN npm ci --quiet
|
|
10
|
+
|
|
11
|
+
COPY . .
|
|
12
|
+
RUN npm run compile
|
|
13
|
+
|
|
14
|
+
FROM node:18.18.0-alpine3.18@sha256:619ce27eb37c7c0476bd518085bf1ba892e2148fc1ab5dbaff2f20c56e50444d as final
|
|
15
|
+
|
|
16
|
+
RUN ["apk", "--no-cache", "upgrade"]
|
|
17
|
+
|
|
18
|
+
RUN ["apk", "add", "--no-cache", "tini"]
|
|
19
|
+
|
|
20
|
+
WORKDIR /app
|
|
21
|
+
COPY . .
|
|
22
|
+
RUN rm -rf ./test
|
|
23
|
+
# Copy in compile assets and deps from build container
|
|
24
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
25
|
+
COPY --from=builder /app/govuk_modules ./govuk_modules
|
|
26
|
+
COPY --from=builder /app/public ./public
|
|
27
|
+
RUN npm prune --omit=dev
|
|
28
|
+
|
|
29
|
+
ENV PORT 9000
|
|
30
|
+
EXPOSE 9000
|
|
31
|
+
|
|
32
|
+
ENTRYPOINT ["tini", "--"]
|
|
33
|
+
|
|
34
|
+
CMD ["npm", "start"]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Preceeding comment
|
|
2
|
+
|
|
3
|
+
FROM node:18.18.0-alpine3.18@sha256:619ce27eb37c7c0476bd518085bf1ba892e2148fc1ab5dbaff2f20c56e50444d as builder
|
|
4
|
+
|
|
5
|
+
WORKDIR /app
|
|
6
|
+
COPY package.json .
|
|
7
|
+
COPY package-lock.json .
|
|
8
|
+
RUN npm ci --quiet
|
|
9
|
+
|
|
10
|
+
COPY . .
|
|
11
|
+
RUN npm run compile
|
|
12
|
+
|
|
13
|
+
FROM node:18.18.0-alpine3.18@sha256:619ce27eb37c7c0476bd518085bf1ba892e2148fc1ab5dbaff2f20c56e50444d as final
|
|
14
|
+
|
|
15
|
+
RUN ["apk", "--no-cache", "upgrade"]
|
|
16
|
+
|
|
17
|
+
RUN ["apk", "add", "--no-cache", "tini"]
|
|
18
|
+
|
|
19
|
+
WORKDIR /app
|
|
20
|
+
COPY . .
|
|
21
|
+
RUN rm -rf ./test
|
|
22
|
+
# Copy in compile assets and deps from build container
|
|
23
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
24
|
+
COPY --from=builder /app/govuk_modules ./govuk_modules
|
|
25
|
+
COPY --from=builder /app/public ./public
|
|
26
|
+
RUN npm prune --omit=dev
|
|
27
|
+
|
|
28
|
+
ENV PORT 9000
|
|
29
|
+
EXPOSE 9000
|
|
30
|
+
|
|
31
|
+
ENTRYPOINT ["tini", "--"]
|
|
32
|
+
|
|
33
|
+
CMD ["npm", "start"]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe PayCLI::Commands::Local::ImageExtractor do
|
|
4
|
+
describe '.parse_image_without_sha' do
|
|
5
|
+
it 'gives the correct image name and tag on a simple dockerfile' do
|
|
6
|
+
expect(
|
|
7
|
+
described_class.parse_image_without_sha(fixture_path('simple'))
|
|
8
|
+
).to eq('node:alpine-3.18')
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'gives the correct image name and tag on a simple dockerfile with no image tag' do
|
|
12
|
+
expect(
|
|
13
|
+
described_class.parse_image_without_sha(fixture_path('simple_no_tag'))
|
|
14
|
+
).to eq('node')
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'gives the correct image name and tag on a dockerfile with an image sha and tag' do
|
|
18
|
+
expect(
|
|
19
|
+
described_class.parse_image_without_sha(fixture_path('with_sha'))
|
|
20
|
+
).to eq('node:alpine-3.18')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'gives the correct image name and tag on a dockerfile with an image sha but no tag' do
|
|
24
|
+
expect(
|
|
25
|
+
described_class.parse_image_without_sha(fixture_path('with_sha_no_tag'))
|
|
26
|
+
).to eq('node')
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'gives the correct image name and tag on a complex dockerfile with multiple FROM lines' do
|
|
30
|
+
expect(
|
|
31
|
+
described_class.parse_image_without_sha(fixture_path('complex'))
|
|
32
|
+
).to eq('node:18.18.0-alpine3.18')
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'gives the first image name and tag on a complex dockerfile with multiple differing FROM lines' do
|
|
36
|
+
expect(
|
|
37
|
+
described_class.parse_image_without_sha(fixture_path('complex_differing_froms'))
|
|
38
|
+
).to eq('node:18.18.0-alpine3.18')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'raises a DockerfileNotFound error if the dockerfile does not exist' do
|
|
42
|
+
expect { described_class.parse_image_without_sha(fixture_path('DOES_NOT_EXIST')) }
|
|
43
|
+
.to raise_error(PayCLI::Commands::Local::ImageExtractor::DockerfileNotFound)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'raises an ImageNotFoundInDockerfile error if the file does not contain a FROM line' do
|
|
47
|
+
expect { described_class.parse_image_without_sha(fixture_path('no_from')) }
|
|
48
|
+
.to raise_error(PayCLI::Commands::Local::ImageExtractor::ImageNotFoundInDockerfile)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def fixture_path(dockerfile_name)
|
|
53
|
+
File.join(__dir__, '..', '..', '..', '..', 'fixtures', 'dockerfile_examples', "Dockerfile.#{dockerfile_name}")
|
|
54
|
+
end
|
|
55
|
+
end
|