@@ -8,12 +8,15 @@ import (
88 "net"
99 "os"
1010 "path/filepath"
11+ "strings"
1112
1213 uuid "github.com/satori/go.uuid"
1314 "golang.org/x/crypto/ssh"
1415 "golang.org/x/crypto/ssh/agent"
16+ "golang.org/x/crypto/ssh/terminal"
1517
1618 "github.com/werf/logboek"
19+ secret_common "github.com/werf/werf/cmd/werf/helm/secret/common"
1720 "github.com/werf/werf/pkg/util"
1821 "github.com/werf/werf/pkg/werf"
1922)
@@ -28,24 +31,93 @@ func setupProcessSSHAgent(sshAuthSock string) error {
2831 return os .Setenv ("SSH_AUTH_SOCK" , SSHAuthSock )
2932}
3033
31- func Init (ctx context.Context , keys []string ) error {
32- for _ , key := range keys {
33- if keyExists , err := util .FileExists (key ); ! keyExists {
34- return fmt .Errorf ("specified ssh key %s does not exist" , key )
35- } else if err != nil {
36- return fmt .Errorf ("specified ssh key %s does not exist: %v" , key , err )
34+ type sshKeyConfig struct {
35+ FilePath string
36+ Passphrase []byte
37+ }
38+
39+ type sshKey struct {
40+ Config sshKeyConfig
41+ PrivateKey interface {}
42+ }
43+
44+ func getSshKeyConfig (path string ) (sshKeyConfig , error ) {
45+ var filePath string
46+ var passphrase []byte
47+
48+ switch {
49+ case strings .HasPrefix (path , "file://" ):
50+ userinfoWithPath := strings .TrimPrefix (path , "file://" )
51+
52+ parts := strings .SplitN (userinfoWithPath , "@" , 2 )
53+ passphrase = []byte (parts [0 ])
54+ filePath = util .ExpandPath (parts [1 ])
55+
56+ default :
57+ filePath = util .ExpandPath (path )
58+ }
59+
60+ if keyExists , err := util .FileExists (filePath ); ! keyExists {
61+ return sshKeyConfig {}, fmt .Errorf ("specified ssh key does not exist" )
62+ } else if err != nil {
63+ return sshKeyConfig {}, fmt .Errorf ("specified ssh key does not exist: %w" , err )
64+ }
65+
66+ return sshKeyConfig {FilePath : filePath , Passphrase : passphrase }, nil
67+ }
68+
69+ type loadSshKeysOptions struct {
70+ WarnInvalidKeys bool
71+ }
72+
73+ func loadSshKeys (ctx context.Context , configs []sshKeyConfig , opts loadSshKeysOptions ) ([]sshKey , error ) {
74+ var res []sshKey
75+
76+ for _ , cfg := range configs {
77+ sshKey , err := parsePrivateSSHKey (cfg )
78+ if err != nil {
79+ if opts .WarnInvalidKeys {
80+ logboek .Context (ctx ).Warn ().LogF ("WARNING: unable to parse ssh key %s: %s\n " , cfg .FilePath , err )
81+ continue
82+ } else {
83+ return nil , fmt .Errorf ("unable to parse ssh key %s: %s" , cfg .FilePath , err )
84+ }
3785 }
86+
87+ res = append (res , sshKey )
3888 }
3989
40- if len (keys ) > 0 {
41- agentSock , err := runSSHAgentWithKeys (ctx , keys )
90+ return res , nil
91+ }
92+
93+ func Init (ctx context.Context , userKeys []string ) error {
94+ var configs []sshKeyConfig
95+
96+ for _ , key := range userKeys {
97+ cfg , err := getSshKeyConfig (key )
4298 if err != nil {
43- return fmt .Errorf ("unable to run ssh agent with specified keys : %s" , err )
99+ return fmt .Errorf ("unable to get ssh key %s config : %s" , key , err )
44100 }
45- if err := setupProcessSSHAgent (agentSock ); err != nil {
46- return fmt .Errorf ("unable to init ssh auth socket to %q: %s" , agentSock , err )
101+
102+ configs = append (configs , cfg )
103+ }
104+
105+ if len (configs ) > 0 {
106+ keys , err := loadSshKeys (ctx , configs , loadSshKeysOptions {})
107+ if err != nil {
108+ return fmt .Errorf ("unable to load ssh keys: %s" , err )
109+ }
110+
111+ if len (keys ) > 0 {
112+ agentSock , err := runSSHAgentWithKeys (ctx , keys )
113+ if err != nil {
114+ return fmt .Errorf ("unable to run ssh agent with specified keys: %s" , err )
115+ }
116+ if err := setupProcessSSHAgent (agentSock ); err != nil {
117+ return fmt .Errorf ("unable to init ssh auth socket to %q: %s" , agentSock , err )
118+ }
119+ return nil
47120 }
48- return nil
49121 }
50122
51123 systemAgentSock := os .Getenv ("SSH_AUTH_SOCK" )
@@ -56,35 +128,23 @@ func Init(ctx context.Context, keys []string) error {
56128 return nil
57129 }
58130
59- var defaultKeys []string
131+ var defaultConfigs []* sshKeyConfig
60132 for _ , defaultFileName := range []string {"id_rsa" , "id_dsa" } {
61133 path := filepath .Join (os .Getenv ("HOME" ), ".ssh" , defaultFileName )
62134
63135 if keyExists , _ := util .FileExists (path ); keyExists {
64- defaultKeys = append (defaultKeys , path )
136+ defaultConfigs = append (defaultConfigs , & sshKeyConfig { FilePath : path } )
65137 }
66138 }
67139
68- if len (defaultKeys ) > 0 {
69- var validKeys []string
70-
71- for _ , key := range defaultKeys {
72- keyData , err := ioutil .ReadFile (key )
73- if err != nil {
74- logboek .Context (ctx ).Warn ().LogF ("WARNING: cannot read default key %s: %s\n " , key , err )
75- continue
76- }
77- _ , err = ssh .ParseRawPrivateKey (keyData )
78- if err != nil {
79- logboek .Context (ctx ).Warn ().LogF ("WARNING: default key %s validation error: %s\n " , key , err )
80- continue
81- }
82-
83- validKeys = append (validKeys , key )
140+ if len (defaultConfigs ) > 0 {
141+ keys , err := loadSshKeys (ctx , configs , loadSshKeysOptions {WarnInvalidKeys : true })
142+ if err != nil {
143+ return fmt .Errorf ("unable to load ssh keys: %s" , err )
84144 }
85145
86- if len (validKeys ) > 0 {
87- agentSock , err := runSSHAgentWithKeys (ctx , validKeys )
146+ if len (keys ) > 0 {
147+ agentSock , err := runSSHAgentWithKeys (ctx , keys )
88148 if err != nil {
89149 return fmt .Errorf ("unable to run ssh agent with specified keys: %s" , err )
90150 }
@@ -108,7 +168,7 @@ func Terminate() error {
108168 return nil
109169}
110170
111- func runSSHAgentWithKeys (ctx context.Context , keys []string ) (string , error ) {
171+ func runSSHAgentWithKeys (ctx context.Context , keys []sshKey ) (string , error ) {
112172 agentSock , err := runSSHAgent (ctx )
113173 if err != nil {
114174 return "" , fmt .Errorf ("error running ssh agent: %s" , err )
@@ -117,7 +177,7 @@ func runSSHAgentWithKeys(ctx context.Context, keys []string) (string, error) {
117177 for _ , key := range keys {
118178 err := addSSHKey (ctx , agentSock , key )
119179 if err != nil {
120- return "" , fmt .Errorf ("error adding ssh key %s : %s" , key , err )
180+ return "" , fmt .Errorf ("error adding ssh key: %s" , err )
121181 }
122182 }
123183
@@ -171,7 +231,7 @@ func runSSHAgent(ctx context.Context) (string, error) {
171231 return sockPath , nil
172232}
173233
174- func addSSHKey (ctx context.Context , authSock string , key string ) error {
234+ func addSSHKey (ctx context.Context , authSock string , key sshKey ) error {
175235 conn , err := net .Dial ("unix" , authSock )
176236 if err != nil {
177237 return fmt .Errorf ("error dialing with ssh agent %s: %s" , authSock , err )
@@ -180,22 +240,52 @@ func addSSHKey(ctx context.Context, authSock string, key string) error {
180240
181241 agentClient := agent .NewClient (conn )
182242
183- keyData , err := ioutil . ReadFile ( key )
243+ err = agentClient . Add (agent. AddedKey { PrivateKey : key . PrivateKey } )
184244 if err != nil {
185- return fmt . Errorf ( "error reading key file %s: %s" , key , err )
245+ return err
186246 }
187247
188- privateKey , err := ssh .ParseRawPrivateKey (keyData )
248+ logboek .Context (ctx ).Info ().LogF ("Added private key %s to ssh agent %s\n " , key .Config .FilePath , authSock )
249+
250+ return nil
251+ }
252+
253+ func parsePrivateSSHKey (cfg sshKeyConfig ) (sshKey , error ) {
254+ keyData , err := ioutil .ReadFile (cfg .FilePath )
189255 if err != nil {
190- return fmt .Errorf ("error parsing private key %s : %s" , key , err )
256+ return sshKey {}, fmt .Errorf ("error reading key file %q : %s" , cfg . FilePath , err )
191257 }
192258
193- err = agentClient .Add (agent.AddedKey {PrivateKey : privateKey })
259+ var privateKey interface {}
260+
261+ privateKey , err = ssh .ParseRawPrivateKey (keyData )
194262 if err != nil {
195- return err
196- }
263+ switch err .(type ) {
264+ case * ssh.PassphraseMissingError :
265+ var passphrase []byte
266+ if len (cfg .Passphrase ) == 0 {
267+ if terminal .IsTerminal (int (os .Stdin .Fd ())) {
268+ if data , err := secret_common .InputFromInteractiveStdin (fmt .Sprintf ("Enter passphrase for ssh key %s: " , cfg .FilePath )); err != nil {
269+ return sshKey {}, fmt .Errorf ("error getting passphrase for ssh key %s: %s" , cfg .FilePath , err )
270+ } else {
271+ passphrase = data
272+ }
273+ } else {
274+ return sshKey {}, fmt .Errorf (`%w: please provide passphrase using --ssh-add="file://PASSPHRASE@FILEPATH" format` , err )
275+ }
276+ } else {
277+ passphrase = cfg .Passphrase
278+ }
197279
198- logboek .Context (ctx ).Info ().LogF ("Added private key %s to ssh agent %s\n " , key , authSock )
280+ privateKey , err = ssh .ParseRawPrivateKeyWithPassphrase (keyData , passphrase )
281+ if err != nil {
282+ return sshKey {}, fmt .Errorf ("error parsing private key %s: %s" , cfg .FilePath , err )
283+ }
199284
200- return nil
285+ default :
286+ return sshKey {}, fmt .Errorf ("error parsing private key %s: %s" , cfg .FilePath , err )
287+ }
288+ }
289+
290+ return sshKey {Config : cfg , PrivateKey : privateKey }, nil
201291}
0 commit comments