Skip to content

Commit

Permalink
Support for SSH connection
Browse files Browse the repository at this point in the history
Signed-off-by: Matej Vasek <[email protected]>
  • Loading branch information
matejvasek committed Sep 23, 2021
1 parent 9dd9f8f commit 38b1b11
Show file tree
Hide file tree
Showing 24 changed files with 1,725 additions and 7 deletions.
178 changes: 177 additions & 1 deletion cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
package cmd

import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"net"
"net/http"
"net/url"
"os"
"strings"

"golang.org/x/crypto/ssh"
"golang.org/x/term"

"github.com/buildpacks/pack/internal/sshdialer"

dockerClient "github.com/docker/docker/client"
"github.com/heroku/color"
"github.com/pkg/errors"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -133,10 +150,169 @@ func initConfig() (config.Config, string, error) {
}

func initClient(logger logging.Logger, cfg config.Config) (pack.Client, error) {
client, err := pack.NewClient(pack.WithLogger(logger), pack.WithExperimental(cfg.Experimental), pack.WithRegistryMirrors(cfg.RegistryMirrors))
dc, err := initDockerClient()
if err != nil {
return pack.Client{}, err
}
client, err := pack.NewClient(pack.WithLogger(logger), pack.WithExperimental(cfg.Experimental), pack.WithRegistryMirrors(cfg.RegistryMirrors), pack.WithDockerClient(dc))
if err != nil {
return pack.Client{}, err
}

return *client, nil
}

func initDockerClient() (dockerClient.CommonAPIClient, error) {
dockerClientOpts := []dockerClient.Opt{
dockerClient.WithVersion("1.38"),
}

usesSSH := false
if dockerHost, ok := os.LookupEnv("DOCKER_HOST"); ok {
if _url, err := url.Parse(dockerHost); err == nil && _url.Scheme == "ssh" {
usesSSH = true
credentialsConfig := sshdialer.Config{
Identity: os.Getenv("DOCKER_HOST_SSH_IDENTITY"),
PassPhrase: os.Getenv("DOCKER_HOST_SSH_IDENTITY_PASSPHRASE"),
PasswordCallback: newPasswordCbk(),
PassPhraseCallback: newPassPhraseCbk(),
HostKeyCallback: newHostKeyCbk(),
}
dialer, err := sshdialer.NewDialer(_url, credentialsConfig)
if err != nil {
return nil, err
}

path := _url.Path
if path == "" {
path = "/var/run/docker.sock"
}

dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) {
return dialer.DialContext(ctx, "unix", path)
}

httpClient := &http.Client{
// No tls
// No proxy
Transport: &http.Transport{
DialContext: dialContext,
},
}
dockerClientOpts = append(dockerClientOpts,
dockerClient.WithHTTPClient(httpClient),
dockerClient.WithHost("http://example.com/"),
dockerClient.WithDialContext(dialContext),
)
}
}
if !usesSSH {
dockerClientOpts = append(dockerClientOpts, dockerClient.FromEnv)
}

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 newPasswordCbk() sshdialer.PasswordCallback {
var pwdSet bool
var pwd string
return func() (string, error) {
if pwdSet {
return pwd, nil
}

p, err := readSecret("please enter password:")
if err != nil {
return "", err
}
pwdSet = true
pwd = string(p)

return pwd, err
}
}

func newPassPhraseCbk() sshdialer.PassPhraseCallback {
var pwdSet bool
var pwd string
return func() (string, error) {
if pwdSet {
return pwd, nil
}

p, err := readSecret("please enter passphrase to private key:")
if err != nil {
return "", err
}
pwdSet = true
pwd = string(p)

return pwd, 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 SHA256:%s
Are you sure you want to continue connecting (yes/no)? `
fmt.Fprintf(os.Stdout, 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()
return nil
}

return errors.New("key rejected")
}
}
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ require (
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e
github.com/moby/sys/mount v0.2.0 // indirect
github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf // indirect
github.com/onsi/gomega v1.16.0
github.com/opencontainers/image-spec v1.0.1
github.com/pelletier/go-toml v1.9.4
Expand Down
6 changes: 1 addition & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -251,8 +251,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
Expand Down Expand Up @@ -590,9 +588,8 @@ github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2J
github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM=
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI=
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf h1:Un6PNx5oMK6CCwO3QTUyPiK2mtZnPrpDl5UnZ64eCkw=
github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
Expand Down Expand Up @@ -1042,7 +1039,6 @@ golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
Loading

0 comments on commit 38b1b11

Please sign in to comment.