mirror of
				https://github.com/containers/podman.git
				synced 2025-10-31 01:50:50 +08:00 
			
		
		
		
	podman machine ssh handling
add the key used in newly initialized machines to the user's known_hosts file. This ensures that golang will be able to ssh into the machine using podman-remote. Also, remove the /dev/null redirection for podman machine ssh's known_hosts file. resolves #15347 Signed-off-by: Charlie Doern <cdoern@redhat.com> Signed-off-by: cdoern <cbdoer23@g.holycross.edu>
This commit is contained in:
		
							
								
								
									
										3
									
								
								vendor/github.com/containers/common/libimage/platform.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								vendor/github.com/containers/common/libimage/platform.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -63,6 +63,9 @@ func toPlatformString(os, arch, variant string) string { | ||||
| //  * 2) a bool indicating whether architecture, os or variant were set (some callers need that to decide whether they need to throw an error) | ||||
| //  * 3) a fatal error that occurred prior to check for matches (e.g., storage errors etc.) | ||||
| func (i *Image) matchesPlatform(ctx context.Context, os, arch, variant string) (error, bool, error) { | ||||
| 	if err := i.isCorrupted(""); err != nil { | ||||
| 		return err, false, nil | ||||
| 	} | ||||
| 	inspectInfo, err := i.inspectInfo(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, false, fmt.Errorf("inspecting image: %w", err) | ||||
|  | ||||
							
								
								
									
										19
									
								
								vendor/github.com/containers/common/pkg/config/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										19
									
								
								vendor/github.com/containers/common/pkg/config/config.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -613,6 +613,9 @@ type Destination struct { | ||||
|  | ||||
| 	// Identity file with ssh key, optional | ||||
| 	Identity string `toml:"identity,omitempty"` | ||||
|  | ||||
| 	// isMachine describes if the remote destination is a machine. | ||||
| 	IsMachine bool `toml:"is_machine,omitempty"` | ||||
| } | ||||
|  | ||||
| // NewConfig creates a new Config. It starts with an empty config and, if | ||||
| @ -1235,32 +1238,32 @@ func Reload() (*Config, error) { | ||||
| 	return defConfig() | ||||
| } | ||||
|  | ||||
| func (c *Config) ActiveDestination() (uri, identity string, err error) { | ||||
| func (c *Config) ActiveDestination() (uri, identity string, machine bool, err error) { | ||||
| 	if uri, found := os.LookupEnv("CONTAINER_HOST"); found { | ||||
| 		if v, found := os.LookupEnv("CONTAINER_SSHKEY"); found { | ||||
| 			identity = v | ||||
| 		} | ||||
| 		return uri, identity, nil | ||||
| 		return uri, identity, false, nil | ||||
| 	} | ||||
| 	connEnv := os.Getenv("CONTAINER_CONNECTION") | ||||
| 	switch { | ||||
| 	case connEnv != "": | ||||
| 		d, found := c.Engine.ServiceDestinations[connEnv] | ||||
| 		if !found { | ||||
| 			return "", "", fmt.Errorf("environment variable CONTAINER_CONNECTION=%q service destination not found", connEnv) | ||||
| 			return "", "", false, fmt.Errorf("environment variable CONTAINER_CONNECTION=%q service destination not found", connEnv) | ||||
| 		} | ||||
| 		return d.URI, d.Identity, nil | ||||
| 		return d.URI, d.Identity, d.IsMachine, nil | ||||
|  | ||||
| 	case c.Engine.ActiveService != "": | ||||
| 		d, found := c.Engine.ServiceDestinations[c.Engine.ActiveService] | ||||
| 		if !found { | ||||
| 			return "", "", fmt.Errorf("%q service destination not found", c.Engine.ActiveService) | ||||
| 			return "", "", false, fmt.Errorf("%q service destination not found", c.Engine.ActiveService) | ||||
| 		} | ||||
| 		return d.URI, d.Identity, nil | ||||
| 		return d.URI, d.Identity, d.IsMachine, nil | ||||
| 	case c.Engine.RemoteURI != "": | ||||
| 		return c.Engine.RemoteURI, c.Engine.RemoteIdentity, nil | ||||
| 		return c.Engine.RemoteURI, c.Engine.RemoteIdentity, false, nil | ||||
| 	} | ||||
| 	return "", "", errors.New("no service destination configured") | ||||
| 	return "", "", false, errors.New("no service destination configured") | ||||
| } | ||||
|  | ||||
| var ( | ||||
|  | ||||
							
								
								
									
										83
									
								
								vendor/github.com/containers/common/pkg/ssh/connection_golang.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										83
									
								
								vendor/github.com/containers/common/pkg/ssh/connection_golang.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -3,6 +3,7 @@ package ssh | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net" | ||||
| @ -70,7 +71,7 @@ func golangConnectionDial(options ConnectionDialOptions) (*ConnectionDialReport, | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	cfg, err := ValidateAndConfigure(uri, options.Identity) | ||||
| 	cfg, err := ValidateAndConfigure(uri, options.Identity, options.InsecureIsMachineConnection) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -84,12 +85,15 @@ func golangConnectionDial(options ConnectionDialOptions) (*ConnectionDialReport, | ||||
| } | ||||
|  | ||||
| func golangConnectionExec(options ConnectionExecOptions) (*ConnectionExecReport, error) { | ||||
| 	if !strings.HasPrefix(options.Host, "ssh://") { | ||||
| 		options.Host = "ssh://" + options.Host | ||||
| 	} | ||||
| 	_, uri, err := Validate(options.User, options.Host, options.Port, options.Identity) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	cfg, err := ValidateAndConfigure(uri, options.Identity) | ||||
| 	cfg, err := ValidateAndConfigure(uri, options.Identity, false) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -111,11 +115,15 @@ func golangConnectionScp(options ConnectionScpOptions) (*ConnectionScpReport, er | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	// removed for parsing | ||||
| 	if !strings.HasPrefix(host, "ssh://") { | ||||
| 		host = "ssh://" + host | ||||
| 	} | ||||
| 	_, uri, err := Validate(options.User, host, options.Port, options.Identity) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	cfg, err := ValidateAndConfigure(uri, options.Identity) | ||||
| 	cfg, err := ValidateAndConfigure(uri, options.Identity, false) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @ -209,7 +217,7 @@ func GetUserInfo(uri *url.URL) (*url.Userinfo, error) { | ||||
| // ValidateAndConfigure will take a ssh url and an identity key (rsa and the like) and ensure the information given is valid | ||||
| // iden iden can be blank to mean no identity key | ||||
| // once the function validates the information it creates and returns an ssh.ClientConfig. | ||||
| func ValidateAndConfigure(uri *url.URL, iden string) (*ssh.ClientConfig, error) { | ||||
| func ValidateAndConfigure(uri *url.URL, iden string, insecureIsMachineConnection bool) (*ssh.ClientConfig, error) { | ||||
| 	var signers []ssh.Signer | ||||
| 	passwd, passwdSet := uri.User.Password() | ||||
| 	if iden != "" { // iden might be blank if coming from image scp or if no validation is needed | ||||
| @ -272,23 +280,61 @@ func ValidateAndConfigure(uri *url.URL, iden string) (*ssh.ClientConfig, error) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	keyFilePath := filepath.Join(homedir.Get(), ".ssh", "known_hosts") | ||||
| 	known, err := knownhosts.New(keyFilePath) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("creating host key callback function for %s: %w", keyFilePath, err) | ||||
|  | ||||
| 	var callback ssh.HostKeyCallback | ||||
| 	if insecureIsMachineConnection { | ||||
| 		callback = ssh.InsecureIgnoreHostKey() | ||||
| 	} else { | ||||
| 		callback = ssh.HostKeyCallback(func(host string, remote net.Addr, pubKey ssh.PublicKey) error { | ||||
| 			keyFilePath := filepath.Join(homedir.Get(), ".ssh", "known_hosts") | ||||
| 			known, err := knownhosts.New(keyFilePath) | ||||
| 			if err != nil { | ||||
| 				if errors.Is(err, os.ErrNotExist) { | ||||
| 					logrus.Warn("please create a known_hosts file. The next time this host is connected to, podman will add it to known_hosts") | ||||
| 					return nil | ||||
| 				} | ||||
| 				return err | ||||
| 			} | ||||
| 			// we need to check if there is an error from reading known hosts for this public key and if there is an error, what is it, and why is it happening? | ||||
| 			// if it is a key mismatch we want to error since we know the host using another key | ||||
| 			// however, if it is a general error not because of a known key, we want to add our key to the known_hosts file | ||||
| 			hErr := known(host, remote, pubKey) | ||||
| 			var keyErr *knownhosts.KeyError | ||||
| 			// if keyErr.Want is not empty, we are receiving a different key meaning the host is known but we are using the wrong key | ||||
| 			as := errors.As(hErr, &keyErr) | ||||
| 			switch { | ||||
| 			case as && len(keyErr.Want) > 0: | ||||
| 				logrus.Warnf("ssh host key mismatch for host %s, got key %s of type %s", host, ssh.FingerprintSHA256(pubKey), pubKey.Type()) | ||||
| 				return keyErr | ||||
| 			// if keyErr.Want is empty that just means we do not know this host yet, add it. | ||||
| 			case as && len(keyErr.Want) == 0: | ||||
| 				// write to known_hosts | ||||
| 				err := addKnownHostsEntry(host, pubKey) | ||||
| 				if err != nil { | ||||
| 					if os.IsNotExist(err) { | ||||
| 						logrus.Warn("podman will soon require a known_hosts file to function properly.") | ||||
| 						return nil | ||||
| 					} | ||||
| 					return err | ||||
| 				} | ||||
| 			case hErr != nil: | ||||
| 				return hErr | ||||
| 			} | ||||
| 			return nil | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	cfg := &ssh.ClientConfig{ | ||||
| 		User:            uri.User.Username(), | ||||
| 		Auth:            authMethods, | ||||
| 		HostKeyCallback: known, | ||||
| 		HostKeyCallback: callback, | ||||
| 		Timeout:         tick, | ||||
| 	} | ||||
| 	return cfg, nil | ||||
| } | ||||
|  | ||||
| func getUDS(uri *url.URL, iden string) (string, error) { | ||||
| 	cfg, err := ValidateAndConfigure(uri, iden) | ||||
| 	cfg, err := ValidateAndConfigure(uri, iden, false) | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("failed to validate: %w", err) | ||||
| 	} | ||||
| @ -324,3 +370,20 @@ func getUDS(uri *url.URL, iden string) (string, error) { | ||||
| 	} | ||||
| 	return info.Host.RemoteSocket.Path, nil | ||||
| } | ||||
|  | ||||
| // addKnownHostsEntry adds (host, pubKey) to user’s known_hosts. | ||||
| func addKnownHostsEntry(host string, pubKey ssh.PublicKey) error { | ||||
| 	hd := homedir.Get() | ||||
| 	known := filepath.Join(hd, ".ssh", "known_hosts") | ||||
| 	f, err := os.OpenFile(known, os.O_APPEND|os.O_WRONLY, 0o600) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
| 	l := knownhosts.Line([]string{host}, pubKey) | ||||
| 	if _, err = f.WriteString("\n" + l + "\n"); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	logrus.Infof("key %s added to %s", ssh.FingerprintSHA256(pubKey), known) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
							
								
								
									
										13
									
								
								vendor/github.com/containers/common/pkg/ssh/types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								vendor/github.com/containers/common/pkg/ssh/types.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -27,12 +27,13 @@ type ConnectionCreateOptions struct { | ||||
| } | ||||
|  | ||||
| type ConnectionDialOptions struct { | ||||
| 	Host     string | ||||
| 	Identity string | ||||
| 	User     *url.Userinfo | ||||
| 	Port     int | ||||
| 	Auth     []string | ||||
| 	Timeout  time.Duration | ||||
| 	Host                        string | ||||
| 	Identity                    string | ||||
| 	User                        *url.Userinfo | ||||
| 	Port                        int | ||||
| 	Auth                        []string | ||||
| 	Timeout                     time.Duration | ||||
| 	InsecureIsMachineConnection bool | ||||
| } | ||||
|  | ||||
| type ConnectionDialReport struct { | ||||
|  | ||||
							
								
								
									
										5
									
								
								vendor/github.com/containers/common/pkg/ssh/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								vendor/github.com/containers/common/pkg/ssh/utils.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -21,6 +21,7 @@ func Validate(user *url.Userinfo, path string, port int, identity string) (*conf | ||||
| 	if strings.Contains(path, "/run") { | ||||
| 		sock = strings.Split(path, "/run")[1] | ||||
| 	} | ||||
| 	// url.Parse NEEDS ssh://, if this ever fails or returns some nonsense, that is why. | ||||
| 	uri, err := url.Parse(path) | ||||
| 	if err != nil { | ||||
| 		return nil, nil, err | ||||
| @ -33,9 +34,9 @@ func Validate(user *url.Userinfo, path string, port int, identity string) (*conf | ||||
|  | ||||
| 	if uri.Port() == "" { | ||||
| 		if port != 0 { | ||||
| 			uri.Host = net.JoinHostPort(uri.Hostname(), strconv.Itoa(port)) | ||||
| 			uri.Host = net.JoinHostPort(uri.Host, strconv.Itoa(port)) | ||||
| 		} else { | ||||
| 			uri.Host = net.JoinHostPort(uri.Hostname(), "22") | ||||
| 			uri.Host = net.JoinHostPort(uri.Host, "22") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Charlie Doern
					Charlie Doern