Skip to content

Commit

Permalink
Merge pull request #1282 from matejvasek/add-ssh-support
Browse files Browse the repository at this point in the history
Support for SSH connection
  • Loading branch information
jromero authored Oct 25, 2021
2 parents fc9cb50 + 224e0a4 commit 28598a1
Show file tree
Hide file tree
Showing 28 changed files with 2,133 additions and 2 deletions.
4 changes: 3 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ func WithRegistryMirrors(registryMirrors map[string]string) ClientOption {
}
}

const DockerAPIVersion = "1.38"

// NewClient allocates and returns a Client configured with the specified options.
func NewClient(opts ...ClientOption) (*Client, error) {
var client Client
Expand All @@ -165,7 +167,7 @@ func NewClient(opts ...ClientOption) (*Client, error) {
var err error
client.docker, err = dockerClient.NewClientWithOpts(
dockerClient.FromEnv,
dockerClient.WithVersion("1.38"),
dockerClient.WithVersion(DockerAPIVersion),
)
if err != nil {
return nil, errors.Wrap(err, "creating docker client")
Expand Down
6 changes: 5 additions & 1 deletion cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,11 @@ 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 := tryInitSSHDockerClient()
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
}
Expand Down
147 changes: 147 additions & 0 deletions cmd/docker_init.go
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")
}
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/apex/log v1.9.0
github.com/buildpacks/imgutil v0.0.0-20211001201950-cf7ae41c3771
github.com/buildpacks/lifecycle v0.12.0
github.com/docker/cli v20.10.7+incompatible
github.com/docker/docker v20.10.9+incompatible
github.com/docker/go-connections v0.4.0
github.com/gdamore/tcell/v2 v2.4.0
Expand All @@ -14,6 +15,7 @@ require (
github.com/google/go-cmp v0.5.6
github.com/google/go-containerregistry v0.6.0
github.com/google/go-github/v30 v30.1.0
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95
github.com/heroku/color v0.0.6
github.com/mitchellh/ioprogress v0.0.0-20180201004757-6a23b12fa88e
github.com/moby/sys/mount v0.2.0 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 h1:S4qyfL2sEm5Budr4KVMyEniCy+PbS55651I/a+Kn/NQ=
github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E=
github.com/heroku/color v0.0.6 h1:UTFFMrmMLFcL3OweqP1lAdp8i1y/9oHqkeHjQ/b/Ny0=
github.com/heroku/color v0.0.6/go.mod h1:ZBvOcx7cTF2QKOv4LbmoBtNl5uB17qWxGuzZrsi1wLU=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
Expand Down Expand Up @@ -993,6 +995,7 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
12 changes: 12 additions & 0 deletions internal/sshdialer/posix_test.go
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)
}
}
Loading

0 comments on commit 28598a1

Please sign in to comment.