-
Notifications
You must be signed in to change notification settings - Fork 293
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1282 from matejvasek/add-ssh-support
Support for SSH connection
- Loading branch information
Showing
28 changed files
with
2,133 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
package cmd | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"encoding/base64" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
"strings" | ||
|
||
"github.com/buildpacks/pack" | ||
"github.com/buildpacks/pack/internal/sshdialer" | ||
|
||
dockerClient "github.com/docker/docker/client" | ||
"golang.org/x/crypto/ssh" | ||
"golang.org/x/term" | ||
) | ||
|
||
func tryInitSSHDockerClient() (dockerClient.CommonAPIClient, error) { | ||
dockerHost := os.Getenv("DOCKER_HOST") | ||
_url, err := url.Parse(dockerHost) | ||
isSSH := err == nil && _url.Scheme == "ssh" | ||
|
||
if !isSSH { | ||
return nil, nil | ||
} | ||
|
||
credentialsConfig := sshdialer.Config{ | ||
Identity: os.Getenv("DOCKER_HOST_SSH_IDENTITY"), | ||
PassPhrase: os.Getenv("DOCKER_HOST_SSH_IDENTITY_PASSPHRASE"), | ||
PasswordCallback: newReadSecretCbk("please enter password:"), | ||
PassPhraseCallback: newReadSecretCbk("please enter passphrase to private key:"), | ||
HostKeyCallback: newHostKeyCbk(), | ||
} | ||
dialContext, err := sshdialer.NewDialContext(_url, credentialsConfig) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
httpClient := &http.Client{ | ||
// No tls | ||
// No proxy | ||
Transport: &http.Transport{ | ||
DialContext: dialContext, | ||
}, | ||
} | ||
|
||
dockerClientOpts := []dockerClient.Opt{ | ||
dockerClient.WithVersion(pack.DockerAPIVersion), | ||
dockerClient.WithHTTPClient(httpClient), | ||
dockerClient.WithHost("http://dummy/"), | ||
dockerClient.WithDialContext(dialContext), | ||
} | ||
|
||
return dockerClient.NewClientWithOpts(dockerClientOpts...) | ||
} | ||
|
||
// readSecret prompts for a secret and returns value input by user from stdin | ||
// Unlike terminal.ReadPassword(), $(echo $SECRET | podman...) is supported. | ||
// Additionally, all input after `<secret>/n` is queued to podman command. | ||
// | ||
// NOTE: this code is based on "github.com/containers/podman/v3/pkg/terminal" | ||
func readSecret(prompt string) (pw []byte, err error) { | ||
fd := int(os.Stdin.Fd()) | ||
if term.IsTerminal(fd) { | ||
fmt.Fprint(os.Stderr, prompt) | ||
pw, err = term.ReadPassword(fd) | ||
fmt.Fprintln(os.Stderr) | ||
return | ||
} | ||
|
||
var b [1]byte | ||
for { | ||
n, err := os.Stdin.Read(b[:]) | ||
// terminal.readSecret discards any '\r', so we do the same | ||
if n > 0 && b[0] != '\r' { | ||
if b[0] == '\n' { | ||
return pw, nil | ||
} | ||
pw = append(pw, b[0]) | ||
// limit size, so that a wrong input won't fill up the memory | ||
if len(pw) > 1024 { | ||
err = errors.New("password too long, 1024 byte limit") | ||
} | ||
} | ||
if err != nil { | ||
// terminal.readSecret accepts EOF-terminated passwords | ||
// if non-empty, so we do the same | ||
if err == io.EOF && len(pw) > 0 { | ||
err = nil | ||
} | ||
return pw, err | ||
} | ||
} | ||
} | ||
|
||
func newReadSecretCbk(prompt string) sshdialer.SecretCallback { | ||
var secretSet bool | ||
var secret string | ||
return func() (string, error) { | ||
if secretSet { | ||
return secret, nil | ||
} | ||
|
||
p, err := readSecret(prompt) | ||
if err != nil { | ||
return "", err | ||
} | ||
secretSet = true | ||
secret = string(p) | ||
|
||
return secret, err | ||
} | ||
} | ||
|
||
func newHostKeyCbk() sshdialer.HostKeyCallback { | ||
var trust []byte | ||
return func(hostPort string, pubKey ssh.PublicKey) error { | ||
if bytes.Equal(trust, pubKey.Marshal()) { | ||
return nil | ||
} | ||
msg := `The authenticity of host %s cannot be established. | ||
%s key fingerprint is %s | ||
Are you sure you want to continue connecting (yes/no)? ` | ||
fmt.Fprintf(os.Stderr, msg, hostPort, pubKey.Type(), ssh.FingerprintSHA256(pubKey)) | ||
reader := bufio.NewReader(os.Stdin) | ||
answer, err := reader.ReadString('\n') | ||
if err != nil { | ||
return err | ||
} | ||
answer = strings.TrimRight(answer, "\r\n") | ||
answer = strings.ToLower(answer) | ||
|
||
if answer == "yes" || answer == "y" { | ||
trust = pubKey.Marshal() | ||
fmt.Fprintf(os.Stderr, "To avoid this in future add following line into your ~/.ssh/known_hosts:\n%s %s %s\n", | ||
hostPort, pubKey.Type(), base64.StdEncoding.EncodeToString(trust)) | ||
return nil | ||
} | ||
|
||
return errors.New("key rejected") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//+build !windows | ||
|
||
package sshdialer_test | ||
|
||
import "os" | ||
|
||
func fixupPrivateKeyMod(path string) { | ||
err := os.Chmod(path, 0400) | ||
if err != nil { | ||
panic(err) | ||
} | ||
} |
Oops, something went wrong.