|
| 1 | +package main |
| 2 | + |
| 3 | +import ( |
| 4 | + "io/ioutil" |
| 5 | + "net/url" |
| 6 | + "path" |
| 7 | + |
| 8 | + "github.com/go-git/go-git/v5/plumbing/transport" |
| 9 | + "github.com/go-git/go-git/v5/plumbing/transport/http" |
| 10 | + gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh" |
| 11 | + "github.com/mitchellh/go-homedir" |
| 12 | + "github.com/pkg/errors" |
| 13 | + "golang.org/x/crypto/ssh" |
| 14 | +) |
| 15 | + |
| 16 | +// setupAuth, if necessary, configures the git CLI for authentication using |
| 17 | +// either SSH or the "store" (username/password-based) credential helper since |
| 18 | +// the git-initializer component does fall back on the git CLI for certain |
| 19 | +// operations. It additionally returns an appropriate implementation of |
| 20 | +// transport.AuthMethod for operations that interact with remote repositories |
| 21 | +// programmatically. |
| 22 | +func setupAuth(evt event) (transport.AuthMethod, error) { |
| 23 | + homeDir, err := homedir.Dir() |
| 24 | + if err != nil { |
| 25 | + return nil, errors.Wrap(err, "error finding user's home directory") |
| 26 | + } |
| 27 | + |
| 28 | + // If an SSH key was provided, use that. |
| 29 | + if key, ok := evt.Project.Secrets["gitSSHKey"]; ok { |
| 30 | + // If a passphrase was supplied for the key, decrypt the key now. |
| 31 | + keyPass, ok := evt.Project.Secrets["gitSSHKeyPassword"] |
| 32 | + if ok { |
| 33 | + var err error |
| 34 | + if key, err = decryptKey(key, keyPass); err != nil { |
| 35 | + return nil, errors.Wrap(err, "error decrypting SSH key") |
| 36 | + } |
| 37 | + } |
| 38 | + rsaKeyPath := path.Join(homeDir, ".ssh", "id_rsa") |
| 39 | + if err := ioutil.WriteFile(rsaKeyPath, []byte(key), 0600); err != nil { |
| 40 | + return nil, errors.Wrapf(err, "error writing SSH key to %q", rsaKeyPath) |
| 41 | + } |
| 42 | + |
| 43 | + // This is the implementation of the transport.AuthMethod interface that can |
| 44 | + // be used for operations that interact with the remote repository |
| 45 | + // interactively. |
| 46 | + publicKeys, err := gitssh.NewPublicKeys("git", []byte(key), keyPass) |
| 47 | + if err != nil { |
| 48 | + return nil, |
| 49 | + errors.Wrap(err, "error getting transport.AuthMethod using SSH key") |
| 50 | + } |
| 51 | + |
| 52 | + // This prevents the CLI from interactively requesting the user to allow |
| 53 | + // connection to a new/unrecognized host. |
| 54 | + publicKeys.HostKeyCallback = ssh.InsecureIgnoreHostKey() // nolint: gosec |
| 55 | + return publicKeys, nil // We're done |
| 56 | + } |
| 57 | + |
| 58 | + // If a password was provided, use that. |
| 59 | + if password, ok := evt.Project.Secrets["gitPassword"]; ok { |
| 60 | + credentialURL, err := url.Parse(evt.Worker.Git.CloneURL) |
| 61 | + if err != nil { |
| 62 | + return nil, |
| 63 | + errors.Wrapf(err, "error parsing URL %q", evt.Worker.Git.CloneURL) |
| 64 | + } |
| 65 | + // If a username was provided, use it. One may not have been because some |
| 66 | + // git providers, like GitHub, for instance, will allow any non-empty |
| 67 | + // username to be used in conjunction with a personal access token. |
| 68 | + username, ok := evt.Project.Secrets["gitUsername"] |
| 69 | + // If a username wasn't provided, we can ALSO try to pick it out of the URL. |
| 70 | + if !ok && credentialURL.User != nil { |
| 71 | + username = credentialURL.User.Username() |
| 72 | + } |
| 73 | + // If the username is still the empty string, we assume we're working with a |
| 74 | + // git provider like GitHub that only requires the username to be non-empty. |
| 75 | + // We arbitrarily set it to "git". |
| 76 | + if username == "" { |
| 77 | + username = "git" |
| 78 | + } |
| 79 | + // Remove path and query string components from the URL |
| 80 | + credentialURL.Path = "" |
| 81 | + credentialURL.RawQuery = "" |
| 82 | + // Augment the URL with user/pass information. |
| 83 | + credentialURL.User = url.UserPassword(username, password) |
| 84 | + // Write the URL to the location used by the "stored" credential helper. |
| 85 | + credentialsPath := path.Join(homeDir, ".git-credentials") |
| 86 | + if err := ioutil.WriteFile( |
| 87 | + credentialsPath, |
| 88 | + []byte(credentialURL.String()), |
| 89 | + 0600, |
| 90 | + ); err != nil { |
| 91 | + return nil, |
| 92 | + errors.Wrapf(err, "error writing credentials to %q", credentialsPath) |
| 93 | + } |
| 94 | + |
| 95 | + // This is the implementation of the transport.AuthMethod interface that can |
| 96 | + // be used for operations that interact with the remote repository |
| 97 | + // interactively. |
| 98 | + return &http.BasicAuth{ |
| 99 | + Username: username, |
| 100 | + Password: password, |
| 101 | + }, nil // We're done |
| 102 | + } |
| 103 | + |
| 104 | + // No auth setup required if we get to here. |
| 105 | + return nil, nil |
| 106 | +} |
0 commit comments