Skip to content

Commit ab7ab32

Browse files
authored
Merge pull request #1620 from jclusso/add-ssh-key-data-secret-support
Add secret support for SSH `key_data`
2 parents 5425a54 + 8b8b722 commit ab7ab32

File tree

5 files changed

+62
-9
lines changed

5 files changed

+62
-9
lines changed

lib/kamal/configuration/docs/ssh.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,9 @@ ssh:
5858

5959
# Key data
6060
#
61-
# An array of strings, with each element of the array being
62-
# a raw private key in PEM format.
63-
key_data: [ "-----BEGIN OPENSSH PRIVATE KEY-----" ]
61+
# An array of strings, with each element of the array being a secret name.
62+
key_data:
63+
- SSH_PRIVATE_KEY
6464

6565
# Config
6666
#

lib/kamal/configuration/ssh.rb

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ class Kamal::Configuration::Ssh
33

44
include Kamal::Configuration::Validation
55

6-
attr_reader :ssh_config
6+
attr_reader :ssh_config, :secrets
77

88
def initialize(config:)
99
@ssh_config = config.raw_config.ssh || {}
10+
@secrets = config.secrets
1011
validate! ssh_config
1112
end
1213

@@ -35,7 +36,17 @@ def keys
3536
end
3637

3738
def key_data
38-
ssh_config["key_data"]
39+
key_data = ssh_config["key_data"]
40+
return unless key_data
41+
42+
key_data.map do |k|
43+
if secrets.key?(k)
44+
secrets[k]
45+
else
46+
warn "Inline key_data usage is deprecated and will be removed in Kamal 3. Please store your key_data in a secret."
47+
k
48+
end
49+
end
3950
end
4051

4152
def config

lib/kamal/secrets.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ def initialize(destination: nil, secrets_path:)
1010
end
1111

1212
def [](key)
13-
# Fetching secrets may ask the user for input, so ensure only one thread does that
14-
@mutex.synchronize do
15-
secrets.fetch(key)
16-
end
13+
synchronized_fetch(key)
1714
rescue KeyError
1815
if secrets_files.present?
1916
raise Kamal::ConfigurationError, "Secret '#{key}' not found in #{secrets_files.join(", ")}"
@@ -30,6 +27,12 @@ def secrets_files
3027
@secrets_files ||= secrets_filenames.select { |f| File.exist?(f) }
3128
end
3229

30+
def key?(key)
31+
synchronized_fetch(key).present?
32+
rescue KeyError
33+
false
34+
end
35+
3336
private
3437
def secrets
3538
@secrets ||= secrets_files.inject({}) do |secrets, secrets_file|
@@ -40,4 +43,11 @@ def secrets
4043
def secrets_filenames
4144
[ "#{@secrets_path}-common", "#{@secrets_path}#{(".#{@destination}" if @destination)}" ]
4245
end
46+
47+
def synchronized_fetch(key)
48+
# Fetching secrets may ask the user for input, so ensure only one thread does that
49+
@mutex.synchronize do
50+
secrets.fetch(key)
51+
end
52+
end
4353
end

test/configuration/ssh_test.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,23 @@ class ConfigurationSshTest < ActiveSupport::TestCase
4949
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!(ssh: { "proxy" => "[email protected]" }) })
5050
assert_equal "[email protected]", config.ssh.options[:proxy].jump_proxies
5151
end
52+
53+
test "ssh key_data with plain value array" do
54+
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!(ssh: { "key_data" => [ "-----BEGIN OPENSSH PRIVATE KEY-----" ] }) })
55+
assert_equal [ "-----BEGIN OPENSSH PRIVATE KEY-----" ], config.ssh.options[:key_data]
56+
end
57+
58+
test "ssh key_data with array containing one secret string" do
59+
with_test_secrets("secrets" => "SSH_PRIVATE_KEY=secret_ssh_key") do
60+
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!(ssh: { "key_data" => [ "SSH_PRIVATE_KEY" ] }) })
61+
assert_equal [ "secret_ssh_key" ], config.ssh.options[:key_data]
62+
end
63+
end
64+
65+
test "ssh key_data with array containing multiple secret strings" do
66+
with_test_secrets("secrets" => "SSH_PRIVATE_KEY=secret_ssh_key\nSECOND_KEY=second_secret_ssh_key") do
67+
config = Kamal::Configuration.new(@deploy.tap { |c| c.merge!(ssh: { "key_data" => [ "SSH_PRIVATE_KEY", "SECOND_KEY" ] }) })
68+
assert_equal [ "secret_ssh_key", "second_secret_ssh_key" ], config.ssh.options[:key_data]
69+
end
70+
end
5271
end

test/secrets_test.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ class SecretsTest < ActiveSupport::TestCase
77
end
88
end
99

10+
test "synchronized_fetch" do
11+
with_test_secrets("secrets" => "SECRET=ABC") do
12+
assert_equal "ABC", Kamal::Secrets.new(secrets_path: ".kamal/secrets").send(:synchronized_fetch, "SECRET")
13+
end
14+
end
15+
16+
test "key?" do
17+
with_test_secrets("secrets" => "SECRET1=ABC") do
18+
assert Kamal::Secrets.new(secrets_path: ".kamal/secrets").key?("SECRET1")
19+
assert_not Kamal::Secrets.new(secrets_path: ".kamal/secrets").key?("SECRET2")
20+
end
21+
end
22+
1023
test "command interpolation" do
1124
with_test_secrets("secrets" => "SECRET=$(echo ABC)") do
1225
assert_equal "ABC", Kamal::Secrets.new(secrets_path: ".kamal/secrets")["SECRET"]

0 commit comments

Comments
 (0)