github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/cmd/docker_init.go (about) 1 package cmd 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/base64" 7 "errors" 8 "fmt" 9 "io" 10 "net/http" 11 "net/url" 12 "os" 13 "strings" 14 15 dockerClient "github.com/docker/docker/client" 16 "golang.org/x/crypto/ssh" 17 "golang.org/x/term" 18 19 "github.com/buildpacks/pack/internal/sshdialer" 20 "github.com/buildpacks/pack/pkg/client" 21 ) 22 23 func tryInitSSHDockerClient() (dockerClient.CommonAPIClient, error) { 24 dockerHost := os.Getenv("DOCKER_HOST") 25 _url, err := url.Parse(dockerHost) 26 isSSH := err == nil && _url.Scheme == "ssh" 27 28 if !isSSH { 29 return nil, nil 30 } 31 32 credentialsConfig := sshdialer.Config{ 33 Identity: os.Getenv("DOCKER_HOST_SSH_IDENTITY"), 34 PassPhrase: os.Getenv("DOCKER_HOST_SSH_IDENTITY_PASSPHRASE"), 35 PasswordCallback: newReadSecretCbk("please enter password:"), 36 PassPhraseCallback: newReadSecretCbk("please enter passphrase to private key:"), 37 HostKeyCallback: newHostKeyCbk(), 38 } 39 dialContext, err := sshdialer.NewDialContext(_url, credentialsConfig) 40 if err != nil { 41 return nil, err 42 } 43 44 httpClient := &http.Client{ 45 // No tls 46 // No proxy 47 Transport: &http.Transport{ 48 DialContext: dialContext, 49 }, 50 } 51 52 dockerClientOpts := []dockerClient.Opt{ 53 dockerClient.WithVersion(client.DockerAPIVersion), 54 dockerClient.WithHTTPClient(httpClient), 55 dockerClient.WithHost("http://dummy"), 56 dockerClient.WithDialContext(dialContext), 57 } 58 59 return dockerClient.NewClientWithOpts(dockerClientOpts...) 60 } 61 62 // readSecret prompts for a secret and returns value input by user from stdin 63 // Unlike terminal.ReadPassword(), $(echo $SECRET | podman...) is supported. 64 // Additionally, all input after `<secret>/n` is queued to podman command. 65 // 66 // NOTE: this code is based on "github.com/containers/podman/v3/pkg/terminal" 67 func readSecret(prompt string) (pw []byte, err error) { 68 fd := int(os.Stdin.Fd()) 69 if term.IsTerminal(fd) { 70 fmt.Fprint(os.Stderr, prompt) 71 pw, err = term.ReadPassword(fd) 72 fmt.Fprintln(os.Stderr) 73 return 74 } 75 76 var b [1]byte 77 for { 78 n, err := os.Stdin.Read(b[:]) 79 // terminal.readSecret discards any '\r', so we do the same 80 if n > 0 && b[0] != '\r' { 81 if b[0] == '\n' { 82 return pw, nil 83 } 84 pw = append(pw, b[0]) 85 // limit size, so that a wrong input won't fill up the memory 86 if len(pw) > 1024 { 87 err = errors.New("password too long, 1024 byte limit") 88 } 89 } 90 if err != nil { 91 // terminal.readSecret accepts EOF-terminated passwords 92 // if non-empty, so we do the same 93 if err == io.EOF && len(pw) > 0 { 94 err = nil 95 } 96 return pw, err 97 } 98 } 99 } 100 101 func newReadSecretCbk(prompt string) sshdialer.SecretCallback { 102 var secretSet bool 103 var secret string 104 return func() (string, error) { 105 if secretSet { 106 return secret, nil 107 } 108 109 p, err := readSecret(prompt) 110 if err != nil { 111 return "", err 112 } 113 secretSet = true 114 secret = string(p) 115 116 return secret, err 117 } 118 } 119 120 func newHostKeyCbk() sshdialer.HostKeyCallback { 121 var trust []byte 122 return func(hostPort string, pubKey ssh.PublicKey) error { 123 if bytes.Equal(trust, pubKey.Marshal()) { 124 return nil 125 } 126 msg := `The authenticity of host %s cannot be established. 127 %s key fingerprint is %s 128 Are you sure you want to continue connecting (yes/no)? ` 129 fmt.Fprintf(os.Stderr, msg, hostPort, pubKey.Type(), ssh.FingerprintSHA256(pubKey)) 130 reader := bufio.NewReader(os.Stdin) 131 answer, err := reader.ReadString('\n') 132 if err != nil { 133 return err 134 } 135 answer = strings.TrimRight(answer, "\r\n") 136 answer = strings.ToLower(answer) 137 138 if answer == "yes" || answer == "y" { 139 trust = pubKey.Marshal() 140 fmt.Fprintf(os.Stderr, "To avoid this in future add following line into your ~/.ssh/known_hosts:\n%s %s %s\n", 141 hostPort, pubKey.Type(), base64.StdEncoding.EncodeToString(trust)) 142 return nil 143 } 144 145 return errors.New("key rejected") 146 } 147 }