mirror of
https://github.com/containers/podman.git
synced 2025-12-03 11:49:18 +08:00
implement new ssh interface into podman this completely redesigns the entire functionality of podman image scp, podman system connection add, and podman --remote. All references to golang.org/x/crypto/ssh have been moved to common as have native ssh/scp execs and the new usage of the sftp package. this PR adds a global flag, --ssh to podman which has two valid inputs `golang` and `native` where golang is the default. Users should not notice any difference in their everyday workflows if they continue using the golang option. UNLESS they have been using an improperly verified ssh key, this will now fail. This is because podman was incorrectly using the ssh callback method to IGNORE the ssh known hosts file which is very insecure and golang tells you not yo use this in production. The native paths allows for immense flexibility, with a new containers.conf field `SSH_CONFIG` that specifies a specific ssh config file to be used in all operations. Else the users ~/.ssh/config file will be used. podman --remote currently only uses the golang path, given its deep interconnection with dialing multiple clients and urls. My goal after this PR is to go back and abstract the idea of podman --remote from golang's dialed clients, as it should not be so intrinsically connected. Overall, this is a v1 of a long process of offering native ssh, and one that covers some good ground with podman system connection add and podman image scp. Signed-off-by: Charlie Doern <cdoern@redhat.com>
259 lines
6.5 KiB
Go
259 lines
6.5 KiB
Go
// Package sftp implements the SSH File Transfer Protocol as described in
|
|
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02
|
|
package sftp
|
|
|
|
import (
|
|
"fmt"
|
|
)
|
|
|
|
const (
|
|
sshFxpInit = 1
|
|
sshFxpVersion = 2
|
|
sshFxpOpen = 3
|
|
sshFxpClose = 4
|
|
sshFxpRead = 5
|
|
sshFxpWrite = 6
|
|
sshFxpLstat = 7
|
|
sshFxpFstat = 8
|
|
sshFxpSetstat = 9
|
|
sshFxpFsetstat = 10
|
|
sshFxpOpendir = 11
|
|
sshFxpReaddir = 12
|
|
sshFxpRemove = 13
|
|
sshFxpMkdir = 14
|
|
sshFxpRmdir = 15
|
|
sshFxpRealpath = 16
|
|
sshFxpStat = 17
|
|
sshFxpRename = 18
|
|
sshFxpReadlink = 19
|
|
sshFxpSymlink = 20
|
|
sshFxpStatus = 101
|
|
sshFxpHandle = 102
|
|
sshFxpData = 103
|
|
sshFxpName = 104
|
|
sshFxpAttrs = 105
|
|
sshFxpExtended = 200
|
|
sshFxpExtendedReply = 201
|
|
)
|
|
|
|
const (
|
|
sshFxOk = 0
|
|
sshFxEOF = 1
|
|
sshFxNoSuchFile = 2
|
|
sshFxPermissionDenied = 3
|
|
sshFxFailure = 4
|
|
sshFxBadMessage = 5
|
|
sshFxNoConnection = 6
|
|
sshFxConnectionLost = 7
|
|
sshFxOPUnsupported = 8
|
|
|
|
// see draft-ietf-secsh-filexfer-13
|
|
// https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1
|
|
sshFxInvalidHandle = 9
|
|
sshFxNoSuchPath = 10
|
|
sshFxFileAlreadyExists = 11
|
|
sshFxWriteProtect = 12
|
|
sshFxNoMedia = 13
|
|
sshFxNoSpaceOnFilesystem = 14
|
|
sshFxQuotaExceeded = 15
|
|
sshFxUnknownPrincipal = 16
|
|
sshFxLockConflict = 17
|
|
sshFxDirNotEmpty = 18
|
|
sshFxNotADirectory = 19
|
|
sshFxInvalidFilename = 20
|
|
sshFxLinkLoop = 21
|
|
sshFxCannotDelete = 22
|
|
sshFxInvalidParameter = 23
|
|
sshFxFileIsADirectory = 24
|
|
sshFxByteRangeLockConflict = 25
|
|
sshFxByteRangeLockRefused = 26
|
|
sshFxDeletePending = 27
|
|
sshFxFileCorrupt = 28
|
|
sshFxOwnerInvalid = 29
|
|
sshFxGroupInvalid = 30
|
|
sshFxNoMatchingByteRangeLock = 31
|
|
)
|
|
|
|
const (
|
|
sshFxfRead = 0x00000001
|
|
sshFxfWrite = 0x00000002
|
|
sshFxfAppend = 0x00000004
|
|
sshFxfCreat = 0x00000008
|
|
sshFxfTrunc = 0x00000010
|
|
sshFxfExcl = 0x00000020
|
|
)
|
|
|
|
var (
|
|
// supportedSFTPExtensions defines the supported extensions
|
|
supportedSFTPExtensions = []sshExtensionPair{
|
|
{"hardlink@openssh.com", "1"},
|
|
{"posix-rename@openssh.com", "1"},
|
|
{"statvfs@openssh.com", "2"},
|
|
}
|
|
sftpExtensions = supportedSFTPExtensions
|
|
)
|
|
|
|
type fxp uint8
|
|
|
|
func (f fxp) String() string {
|
|
switch f {
|
|
case sshFxpInit:
|
|
return "SSH_FXP_INIT"
|
|
case sshFxpVersion:
|
|
return "SSH_FXP_VERSION"
|
|
case sshFxpOpen:
|
|
return "SSH_FXP_OPEN"
|
|
case sshFxpClose:
|
|
return "SSH_FXP_CLOSE"
|
|
case sshFxpRead:
|
|
return "SSH_FXP_READ"
|
|
case sshFxpWrite:
|
|
return "SSH_FXP_WRITE"
|
|
case sshFxpLstat:
|
|
return "SSH_FXP_LSTAT"
|
|
case sshFxpFstat:
|
|
return "SSH_FXP_FSTAT"
|
|
case sshFxpSetstat:
|
|
return "SSH_FXP_SETSTAT"
|
|
case sshFxpFsetstat:
|
|
return "SSH_FXP_FSETSTAT"
|
|
case sshFxpOpendir:
|
|
return "SSH_FXP_OPENDIR"
|
|
case sshFxpReaddir:
|
|
return "SSH_FXP_READDIR"
|
|
case sshFxpRemove:
|
|
return "SSH_FXP_REMOVE"
|
|
case sshFxpMkdir:
|
|
return "SSH_FXP_MKDIR"
|
|
case sshFxpRmdir:
|
|
return "SSH_FXP_RMDIR"
|
|
case sshFxpRealpath:
|
|
return "SSH_FXP_REALPATH"
|
|
case sshFxpStat:
|
|
return "SSH_FXP_STAT"
|
|
case sshFxpRename:
|
|
return "SSH_FXP_RENAME"
|
|
case sshFxpReadlink:
|
|
return "SSH_FXP_READLINK"
|
|
case sshFxpSymlink:
|
|
return "SSH_FXP_SYMLINK"
|
|
case sshFxpStatus:
|
|
return "SSH_FXP_STATUS"
|
|
case sshFxpHandle:
|
|
return "SSH_FXP_HANDLE"
|
|
case sshFxpData:
|
|
return "SSH_FXP_DATA"
|
|
case sshFxpName:
|
|
return "SSH_FXP_NAME"
|
|
case sshFxpAttrs:
|
|
return "SSH_FXP_ATTRS"
|
|
case sshFxpExtended:
|
|
return "SSH_FXP_EXTENDED"
|
|
case sshFxpExtendedReply:
|
|
return "SSH_FXP_EXTENDED_REPLY"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
type fx uint8
|
|
|
|
func (f fx) String() string {
|
|
switch f {
|
|
case sshFxOk:
|
|
return "SSH_FX_OK"
|
|
case sshFxEOF:
|
|
return "SSH_FX_EOF"
|
|
case sshFxNoSuchFile:
|
|
return "SSH_FX_NO_SUCH_FILE"
|
|
case sshFxPermissionDenied:
|
|
return "SSH_FX_PERMISSION_DENIED"
|
|
case sshFxFailure:
|
|
return "SSH_FX_FAILURE"
|
|
case sshFxBadMessage:
|
|
return "SSH_FX_BAD_MESSAGE"
|
|
case sshFxNoConnection:
|
|
return "SSH_FX_NO_CONNECTION"
|
|
case sshFxConnectionLost:
|
|
return "SSH_FX_CONNECTION_LOST"
|
|
case sshFxOPUnsupported:
|
|
return "SSH_FX_OP_UNSUPPORTED"
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|
|
|
|
type unexpectedPacketErr struct {
|
|
want, got uint8
|
|
}
|
|
|
|
func (u *unexpectedPacketErr) Error() string {
|
|
return fmt.Sprintf("sftp: unexpected packet: want %v, got %v", fxp(u.want), fxp(u.got))
|
|
}
|
|
|
|
func unimplementedPacketErr(u uint8) error {
|
|
return fmt.Errorf("sftp: unimplemented packet type: got %v", fxp(u))
|
|
}
|
|
|
|
type unexpectedIDErr struct{ want, got uint32 }
|
|
|
|
func (u *unexpectedIDErr) Error() string {
|
|
return fmt.Sprintf("sftp: unexpected id: want %d, got %d", u.want, u.got)
|
|
}
|
|
|
|
func unimplementedSeekWhence(whence int) error {
|
|
return fmt.Errorf("sftp: unimplemented seek whence %d", whence)
|
|
}
|
|
|
|
func unexpectedCount(want, got uint32) error {
|
|
return fmt.Errorf("sftp: unexpected count: want %d, got %d", want, got)
|
|
}
|
|
|
|
type unexpectedVersionErr struct{ want, got uint32 }
|
|
|
|
func (u *unexpectedVersionErr) Error() string {
|
|
return fmt.Sprintf("sftp: unexpected server version: want %v, got %v", u.want, u.got)
|
|
}
|
|
|
|
// A StatusError is returned when an SFTP operation fails, and provides
|
|
// additional information about the failure.
|
|
type StatusError struct {
|
|
Code uint32
|
|
msg, lang string
|
|
}
|
|
|
|
func (s *StatusError) Error() string {
|
|
return fmt.Sprintf("sftp: %q (%v)", s.msg, fx(s.Code))
|
|
}
|
|
|
|
// FxCode returns the error code typed to match against the exported codes
|
|
func (s *StatusError) FxCode() fxerr {
|
|
return fxerr(s.Code)
|
|
}
|
|
|
|
func getSupportedExtensionByName(extensionName string) (sshExtensionPair, error) {
|
|
for _, supportedExtension := range supportedSFTPExtensions {
|
|
if supportedExtension.Name == extensionName {
|
|
return supportedExtension, nil
|
|
}
|
|
}
|
|
return sshExtensionPair{}, fmt.Errorf("unsupported extension: %s", extensionName)
|
|
}
|
|
|
|
// SetSFTPExtensions allows to customize the supported server extensions.
|
|
// See the variable supportedSFTPExtensions for supported extensions.
|
|
// This method accepts a slice of sshExtensionPair names for example 'hardlink@openssh.com'.
|
|
// If an invalid extension is given an error will be returned and nothing will be changed
|
|
func SetSFTPExtensions(extensions ...string) error {
|
|
tempExtensions := []sshExtensionPair{}
|
|
for _, extension := range extensions {
|
|
sftpExtension, err := getSupportedExtensionByName(extension)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tempExtensions = append(tempExtensions, sftpExtension)
|
|
}
|
|
sftpExtensions = tempExtensions
|
|
return nil
|
|
}
|