summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/bindings/bindings.go43
-rw-r--r--pkg/bindings/connection.go98
-rw-r--r--pkg/domain/entities/engine.go3
-rw-r--r--pkg/domain/infra/runtime_abi.go4
-rw-r--r--pkg/domain/infra/runtime_tunnel.go4
-rw-r--r--pkg/terminal/util.go133
6 files changed, 153 insertions, 132 deletions
diff --git a/pkg/bindings/bindings.go b/pkg/bindings/bindings.go
index 94f7a45d0..ae5610b0f 100644
--- a/pkg/bindings/bindings.go
+++ b/pkg/bindings/bindings.go
@@ -8,13 +8,7 @@
package bindings
import (
- "errors"
- "fmt"
- "io"
- "os"
-
"github.com/blang/semver"
- "golang.org/x/crypto/ssh/terminal"
)
var (
@@ -30,40 +24,3 @@ var (
// APIVersion - podman will fail to run if this value is wrong
APIVersion = semver.MustParse("1.0.0")
)
-
-// readPassword 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.
-func readPassword(prompt string) (pw []byte, err error) {
- fd := int(os.Stdin.Fd())
- if terminal.IsTerminal(fd) {
- fmt.Fprint(os.Stderr, prompt)
- pw, err = terminal.ReadPassword(fd)
- fmt.Fprintln(os.Stderr)
- return
- }
-
- var b [1]byte
- for {
- n, err := os.Stdin.Read(b[:])
- // terminal.ReadPassword 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.ReadPassword accepts EOF-terminated passwords
- // if non-empty, so we do the same
- if err == io.EOF && len(pw) > 0 {
- err = nil
- }
- return pw, err
- }
- }
-}
diff --git a/pkg/bindings/connection.go b/pkg/bindings/connection.go
index a9c61e5ae..584aa55c1 100644
--- a/pkg/bindings/connection.go
+++ b/pkg/bindings/connection.go
@@ -1,28 +1,24 @@
package bindings
import (
- "bufio"
"context"
"fmt"
"io"
- "io/ioutil"
"net"
"net/http"
"net/url"
"os"
- "path/filepath"
"strconv"
"strings"
- "sync"
"time"
"github.com/blang/semver"
+ "github.com/containers/libpod/pkg/terminal"
jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
- "k8s.io/client-go/util/homedir"
)
var (
@@ -31,8 +27,6 @@ var (
Host: "d",
Path: "/v" + APIVersion.String() + "/libpod",
}
- passPhrase []byte
- phraseSync sync.Once
)
type APIResponse struct {
@@ -77,7 +71,7 @@ func NewConnection(ctx context.Context, uri string) (context.Context, error) {
// For example tcp://localhost:<port>
// or unix:///run/podman/podman.sock
// or ssh://<user>@<host>[:port]/run/podman/podman.sock?secure=True
-func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase string, identities ...string) (context.Context, error) {
+func NewConnectionWithIdentity(ctx context.Context, uri string, identity string) (context.Context, error) {
var (
err error
secure bool
@@ -86,11 +80,12 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase strin
uri = v
}
- if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found && len(identities) == 0 {
- identities = append(identities, v)
+ if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found && len(identity) == 0 {
+ identity = v
}
- if v, found := os.LookupEnv("CONTAINER_PASSPHRASE"); found && passPhrase == "" {
+ passPhrase := ""
+ if v, found := os.LookupEnv("CONTAINER_PASSPHRASE"); found {
passPhrase = v
}
@@ -98,7 +93,6 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase strin
if err != nil {
return nil, errors.Wrapf(err, "Value of CONTAINER_HOST is not a valid url: %s", uri)
}
- // TODO Fill in missing defaults for _url...
// Now we setup the http Client to use the connection above
var connection Connection
@@ -108,7 +102,7 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase strin
if err != nil {
secure = false
}
- connection, err = sshClient(_url, secure, passPhrase, identities...)
+ connection, err = sshClient(_url, secure, passPhrase, identity)
case "unix":
if !strings.HasPrefix(uri, "unix:///") {
// autofix unix://path_element vs unix:///path_element
@@ -122,7 +116,7 @@ func NewConnectionWithIdentity(ctx context.Context, uri string, passPhrase strin
}
connection = tcpClient(_url)
default:
- return nil, errors.Errorf("'%s' is not a supported schema", _url.Scheme)
+ return nil, errors.Errorf("unable to create connection. %q is not a supported schema", _url.Scheme)
}
if err != nil {
return nil, errors.Wrapf(err, "Failed to create %sClient", _url.Scheme)
@@ -185,16 +179,14 @@ func pingNewConnection(ctx context.Context) error {
return errors.Errorf("ping response was %q", response.StatusCode)
}
-func sshClient(_url *url.URL, secure bool, passPhrase string, identities ...string) (Connection, error) {
+func sshClient(_url *url.URL, secure bool, passPhrase string, identity string) (Connection, error) {
authMethods := []ssh.AuthMethod{}
- for _, i := range identities {
- auth, err := publicKey(i, []byte(passPhrase))
- if err != nil {
- fmt.Fprint(os.Stderr, errors.Wrapf(err, "failed to parse identity %q", i).Error()+"\n")
- continue
- }
- authMethods = append(authMethods, auth)
+ auth, err := terminal.PublicKey(identity, []byte(passPhrase))
+ if err != nil {
+ return Connection{}, errors.Wrapf(err, "failed to parse identity %q", identity)
}
+ logrus.Debugf("public key signer enabled for identity %q", identity)
+ authMethods = append(authMethods, auth)
if sock, found := os.LookupEnv("SSH_AUTH_SOCK"); found {
logrus.Debugf("Found SSH_AUTH_SOCK %q, ssh-agent signer enabled", sock)
@@ -213,7 +205,7 @@ func sshClient(_url *url.URL, secure bool, passPhrase string, identities ...stri
callback := ssh.InsecureIgnoreHostKey()
if secure {
- key := hostKey(_url.Hostname())
+ key := terminal.HostKey(_url.Hostname())
if key != nil {
callback = ssh.FixedHostKey(key)
}
@@ -339,63 +331,3 @@ func (h *APIResponse) IsClientError() bool {
func (h *APIResponse) IsServerError() bool {
return h.Response.StatusCode/100 == 5
}
-
-func publicKey(path string, passphrase []byte) (ssh.AuthMethod, error) {
- key, err := ioutil.ReadFile(path)
- if err != nil {
- return nil, err
- }
-
- signer, err := ssh.ParsePrivateKey(key)
- if err != nil {
- if _, ok := err.(*ssh.PassphraseMissingError); !ok {
- return nil, err
- }
- if len(passphrase) == 0 {
- phraseSync.Do(promptPassphrase)
- passphrase = passPhrase
- }
- signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphrase)
- if err != nil {
- return nil, err
- }
- }
- return ssh.PublicKeys(signer), nil
-}
-
-func promptPassphrase() {
- phrase, err := readPassword("Key Passphrase: ")
- if err != nil {
- passPhrase = []byte{}
- return
- }
- passPhrase = phrase
-}
-
-func hostKey(host string) ssh.PublicKey {
- // parse OpenSSH known_hosts file
- // ssh or use ssh-keyscan to get initial key
- knownHosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts")
- fd, err := os.Open(knownHosts)
- if err != nil {
- logrus.Error(err)
- return nil
- }
-
- scanner := bufio.NewScanner(fd)
- for scanner.Scan() {
- _, hosts, key, _, _, err := ssh.ParseKnownHosts(scanner.Bytes())
- if err != nil {
- logrus.Errorf("Failed to parse known_hosts: %s", scanner.Text())
- continue
- }
-
- for _, h := range hosts {
- if h == host {
- return key
- }
- }
- }
-
- return nil
-}
diff --git a/pkg/domain/entities/engine.go b/pkg/domain/entities/engine.go
index 1f056bad7..6776d09e9 100644
--- a/pkg/domain/entities/engine.go
+++ b/pkg/domain/entities/engine.go
@@ -41,9 +41,8 @@ type PodmanConfig struct {
ConmonPath string // --conmon flag will set Engine.ConmonPath
CPUProfile string // Hidden: Should CPU profile be taken
EngineMode EngineMode // ABI or Tunneling mode
- Identities []string // ssh identities for connecting to server
+ Identity string // ssh identity for connecting to server
MaxWorks int // maximum number of parallel threads
- PassPhrase string // ssh passphrase for identity for connecting to server
RegistriesConf string // allows for specifying a custom registries.conf
Remote bool // Connection to Podman API Service will use RESTful API
RuntimePath string // --runtime flag will set Engine.RuntimePath
diff --git a/pkg/domain/infra/runtime_abi.go b/pkg/domain/infra/runtime_abi.go
index 60d0c6e86..3b344cb08 100644
--- a/pkg/domain/infra/runtime_abi.go
+++ b/pkg/domain/infra/runtime_abi.go
@@ -20,7 +20,7 @@ func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine,
r, err := NewLibpodRuntime(facts.FlagSet, facts)
return r, err
case entities.TunnelMode:
- ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.PassPhrase, facts.Identities...)
+ ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
return &tunnel.ContainerEngine{ClientCxt: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
@@ -33,7 +33,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error)
r, err := NewLibpodImageRuntime(facts.FlagSet, facts)
return r, err
case entities.TunnelMode:
- ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.PassPhrase, facts.Identities...)
+ ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
return &tunnel.ImageEngine{ClientCxt: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
diff --git a/pkg/domain/infra/runtime_tunnel.go b/pkg/domain/infra/runtime_tunnel.go
index 24a93b888..039a8339b 100644
--- a/pkg/domain/infra/runtime_tunnel.go
+++ b/pkg/domain/infra/runtime_tunnel.go
@@ -16,7 +16,7 @@ func NewContainerEngine(facts *entities.PodmanConfig) (entities.ContainerEngine,
case entities.ABIMode:
return nil, fmt.Errorf("direct runtime not supported")
case entities.TunnelMode:
- ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.PassPhrase, facts.Identities...)
+ ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
return &tunnel.ContainerEngine{ClientCxt: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
@@ -28,7 +28,7 @@ func NewImageEngine(facts *entities.PodmanConfig) (entities.ImageEngine, error)
case entities.ABIMode:
return nil, fmt.Errorf("direct image runtime not supported")
case entities.TunnelMode:
- ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.PassPhrase, facts.Identities...)
+ ctx, err := bindings.NewConnectionWithIdentity(context.Background(), facts.URI, facts.Identity)
return &tunnel.ImageEngine{ClientCxt: ctx}, err
}
return nil, fmt.Errorf("runtime mode '%v' is not supported", facts.EngineMode)
diff --git a/pkg/terminal/util.go b/pkg/terminal/util.go
new file mode 100644
index 000000000..ab3dc54e4
--- /dev/null
+++ b/pkg/terminal/util.go
@@ -0,0 +1,133 @@
+package terminal
+
+import (
+ "bufio"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "sync"
+
+ "github.com/sirupsen/logrus"
+ "golang.org/x/crypto/ssh"
+ "golang.org/x/crypto/ssh/terminal"
+ "k8s.io/client-go/util/homedir"
+)
+
+var (
+ passPhrase []byte
+ phraseSync sync.Once
+ password []byte
+ passwordSync sync.Once
+)
+
+// ReadPassword 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.
+func ReadPassword(prompt string) (pw []byte, err error) {
+ fd := int(os.Stdin.Fd())
+ if terminal.IsTerminal(fd) {
+ fmt.Fprint(os.Stderr, prompt)
+ pw, err = terminal.ReadPassword(fd)
+ fmt.Fprintln(os.Stderr)
+ return
+ }
+
+ var b [1]byte
+ for {
+ n, err := os.Stdin.Read(b[:])
+ // terminal.ReadPassword 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.ReadPassword 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 PublicKey(path string, passphrase []byte) (ssh.AuthMethod, error) {
+ key, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+
+ signer, err := ssh.ParsePrivateKey(key)
+ if err != nil {
+ if _, ok := err.(*ssh.PassphraseMissingError); !ok {
+ return nil, err
+ }
+ if len(passphrase) == 0 {
+ passphrase = ReadPassphrase()
+ }
+ signer, err = ssh.ParsePrivateKeyWithPassphrase(key, passphrase)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return ssh.PublicKeys(signer), nil
+}
+
+func ReadPassphrase() []byte {
+ phraseSync.Do(func() {
+ secret, err := ReadPassword("Key Passphrase: ")
+ if err != nil {
+ secret = []byte{}
+ }
+ passPhrase = secret
+ })
+ return passPhrase
+}
+
+func ReadLogin() []byte {
+ passwordSync.Do(func() {
+ secret, err := ReadPassword("Login password: ")
+ if err != nil {
+ secret = []byte{}
+ }
+ password = secret
+ })
+ return password
+}
+
+func HostKey(host string) ssh.PublicKey {
+ // parse OpenSSH known_hosts file
+ // ssh or use ssh-keyscan to get initial key
+ knownHosts := filepath.Join(homedir.HomeDir(), ".ssh", "known_hosts")
+ fd, err := os.Open(knownHosts)
+ if err != nil {
+ logrus.Error(err)
+ return nil
+ }
+
+ scanner := bufio.NewScanner(fd)
+ for scanner.Scan() {
+ _, hosts, key, _, _, err := ssh.ParseKnownHosts(scanner.Bytes())
+ if err != nil {
+ logrus.Errorf("Failed to parse known_hosts: %s", scanner.Text())
+ continue
+ }
+
+ for _, h := range hosts {
+ if h == host {
+ return key
+ }
+ }
+ }
+
+ return nil
+}