55# (https://github.com/cbeer/capistrano-one_time_key). Generates a fresh, passphrase-less
66# keypair, adds the public key to each target host's ~/.ssh/authorized_keys (relying on
77# whatever trusted channel -- e.g. Kerberos/ksu -- already lets that connection succeed),
8- # runs `bin/kamal deploy ` using that key, then always removes it from authorized_keys.
8+ # runs `bin/kamal` using that key, then always removes it from authorized_keys.
99#
10- # Usage: bin/deploy DESTINATION [kamal deploy args...]
10+ # The key is both configured via config/deploy.<destination>.yml's `ssh.keys` (read from
11+ # KAMAL_SSH_KEY_PATH) and loaded into a scratch ssh-agent: Kamal's remote-builder port
12+ # forwarding (lib/kamal/cli/build.rb#remote_builder_ssh_options) builds its own SSH options
13+ # from the `builder.remote` URI and doesn't include configured keys, so it only succeeds
14+ # via agent-based auth.
15+ #
16+ # Usage: bin/kamal-otk DESTINATION [kamal args...]
1117
1218require 'bundler/setup'
1319require 'kamal'
@@ -29,6 +35,21 @@ def ssh_exec!(user, host, command)
2935 system ( 'ssh' , "#{ user } @#{ host } " , command , exception : true )
3036end
3137
38+ # Same mechanism as dlss-capistrano's controlmaster:setup/:start tasks:
39+ # https://github.com/sul-dlss/dlss-capistrano/blob/main/lib/dlss/capistrano/tasks/control_master.rake
40+ def ensure_controlmaster!
41+ return unless ENV . fetch ( 'USE_CAPISTRANO_CONTROLMASTER' , false ) == 'true'
42+
43+ controlmaster_host = ENV . fetch ( 'CONTROLMASTER_HOST' , 'dlss-jump' )
44+ controlmaster_socket = ENV . fetch ( 'CONTROLMASTER_SOCKET' , '~/.ssh/%r@%h:%p' )
45+
46+ return if system ( 'ssh' , '-O' , 'check' , '-S' , controlmaster_socket , controlmaster_host )
47+
48+ system ( 'ssh' , '-f' , '-N' , '-S' , controlmaster_socket , controlmaster_host , exception : true )
49+ end
50+
51+ ensure_controlmaster!
52+
3253Dir . mktmpdir do |dir |
3354 key_path = File . join ( dir , 'deploy_otk' )
3455 comment = "kamal-otk-#{ SecureRandom . hex ( 6 ) } "
@@ -42,10 +63,19 @@ Dir.mktmpdir do |dir|
4263
4364 hosts . each { |host | ssh_exec! ( user , host , add_key_command ) }
4465
66+ agent_pid = nil
4567 begin
68+ `ssh-agent -s` . each_line do |line |
69+ ENV [ Regexp . last_match ( 1 ) ] = Regexp . last_match ( 2 ) if line =~ /^(SSH_AUTH_SOCK|SSH_AGENT_PID)=([^;]+);/
70+ end
71+ agent_pid = ENV [ 'SSH_AGENT_PID' ]
72+ system ( 'ssh-add' , key_path , exception : true )
73+
4674 ENV [ 'KAMAL_SSH_KEY_PATH' ] = key_path
4775 system ( 'bin/kamal' , *ARGV , '-d' , destination , exception : true )
4876 ensure
77+ system ( 'ssh-agent' , '-k' ) if agent_pid
78+
4979 # Best-effort cleanup: don't let a removal failure mask the deploy's own result.
5080 remove_key_command = "sed -i.bak -e '/#{ comment } $/d' ~/.ssh/authorized_keys && rm -f ~/.ssh/authorized_keys.bak"
5181 hosts . each do |host |
0 commit comments