mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-08-05 00:15:44 +00:00
Merge pull request #28336 from l0rd/import_native_ca
Import host trusted certificates into the guest machine - Windows part
This commit is contained in:
commit
3279767614
24 changed files with 981 additions and 66 deletions
|
|
@ -5,9 +5,6 @@ package machine
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"github.com/containers/podman/v6/cmd/podman/registry"
|
||||
"github.com/containers/podman/v6/libpod/events"
|
||||
|
|
@ -15,17 +12,13 @@ import (
|
|||
"github.com/containers/podman/v6/pkg/machine"
|
||||
"github.com/containers/podman/v6/pkg/machine/define"
|
||||
"github.com/containers/podman/v6/pkg/machine/shim"
|
||||
"github.com/containers/podman/v6/pkg/machine/vmconfigs"
|
||||
"github.com/containers/podman/v6/pkg/specgen"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type cpOptions struct {
|
||||
Quiet bool
|
||||
Machine *vmconfigs.MachineConfig
|
||||
IsSrc bool
|
||||
SrcPath string
|
||||
DestPath string
|
||||
Quiet bool
|
||||
IsSrc bool
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -95,11 +88,18 @@ func cp(_ *cobra.Command, args []string) error {
|
|||
return fmt.Errorf("vm %q is not running", mc.Name)
|
||||
}
|
||||
|
||||
cpOpts.Machine = mc
|
||||
cpOpts.SrcPath = srcPath
|
||||
cpOpts.DestPath = destPath
|
||||
|
||||
err = localhostSSHCopy(&cpOpts)
|
||||
sshConfig := mc.SSH
|
||||
username := sshConfig.RemoteUsername
|
||||
if mc.HostUser.Rootful {
|
||||
username = "root"
|
||||
}
|
||||
err = machine.LocalhostSSHCopy(username,
|
||||
sshConfig.IdentityPath,
|
||||
sshConfig.Port,
|
||||
srcPath,
|
||||
destPath,
|
||||
cpOpts.IsSrc,
|
||||
cpOpts.Quiet)
|
||||
if err != nil {
|
||||
return fmt.Errorf("copy failed: %s", err.Error())
|
||||
}
|
||||
|
|
@ -109,38 +109,6 @@ func cp(_ *cobra.Command, args []string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// localhostSSHCopy uses scp to copy files from/to a localhost machine using ssh.
|
||||
func localhostSSHCopy(opts *cpOptions) error {
|
||||
srcPath := opts.SrcPath
|
||||
destPath := opts.DestPath
|
||||
sshConfig := opts.Machine.SSH
|
||||
|
||||
username := sshConfig.RemoteUsername
|
||||
if cpOpts.Machine.HostUser.Rootful {
|
||||
username = "root"
|
||||
}
|
||||
username += "@localhost:"
|
||||
|
||||
if opts.IsSrc {
|
||||
srcPath = username + srcPath
|
||||
} else {
|
||||
destPath = username + destPath
|
||||
}
|
||||
|
||||
args := append(
|
||||
machine.LocalhostSSHArgs(), // Warning: This MUST NOT be generalized to allow communication over untrusted networks.
|
||||
"-r",
|
||||
"-i", sshConfig.IdentityPath,
|
||||
"-P", strconv.Itoa(sshConfig.Port),
|
||||
srcPath, destPath)
|
||||
cmd := exec.Command("scp", args...)
|
||||
if !opts.Quiet {
|
||||
cmd.Stdout = os.Stdout
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func resolveMachineName(srcMachine, destMachine string) (string, error) {
|
||||
if len(srcMachine) > 0 && len(destMachine) > 0 {
|
||||
return "", errors.New("copying between two machines is unsupported")
|
||||
|
|
|
|||
|
|
@ -170,6 +170,9 @@ func init() {
|
|||
|
||||
setDefaultConnectionFlagName := "update-connection"
|
||||
flags.BoolVarP(&setDefaultSystemConn, setDefaultConnectionFlagName, "u", false, "Set default system connection for this machine")
|
||||
|
||||
importNativeCaFlagName := "import-native-ca"
|
||||
flags.BoolVar(&initOpts.ImportNativeCA, importNativeCaFlagName, cfg.ContainersConfDefaultsRO.Machine.ImportNativeCA, "Import the host trusted CA certificates into the machine")
|
||||
}
|
||||
|
||||
func initMachine(cmd *cobra.Command, args []string) error {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ type SetFlags struct {
|
|||
Rootful bool
|
||||
UserModeNetworking bool
|
||||
USBs []string
|
||||
ImportNativeCA bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
|
@ -81,6 +82,10 @@ func init() {
|
|||
userModeNetFlagName := "user-mode-networking"
|
||||
flags.BoolVar(&setFlags.UserModeNetworking, userModeNetFlagName, false, // defaults not-relevant due to use of Changed()
|
||||
"Whether this machine should use user-mode networking, routing traffic through a host user-space process")
|
||||
|
||||
importNativeCaFlagName := "import-native-ca"
|
||||
flags.BoolVar(&setFlags.ImportNativeCA, importNativeCaFlagName, false, // defaults not-relevant due to use of Changed()
|
||||
"Import the host trusted CA certificates into the machine")
|
||||
}
|
||||
|
||||
func setMachine(cmd *cobra.Command, args []string) error {
|
||||
|
|
@ -120,6 +125,9 @@ func setMachine(cmd *cobra.Command, args []string) error {
|
|||
if cmd.Flags().Changed("usb") {
|
||||
setOpts.USBs = &setFlags.USBs
|
||||
}
|
||||
if cmd.Flags().Changed("import-native-ca") {
|
||||
setOpts.ImportNativeCA = &setFlags.ImportNativeCA
|
||||
}
|
||||
|
||||
// At this point, we have the known changed information, etc
|
||||
// Walk through changes to the providers if they need them
|
||||
|
|
|
|||
|
|
@ -81,6 +81,13 @@ Registry target must be in the form of `docker://registry/repo/image:version`.
|
|||
|
||||
Note: Only images provided by podman will be supported.
|
||||
|
||||
#### **--import-native-ca**
|
||||
|
||||
Import the host's trusted CA certificates into the machine.
|
||||
When set to true, the certificates from the host system are imported during machine startup.
|
||||
This allows containers running in the machine to trust the same Certificate Authorities
|
||||
as the host OS. The default value is `false`.
|
||||
|
||||
#### **--memory**, **-m**=*number*
|
||||
|
||||
Memory (in MiB). Note: 1024MiB = 1GiB.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ Can only be increased. Only supported for QEMU machines.
|
|||
|
||||
Print usage statement.
|
||||
|
||||
#### **--import-native-ca**
|
||||
|
||||
Import the host's trusted CA certificates into the machine.
|
||||
When set to true, the certificates from the host system are imported during machine startup.
|
||||
This allows containers running in the machine to trust the same Certificate Authorities
|
||||
as the host OS. The default value is `false`.
|
||||
|
||||
#### **--memory**, **-m**=*number*
|
||||
|
||||
Memory (in MB).
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"io/fs"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -81,12 +82,12 @@ func getMachineMountsAndVMType(connectionURI string, parsedConnection *url.URL)
|
|||
return mc.Mounts, machineProvider.VMType(), nil
|
||||
}
|
||||
|
||||
// isPathAvailableOnMachine checks if a local path is available on the machine through mounted directories.
|
||||
// IsPathAvailableOnMachine checks if a local path is available on the machine through mounted directories.
|
||||
// If the path is available, it returns a LocalAPIMap with the corresponding remote path.
|
||||
func isPathAvailableOnMachine(mounts []*vmconfigs.Mount, vmType define.VMType, path string) (*LocalAPIMap, bool) {
|
||||
pathABS, err := filepath.Abs(path)
|
||||
func IsPathAvailableOnMachine(mounts []*vmconfigs.Mount, vmType define.VMType, localPath string) (*LocalAPIMap, bool) {
|
||||
pathABS, err := filepath.Abs(localPath)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to get absolute path for %s: %v", path, err)
|
||||
logrus.Debugf("Failed to get absolute path for %q: %v", localPath, err)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
|
@ -107,19 +108,18 @@ func isPathAvailableOnMachine(mounts []*vmconfigs.Mount, vmType define.VMType, p
|
|||
|
||||
for _, mount := range mounts {
|
||||
mountSource := filepath.Clean(mount.Source)
|
||||
logrus.Debugf("looking if %q is in mount %q (with target %q)", pathABS, mountSource, mount.Target)
|
||||
relPath, err := filepath.Rel(mountSource, pathABS)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to get relative path: %v", err)
|
||||
continue
|
||||
} else {
|
||||
logrus.Debugf("Yes, filepath.Rel returned %q", relPath)
|
||||
}
|
||||
// If relPath starts with ".." or is absolute, pathABS is not under mountSource
|
||||
if relPath == "." || (!strings.HasPrefix(relPath, "..") && !filepath.IsAbs(relPath)) {
|
||||
target := filepath.Join(mount.Target, relPath)
|
||||
converted_path, err := specgen.ConvertWinMountPath(target)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed to convert Windows mount path: %v", err)
|
||||
return nil, false
|
||||
}
|
||||
// Target is a Linux path, we should use path.Join rather than filepath.Join
|
||||
converted_path := path.Join(mount.Target, filepath.ToSlash(relPath))
|
||||
logrus.Debugf("Converted client path: %q", converted_path)
|
||||
return &LocalAPIMap{
|
||||
ClientPath: pathABS,
|
||||
|
|
@ -134,7 +134,7 @@ func isPathAvailableOnMachine(mounts []*vmconfigs.Mount, vmType define.VMType, p
|
|||
// on any currently running machine. It combines machine inspection and path checking.
|
||||
func CheckPathOnRunningMachine(ctx context.Context, path string) (*LocalAPIMap, bool) {
|
||||
if err := fileutils.Exists(path); errors.Is(err, fs.ErrNotExist) {
|
||||
logrus.Debugf("Path %s does not exist locally, skipping machine check", path)
|
||||
logrus.Debugf("Path %q does not exist locally, skipping machine check", path)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
|
|
@ -155,7 +155,7 @@ func CheckPathOnRunningMachine(ctx context.Context, path string) (*LocalAPIMap,
|
|||
return nil, false
|
||||
}
|
||||
|
||||
return isPathAvailableOnMachine(mounts, vmType, path)
|
||||
return IsPathAvailableOnMachine(mounts, vmType, path)
|
||||
}
|
||||
|
||||
// CheckIfImageBuildPathsOnRunningMachine checks if the build context directory and all specified
|
||||
|
|
@ -181,12 +181,12 @@ func CheckIfImageBuildPathsOnRunningMachine(ctx context.Context, containerFiles
|
|||
|
||||
// Context directory
|
||||
if err := fileutils.Lexists(options.ContextDirectory); errors.Is(err, fs.ErrNotExist) {
|
||||
logrus.Debugf("Path %s does not exist locally, skipping machine check", options.ContextDirectory)
|
||||
logrus.Debugf("Path %q does not exist locally, skipping machine check", options.ContextDirectory)
|
||||
return nil, options, false
|
||||
}
|
||||
mapping, found := isPathAvailableOnMachine(mounts, vmType, options.ContextDirectory)
|
||||
mapping, found := IsPathAvailableOnMachine(mounts, vmType, options.ContextDirectory)
|
||||
if !found {
|
||||
logrus.Debugf("Path %s is not available on the running machine", options.ContextDirectory)
|
||||
logrus.Debugf("Path %q is not available on the running machine", options.ContextDirectory)
|
||||
return nil, options, false
|
||||
}
|
||||
options.ContextDirectory = mapping.RemotePath
|
||||
|
|
@ -208,9 +208,9 @@ func CheckIfImageBuildPathsOnRunningMachine(ctx context.Context, containerFiles
|
|||
continue
|
||||
}
|
||||
|
||||
mapping, found := isPathAvailableOnMachine(mounts, vmType, containerFile)
|
||||
mapping, found := IsPathAvailableOnMachine(mounts, vmType, containerFile)
|
||||
if !found {
|
||||
logrus.Debugf("Path %s is not available on the running machine", containerFile)
|
||||
logrus.Debugf("Path %q is not available on the running machine", containerFile)
|
||||
return nil, options, false
|
||||
}
|
||||
translatedContainerFiles = append(translatedContainerFiles, mapping.RemotePath)
|
||||
|
|
@ -223,12 +223,12 @@ func CheckIfImageBuildPathsOnRunningMachine(ctx context.Context, containerFiles
|
|||
continue
|
||||
default:
|
||||
if err := fileutils.Lexists(context.Value); errors.Is(err, fs.ErrNotExist) {
|
||||
logrus.Debugf("Path %s does not exist locally, skipping machine check", context.Value)
|
||||
logrus.Debugf("Path %q does not exist locally, skipping machine check", context.Value)
|
||||
return nil, options, false
|
||||
}
|
||||
mapping, found := isPathAvailableOnMachine(mounts, vmType, context.Value)
|
||||
mapping, found := IsPathAvailableOnMachine(mounts, vmType, context.Value)
|
||||
if !found {
|
||||
logrus.Debugf("Path %s is not available on the running machine", context.Value)
|
||||
logrus.Debugf("Path %q is not available on the running machine", context.Value)
|
||||
return nil, options, false
|
||||
}
|
||||
context.Value = mapping.RemotePath
|
||||
|
|
|
|||
157
internal/localapi/utils_test.go
Normal file
157
internal/localapi/utils_test.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
//go:build linux || darwin || freebsd
|
||||
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containers/podman/v6/pkg/machine/define"
|
||||
"github.com/containers/podman/v6/pkg/machine/vmconfigs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsPathAvailableOnMachine_Unix(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mounts []*vmconfigs.Mount
|
||||
vmType define.VMType
|
||||
localPath string
|
||||
wantRemotePath string
|
||||
wantFound bool
|
||||
}{
|
||||
{
|
||||
name: "QEMU - Path within mount",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "/home/test",
|
||||
Target: "/home/user",
|
||||
},
|
||||
},
|
||||
vmType: define.QemuVirt,
|
||||
localPath: "/home/test/project/file.txt",
|
||||
wantRemotePath: "/home/user/project/file.txt",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "QEMU - Path exactly at mount point",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "/home/test",
|
||||
Target: "/mnt/host",
|
||||
},
|
||||
},
|
||||
vmType: define.QemuVirt,
|
||||
localPath: "/home/test",
|
||||
wantRemotePath: "/mnt/host",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "QEMU - Path not in any mount",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "/home/test",
|
||||
Target: "/mnt/test",
|
||||
},
|
||||
},
|
||||
vmType: define.QemuVirt,
|
||||
localPath: "/home/another/file.txt",
|
||||
wantFound: false,
|
||||
},
|
||||
{
|
||||
name: "QEMU - Multiple mounts, path in second",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "/home/first",
|
||||
Target: "/mnt/first",
|
||||
},
|
||||
{
|
||||
Source: "/home/second",
|
||||
Target: "/mnt/second",
|
||||
},
|
||||
},
|
||||
vmType: define.QemuVirt,
|
||||
localPath: "/home/second/data",
|
||||
wantRemotePath: "/mnt/second/data",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "QEMU - Nested subdirectory in mount",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "/home/test",
|
||||
Target: "/home/user",
|
||||
},
|
||||
},
|
||||
vmType: define.QemuVirt,
|
||||
localPath: "/home/test/deep/nested/path/file.go",
|
||||
wantRemotePath: "/home/user/deep/nested/path/file.go",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "QEMU - Empty mounts list",
|
||||
mounts: []*vmconfigs.Mount{},
|
||||
vmType: define.QemuVirt,
|
||||
localPath: "/home/test/file.txt",
|
||||
wantFound: false,
|
||||
},
|
||||
{
|
||||
name: "LibKrun - Path within mount",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "/Users/developer/projects",
|
||||
Target: "/mnt/projects",
|
||||
},
|
||||
},
|
||||
vmType: define.LibKrun,
|
||||
localPath: "/Users/developer/projects/myapp/src/main.go",
|
||||
wantRemotePath: "/mnt/projects/myapp/src/main.go",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "AppleHV - Path within mount",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "/Users/developer",
|
||||
Target: "/home/developer",
|
||||
},
|
||||
},
|
||||
vmType: define.AppleHvVirt,
|
||||
localPath: "/Users/developer/Documents/code",
|
||||
wantRemotePath: "/home/developer/Documents/code",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "Multiple mounts - first match wins",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "/home",
|
||||
Target: "/mnt/home",
|
||||
},
|
||||
{
|
||||
Source: "/home/user",
|
||||
Target: "/mnt/user",
|
||||
},
|
||||
},
|
||||
vmType: define.QemuVirt,
|
||||
localPath: "/home/user/file.txt",
|
||||
wantRemotePath: "/mnt/home/user/file.txt",
|
||||
wantFound: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, found := IsPathAvailableOnMachine(tt.mounts, tt.vmType, tt.localPath)
|
||||
|
||||
assert.Equal(t, tt.wantFound, found, "found flag mismatch")
|
||||
|
||||
if tt.wantFound {
|
||||
require.NotNil(t, result, "result should not be nil when found is true")
|
||||
assert.Equal(t, tt.wantRemotePath, result.RemotePath, "RemotePath mismatch")
|
||||
} else {
|
||||
assert.Nil(t, result, "result should be nil when found is false")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
168
internal/localapi/utils_windows_test.go
Normal file
168
internal/localapi/utils_windows_test.go
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
//go:build windows
|
||||
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/containers/podman/v6/pkg/machine/define"
|
||||
"github.com/containers/podman/v6/pkg/machine/vmconfigs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestIsPathAvailableOnMachine_Windows(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mounts []*vmconfigs.Mount
|
||||
vmType define.VMType
|
||||
localPath string
|
||||
wantRemotePath string
|
||||
wantFound bool
|
||||
}{
|
||||
{
|
||||
name: "WSL - Windows C drive path",
|
||||
mounts: nil, // WSL doesn't use mounts
|
||||
vmType: define.WSLVirt,
|
||||
localPath: "C:\\Users\\test\\project",
|
||||
wantRemotePath: "/mnt/c/Users/test/project",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "WSL - Windows D drive path",
|
||||
mounts: nil,
|
||||
vmType: define.WSLVirt,
|
||||
localPath: "D:\\data\\workspace",
|
||||
wantRemotePath: "/mnt/d/data/workspace",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "HyperV - Path within mount",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "C:\\Users\\test",
|
||||
Target: "/home/user",
|
||||
},
|
||||
},
|
||||
vmType: define.HyperVVirt,
|
||||
localPath: "C:\\Users\\test\\project\\file.txt",
|
||||
wantRemotePath: "/home/user/project/file.txt",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "HyperV - D drive path",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "D:\\Users\\test",
|
||||
Target: "/home/user",
|
||||
},
|
||||
},
|
||||
vmType: define.HyperVVirt,
|
||||
localPath: "D:\\Users\\test\\project\\file.txt",
|
||||
wantRemotePath: "/home/user/project/file.txt",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "HyperV - Path exactly at mount point",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "C:\\Users\\test",
|
||||
Target: "/mnt/host",
|
||||
},
|
||||
},
|
||||
vmType: define.HyperVVirt,
|
||||
localPath: "C:\\Users\\test",
|
||||
wantRemotePath: "/mnt/host",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "HyperV - Path not in any mount",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "C:\\Users\\test",
|
||||
Target: "/mnt/test",
|
||||
},
|
||||
},
|
||||
vmType: define.HyperVVirt,
|
||||
localPath: "C:\\Users\\another\\file.txt",
|
||||
wantFound: false,
|
||||
},
|
||||
{
|
||||
name: "HyperV - Path in different drive",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "C:\\Users\\test",
|
||||
Target: "/mnt/test",
|
||||
},
|
||||
},
|
||||
vmType: define.HyperVVirt,
|
||||
localPath: "D:\\Users\\test\\file.txt",
|
||||
wantFound: false,
|
||||
},
|
||||
{
|
||||
name: "HyperV - Multiple mounts, path in second",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "C:\\Users\\first",
|
||||
Target: "/mnt/first",
|
||||
},
|
||||
{
|
||||
Source: "C:\\Users\\second",
|
||||
Target: "/mnt/second",
|
||||
},
|
||||
},
|
||||
vmType: define.HyperVVirt,
|
||||
localPath: "C:\\Users\\second\\data",
|
||||
wantRemotePath: "/mnt/second/data",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "HyperV - Nested subdirectory in mount",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "C:\\Users\\test",
|
||||
Target: "/home/user",
|
||||
},
|
||||
},
|
||||
vmType: define.HyperVVirt,
|
||||
localPath: "C:\\Users\\test\\deep\\nested\\path\\file.go",
|
||||
wantRemotePath: "/home/user/deep/nested/path/file.go",
|
||||
wantFound: true,
|
||||
},
|
||||
{
|
||||
name: "HyperV - Empty mounts list",
|
||||
mounts: []*vmconfigs.Mount{},
|
||||
vmType: define.HyperVVirt,
|
||||
localPath: "C:\\Users\\test\\file.txt",
|
||||
wantFound: false,
|
||||
},
|
||||
{
|
||||
name: "HyperV - Path with mixed case drive letter",
|
||||
mounts: []*vmconfigs.Mount{
|
||||
{
|
||||
Source: "C:\\Projects",
|
||||
Target: "/home/projects",
|
||||
},
|
||||
},
|
||||
vmType: define.HyperVVirt,
|
||||
localPath: "c:\\Projects\\myapp",
|
||||
wantRemotePath: "/home/projects/myapp",
|
||||
wantFound: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, found := IsPathAvailableOnMachine(tt.mounts, tt.vmType, tt.localPath)
|
||||
|
||||
assert.Equal(t, tt.wantFound, found, "found flag mismatch")
|
||||
|
||||
if tt.wantFound {
|
||||
require.NotNil(t, result, "result should not be nil when found is true")
|
||||
assert.Equal(t, tt.wantRemotePath, result.RemotePath, "RemotePath mismatch")
|
||||
} else {
|
||||
assert.Nil(t, result, "result should be nil when found is false")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
165
pkg/machine/certificates/certificates.go
Normal file
165
pkg/machine/certificates/certificates.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containers/podman/v6/internal/localapi"
|
||||
"github.com/containers/podman/v6/pkg/machine"
|
||||
"github.com/containers/podman/v6/pkg/machine/define"
|
||||
"github.com/containers/podman/v6/pkg/machine/vmconfigs"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
// CertificatesFileName is the name of the PEM file containing the host
|
||||
// trusted CA certificates
|
||||
CertificatesFileName = "host-ca-certs.pem"
|
||||
// GuestAnchorsPath is the Fedora folder where the command `update-ca-trust`
|
||||
// looks for users-defined trusted certificates
|
||||
GuestAnchorsPath = "/etc/pki/ca-trust/source/anchors"
|
||||
// UpdateCATrustCommand is the Fedora command to update the system store
|
||||
UpdateCATrustCommand = "update-ca-trust"
|
||||
)
|
||||
|
||||
// ImportNativeCertificates imports the host's trusted CA certificates into the
|
||||
// guest machine OS trust store. To do that it:
|
||||
// - extract all certificates from the host trust store
|
||||
// - export all the certificates in a single file in the machine data folder
|
||||
// - check if the file is already mounted in the guest or transfer via SCP
|
||||
// - update the guest trust store to include the certificates
|
||||
func ImportNativeCertificates(mc *vmconfigs.MachineConfig, vmType define.VMType) error {
|
||||
logrus.Debugf("Importing the host CA certificates into machine %q", mc.Name)
|
||||
// Extract certificates from the host system store
|
||||
certs := deduplicateCertificates(extractHostCertificates())
|
||||
if len(certs) == 0 {
|
||||
logrus.Debugf("No native CA certificates found to import")
|
||||
return nil
|
||||
}
|
||||
logrus.Debugf("Extracted %d host certificates from the system store", len(certs))
|
||||
// Save the certificates to a PEM file in the machine data folder
|
||||
certFilePath, err := saveCertificatesToFile(mc, certs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create certs file in machine data folder: %s", err)
|
||||
}
|
||||
logrus.Debugf("Saved the certificates to file %q", certFilePath)
|
||||
// Copy or transfer via SCP the file with the certificates to the anchors
|
||||
// folder in the guest
|
||||
err = copyOrTransferFileToGuestAnchorsFolder(mc, vmType, certFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to transfer or copy the certs file in the guest anchors folder: %s", err)
|
||||
}
|
||||
// Update the CA trust list
|
||||
if err := runUpdateCATrustInGuest(mc); err != nil {
|
||||
return fmt.Errorf("failed to update CA trust in guest: %w", err)
|
||||
}
|
||||
logrus.Debugf("Successfully imported the host trusted certificates into machine %q", mc.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// saveCertificatesToFile saves the certificates to a PEM file in the machine data directory.
|
||||
func saveCertificatesToFile(mc *vmconfigs.MachineConfig, certs []*x509.Certificate) (string, error) {
|
||||
dataDir, err := mc.DataDir()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get machine data directory: %w", err)
|
||||
}
|
||||
// Create the directory if it doesn't exist
|
||||
certFileDirPath := filepath.Join(dataDir.GetPath(), mc.Name)
|
||||
if err = os.MkdirAll(certFileDirPath, 0o755); err != nil {
|
||||
return "", fmt.Errorf("failed to create certificate directory: %w", err)
|
||||
}
|
||||
// Save certificates to PEM file in the directory
|
||||
certFilePath := filepath.Join(certFileDirPath, CertificatesFileName)
|
||||
if err := saveCertificatesToPEM(certs, certFilePath); err != nil {
|
||||
return "", fmt.Errorf("failed to save certificates to PEM file: %w", err)
|
||||
}
|
||||
return certFilePath, nil
|
||||
}
|
||||
|
||||
// copyOrTransferFileToGuestAnchorsFolder copies or transfers hostFilePath to
|
||||
// the guest anchor folder, depending on whether the file is already mounted in
|
||||
// the guest or not.
|
||||
func copyOrTransferFileToGuestAnchorsFolder(mc *vmconfigs.MachineConfig, vmType define.VMType, hostFilePath string) error {
|
||||
// Look for the file in the machine mounts
|
||||
mounts := mc.Mounts
|
||||
if localMap, ok := localapi.IsPathAvailableOnMachine(mounts, vmType, hostFilePath); ok {
|
||||
// Copy the mounted file to the guest anchors folder
|
||||
remotePath := localMap.RemotePath
|
||||
logrus.Debugf("The certificates file is already mounted in the guest (%q), copy it to the anchors folder", remotePath)
|
||||
return copyFileToGuestAnchorsFolder(mc, remotePath)
|
||||
}
|
||||
// Transfer the certificate file to the guest OS
|
||||
logrus.Debugf("The certificates file isn't mounted in the guest, transfer it via SCP.")
|
||||
return machine.LocalhostSSHCopy(
|
||||
"root", // need root to copy to /etc/pki/ca-trust/source/anchors/
|
||||
mc.SSH.IdentityPath,
|
||||
mc.SSH.Port,
|
||||
hostFilePath,
|
||||
GuestAnchorsPath,
|
||||
false,
|
||||
true)
|
||||
}
|
||||
|
||||
// copyFileToGuestAnchorsFolder runs `sudo cp` in ghe guest to copy guestFilePath
|
||||
// to /etc/pki/ca-trust/source/anchors/
|
||||
func copyFileToGuestAnchorsFolder(mc *vmconfigs.MachineConfig, guestFilePath string) error {
|
||||
logrus.Debugf("Running %q in guest", UpdateCATrustCommand)
|
||||
return machine.LocalhostSSHSilent(
|
||||
mc.SSH.RemoteUsername,
|
||||
mc.SSH.IdentityPath,
|
||||
mc.Name,
|
||||
mc.SSH.Port,
|
||||
[]string{"sudo", "cp", guestFilePath, GuestAnchorsPath},
|
||||
)
|
||||
}
|
||||
|
||||
// saveCertificatesToPEM exports the certificates in certs to a PEM file
|
||||
func saveCertificatesToPEM(certs []*x509.Certificate, certsFilePath string) error {
|
||||
certsFile, err := os.Create(certsFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer certsFile.Close()
|
||||
for _, cert := range certs {
|
||||
block := &pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: cert.Raw,
|
||||
}
|
||||
if err := pem.Encode(certsFile, block); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// runUpdateCATrustInGuest runs the update-ca-trust command in the guest OS
|
||||
func runUpdateCATrustInGuest(mc *vmconfigs.MachineConfig) error {
|
||||
logrus.Debugf("Running %q in guest", UpdateCATrustCommand)
|
||||
return machine.LocalhostSSHSilent(
|
||||
mc.SSH.RemoteUsername,
|
||||
mc.SSH.IdentityPath,
|
||||
mc.Name,
|
||||
mc.SSH.Port,
|
||||
[]string{"sudo", UpdateCATrustCommand},
|
||||
)
|
||||
}
|
||||
|
||||
// deduplicateCertificates removes duplicate certificates from a slice of certificates.
|
||||
func deduplicateCertificates(certs []*x509.Certificate) []*x509.Certificate {
|
||||
seen := make(map[string]bool)
|
||||
var unique []*x509.Certificate
|
||||
for _, cert := range certs {
|
||||
if cert == nil {
|
||||
continue
|
||||
}
|
||||
if seen[string(cert.Signature)] {
|
||||
continue
|
||||
}
|
||||
seen[string(cert.Signature)] = true
|
||||
unique = append(unique, cert)
|
||||
}
|
||||
return unique
|
||||
}
|
||||
13
pkg/machine/certificates/certificates_darwin.go
Normal file
13
pkg/machine/certificates/certificates_darwin.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// extractHostCertificates extracts trusted CA certificates from the macOS system keychain
|
||||
func extractHostCertificates() []*x509.Certificate {
|
||||
logrus.Warn("extracting trusted CA certificates on macOS is not implemented yet")
|
||||
return nil
|
||||
}
|
||||
13
pkg/machine/certificates/certificates_freebsd.go
Normal file
13
pkg/machine/certificates/certificates_freebsd.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// extractHostCertificates extracts trusted CA certificates from the FreeBSD system keychain
|
||||
func extractHostCertificates() []*x509.Certificate {
|
||||
logrus.Warn("extracting trusted CA certificates on FreeBSD is not implemented yet")
|
||||
return nil
|
||||
}
|
||||
20
pkg/machine/certificates/certificates_linux.go
Normal file
20
pkg/machine/certificates/certificates_linux.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// extractHostCertificates retrieved Linux trusted CA certificates from the OS
|
||||
// trust stores.
|
||||
func extractHostCertificates() []*x509.Certificate {
|
||||
// On Linux, system CA certificates are typically stored in:
|
||||
// - /etc/ssl/certs/ca-certificates.crt (Debian/Ubuntu)
|
||||
// - /etc/pki/tls/certs/ca-bundle.crt (RHEL/Fedora/CentOS)
|
||||
// - /etc/ssl/ca-bundle.pem (OpenSUSE)
|
||||
// - /etc/pki/tls/cacert.pem (OpenELEC)
|
||||
// - /etc/ssl/cert.pem (Alpine Linux)
|
||||
logrus.Warn("extracting trusted CA certificates on Linux is not implemented yet")
|
||||
return nil
|
||||
}
|
||||
26
pkg/machine/certificates/certificates_linux_test.go
Normal file
26
pkg/machine/certificates/certificates_linux_test.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package certificates
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func verifyCertificateFile(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestExtractAndSaveCertificates(t *testing.T) {
|
||||
certs := extractHostCertificates()
|
||||
// On Linux extractHostCertificates is not implemented
|
||||
// therefore `certs` is expected to be empty.
|
||||
assert.Empty(t, certs)
|
||||
|
||||
filePath := filepath.Join(t.TempDir(), "cert.pem")
|
||||
err := saveCertificatesToPEM(certs, filePath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = verifyCertificateFile(filePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
68
pkg/machine/certificates/certificates_test.go
Normal file
68
pkg/machine/certificates/certificates_test.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDeduplicateCertificates(t *testing.T) {
|
||||
certA := &x509.Certificate{Signature: []byte("sig-a")}
|
||||
certB := &x509.Certificate{Signature: []byte("sig-b")}
|
||||
certC := &x509.Certificate{Signature: []byte("sig-c")}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []*x509.Certificate
|
||||
expected []*x509.Certificate
|
||||
}{
|
||||
{
|
||||
name: "nil slice",
|
||||
input: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "empty slice",
|
||||
input: []*x509.Certificate{},
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "no duplicates",
|
||||
input: []*x509.Certificate{certA, certB, certC},
|
||||
expected: []*x509.Certificate{certA, certB, certC},
|
||||
},
|
||||
{
|
||||
name: "with duplicates",
|
||||
input: []*x509.Certificate{certA, certB, certA, certC, certB},
|
||||
expected: []*x509.Certificate{certA, certB, certC},
|
||||
},
|
||||
{
|
||||
name: "all duplicates",
|
||||
input: []*x509.Certificate{certA, certA, certA},
|
||||
expected: []*x509.Certificate{certA},
|
||||
},
|
||||
{
|
||||
name: "nil entries are skipped",
|
||||
input: []*x509.Certificate{nil, certA, nil, certB},
|
||||
expected: []*x509.Certificate{certA, certB},
|
||||
},
|
||||
{
|
||||
name: "nil and duplicate entries",
|
||||
input: []*x509.Certificate{nil, certA, certB, nil, certA},
|
||||
expected: []*x509.Certificate{certA, certB},
|
||||
},
|
||||
{
|
||||
name: "single certificate",
|
||||
input: []*x509.Certificate{certA},
|
||||
expected: []*x509.Certificate{certA},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := deduplicateCertificates(tt.input)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
146
pkg/machine/certificates/certificates_windows.go
Normal file
146
pkg/machine/certificates/certificates_windows.go
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
package certificates
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"errors"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
predefinedWinStores = []string{
|
||||
"AAD Token Issuer",
|
||||
"AuthRoot",
|
||||
"CA",
|
||||
"ClientAuthIssuer",
|
||||
"Disallowed",
|
||||
"eSIM Certification Authorities",
|
||||
"FlightRoot",
|
||||
"My",
|
||||
"OemEsim",
|
||||
"PasspointTrustedRoots",
|
||||
"Remote Desktop",
|
||||
"REQUEST",
|
||||
"Root",
|
||||
"SmartCardRoot",
|
||||
"TestSignRoot",
|
||||
"Trust",
|
||||
"TrustedAppRoot",
|
||||
"TrustedDevices",
|
||||
"TrustedPeople",
|
||||
"TrustedPublisher",
|
||||
"Windows Live ID Token Issuer",
|
||||
"WindowsServerUpdateServices",
|
||||
"UserDS",
|
||||
}
|
||||
winStoresLocations = []uint{
|
||||
windows.CERT_SYSTEM_STORE_CURRENT_USER,
|
||||
windows.CERT_SYSTEM_STORE_LOCAL_MACHINE,
|
||||
windows.CERT_SYSTEM_STORE_CURRENT_SERVICE,
|
||||
windows.CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE,
|
||||
}
|
||||
winStoresLocationsNames = map[uint]string{
|
||||
windows.CERT_SYSTEM_STORE_CURRENT_USER: "Current User",
|
||||
windows.CERT_SYSTEM_STORE_LOCAL_MACHINE: "Local Machine",
|
||||
windows.CERT_SYSTEM_STORE_CURRENT_SERVICE: "Current Service",
|
||||
windows.CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE: "Local Machine Enterprise",
|
||||
}
|
||||
)
|
||||
|
||||
// extractHostCertificates extracts trusted CA certificates from the prederfined
|
||||
// Windows certificate stores
|
||||
func extractHostCertificates() []*x509.Certificate {
|
||||
var certificates []*x509.Certificate
|
||||
for _, location := range winStoresLocations {
|
||||
for _, store := range predefinedWinStores {
|
||||
certs := extractFromStore(location, store)
|
||||
certificates = append(certificates, certs...)
|
||||
}
|
||||
}
|
||||
return certificates
|
||||
}
|
||||
|
||||
// extractFromStore extracts certificates from a specific Windows certificate store
|
||||
func extractFromStore(storeLocation uint, storeName string) []*x509.Certificate {
|
||||
storeHandle, err := getStoreHandle(storeLocation, storeName)
|
||||
if err != nil {
|
||||
logrus.Debugf("Failed open store %s in %s: %v",
|
||||
storeName,
|
||||
winStoresLocationsNames[storeLocation],
|
||||
err)
|
||||
return nil
|
||||
}
|
||||
|
||||
defer func(h windows.Handle) {
|
||||
if err := windows.CertCloseStore(h, 0); err != nil {
|
||||
logrus.Debugf("Failed to close certificate store: %v", err)
|
||||
}
|
||||
}(*storeHandle)
|
||||
|
||||
var certs []*x509.Certificate
|
||||
var ctx *windows.CertContext = nil
|
||||
for {
|
||||
ctx, err = windows.CertEnumCertificatesInStore(*storeHandle, ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, windows.Errno(windows.CRYPT_E_NOT_FOUND)) {
|
||||
// The function reached the end of the store's list
|
||||
break
|
||||
}
|
||||
logrus.Debugf("Failed to enumerate certificates in store %s in %s: %v",
|
||||
storeName,
|
||||
winStoresLocationsNames[storeLocation],
|
||||
err)
|
||||
return nil
|
||||
}
|
||||
if ctx == nil {
|
||||
break
|
||||
}
|
||||
|
||||
c := parseCertificate(ctx)
|
||||
if c != nil {
|
||||
certs = append(certs, c)
|
||||
}
|
||||
}
|
||||
logrus.Debugf("Extracted %d certificates from %s store in %s", len(certs), storeName, winStoresLocationsNames[storeLocation])
|
||||
return certs
|
||||
}
|
||||
|
||||
// getStoreHandle returns a handle to the certificate store with the given name.
|
||||
func getStoreHandle(storeLocation uint, storeName string) (*windows.Handle, error) {
|
||||
storeNameUTF16, err := windows.UTF16PtrFromString(storeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to convert store name to UTF16: %w", err)
|
||||
}
|
||||
// storeHandle, err := windows.CertOpenSystemStore(0, storeNameUTF16)
|
||||
storeHandle, err := windows.CertOpenStore(
|
||||
windows.CERT_STORE_PROV_SYSTEM,
|
||||
0,
|
||||
0,
|
||||
uint32(storeLocation)|uint32(windows.CERT_STORE_READONLY_FLAG),
|
||||
uintptr(unsafe.Pointer(storeNameUTF16)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open certificate store: %w", err)
|
||||
}
|
||||
return &storeHandle, nil
|
||||
}
|
||||
|
||||
// parseCertificate parses an x509 certificate from a win32 API CertContext
|
||||
// object. If the certificate cannot be parsed, nil is returned.
|
||||
func parseCertificate(certCtx *windows.CertContext) *x509.Certificate {
|
||||
certSize := certCtx.Length
|
||||
certBytesOrig := unsafe.Slice(certCtx.EncodedCert, certSize)
|
||||
// Make a copy because certCtx will be overridden
|
||||
certBytes := make([]byte, certSize)
|
||||
copy(certBytes, certBytesOrig)
|
||||
|
||||
cert, err := x509.ParseCertificate(certBytes) // TODO: if it's not an x509 cert we may want to use a different parising function
|
||||
if err != nil {
|
||||
certSubject := unsafe.Slice(certCtx.CertInfo.Subject.Data, certCtx.CertInfo.Subject.Size)
|
||||
logrus.Debugf("Failed to parse certificate (subject: %s): %v", certSubject, err)
|
||||
return nil
|
||||
}
|
||||
return cert
|
||||
}
|
||||
41
pkg/machine/certificates/certificates_windows_test.go
Normal file
41
pkg/machine/certificates/certificates_windows_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package certificates
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func verifyCertificateFile(filePath string) error {
|
||||
cmd := exec.Command(
|
||||
`powershell`,
|
||||
"-NoProfile",
|
||||
"-NonInteractive",
|
||||
"-Command",
|
||||
"(Get-PfxCertificate -FilePath '"+filePath+"').Verify()",
|
||||
)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("output: %s, error: %s", out, err)
|
||||
}
|
||||
if !strings.Contains(string(out), "True") {
|
||||
return fmt.Errorf("certificate verification failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestExtractAndSaveCertificates(t *testing.T) {
|
||||
certs := extractHostCertificates()
|
||||
assert.NotEmpty(t, certs)
|
||||
|
||||
filePath := filepath.Join(t.TempDir(), "cert.pem")
|
||||
err := saveCertificatesToPEM(certs, filePath)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = verifyCertificateFile(filePath)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
|
@ -26,4 +26,5 @@ type InitOptions struct {
|
|||
UserModeNetworking *bool // nil = use backend/system default, false = disable, true = enable
|
||||
USBs []string
|
||||
SkipTlsVerify types.OptionalBool
|
||||
ImportNativeCA bool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ type SetOptions struct {
|
|||
Rootful *bool
|
||||
UserModeNetworking *bool
|
||||
USBs *[]string
|
||||
ImportNativeCA *bool
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ type initMachine struct {
|
|||
updateConnection *bool
|
||||
userModeNetworking bool
|
||||
tlsVerify *bool
|
||||
importNativeCA bool
|
||||
|
||||
cmd []string
|
||||
}
|
||||
|
|
@ -83,6 +84,9 @@ func (i *initMachine) buildCmd(m *machineTestBuilder) []string {
|
|||
if i.updateConnection != nil {
|
||||
cmd = append(cmd, fmt.Sprintf("--update-connection=%s", strconv.FormatBool(*i.updateConnection)))
|
||||
}
|
||||
if i.importNativeCA {
|
||||
cmd = append(cmd, "--import-native-ca")
|
||||
}
|
||||
|
||||
name := m.name
|
||||
cmd = append(cmd, name)
|
||||
|
|
@ -190,3 +194,8 @@ func (i *initMachine) withUserModeNetworking(r bool) *initMachine { //nolint:unu
|
|||
i.userModeNetworking = r
|
||||
return i
|
||||
}
|
||||
|
||||
func (i *initMachine) withImportNativeCA(r bool) *initMachine {
|
||||
i.importNativeCA = r
|
||||
return i
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ type setMachine struct {
|
|||
memory *uint
|
||||
rootful bool
|
||||
userModeNetworking bool
|
||||
importNativeCA bool
|
||||
|
||||
cmd []string
|
||||
}
|
||||
|
|
@ -31,6 +32,9 @@ func (i *setMachine) buildCmd(m *machineTestBuilder) []string {
|
|||
if i.userModeNetworking {
|
||||
cmd = append(cmd, "--user-mode-networking")
|
||||
}
|
||||
if i.importNativeCA {
|
||||
cmd = append(cmd, "--import-native-ca")
|
||||
}
|
||||
cmd = append(cmd, m.name)
|
||||
i.cmd = cmd
|
||||
return cmd
|
||||
|
|
|
|||
|
|
@ -310,6 +310,53 @@ var _ = Describe("podman machine start", func() {
|
|||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(listings.IsDefault(machineName2)).To(BeTrue())
|
||||
})
|
||||
It("machine init --now with --import-native-ca with mounted data folder", func() {
|
||||
// Windows only
|
||||
skipIfVmtype(define.QemuVirt, "Importing native certificates isn't supported on Linux")
|
||||
skipIfVmtype(define.AppleHvVirt, "Importing native certificates isn't supported on macOS")
|
||||
skipIfVmtype(define.LibKrun, "Importing native certificates isn't supported on macOS")
|
||||
|
||||
// Create a new machine
|
||||
i := initMachine{}
|
||||
initCommand := i.withImage(mb.imagePath).withImportNativeCA(true).withNow()
|
||||
m := randomString()
|
||||
initSession, err := mb.setName(m).setCmd(initCommand).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(initSession).To(Exit(0))
|
||||
|
||||
// Verify that the file in the guest exist
|
||||
certFilePath := "/etc/pki/ca-trust/source/anchors"
|
||||
certFileName := "host-ca-certs.pem"
|
||||
sshMachine := sshMachine{}
|
||||
sshCertFile, err := mb.setName(m).setCmd(sshMachine.withSSHCommand([]string{"ls", certFilePath})).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(sshCertFile).To(Exit(0))
|
||||
Expect(sshCertFile.outputToString()).To(Equal(certFileName))
|
||||
})
|
||||
It("machine init --now with --import-native-ca with SCP file transfer", func() {
|
||||
skipIfVmtype(define.QemuVirt, "Importing native certificates isn't supported on Linux")
|
||||
skipIfVmtype(define.AppleHvVirt, "Importing native certificates isn't supported on macOS")
|
||||
skipIfVmtype(define.LibKrun, "Importing native certificates isn't supported on macOS")
|
||||
skipIfVmtype(define.WSLVirt, "WSL doesn't allow handling volumes (the machine data folder is always mounted")
|
||||
|
||||
// Create a new machine
|
||||
i := initMachine{}
|
||||
initCommand := i.withImage(mb.imagePath).withImportNativeCA(true).withNow()
|
||||
// Don't mount any volume to force the transfer the certificates file via SCP
|
||||
initCommand = initCommand.withVolume("")
|
||||
m := randomString()
|
||||
initSession, err := mb.setName(m).setCmd(initCommand).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(initSession).To(Exit(0))
|
||||
|
||||
certFilePath := "/etc/pki/ca-trust/source/anchors"
|
||||
certFileName := "host-ca-certs.pem"
|
||||
sshMachine := sshMachine{}
|
||||
sshCertFile, err := mb.setName(m).setCmd(sshMachine.withSSHCommand([]string{"ls", certFilePath})).run()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(sshCertFile).To(Exit(0))
|
||||
Expect(sshCertFile.outputToString()).To(Equal(certFileName))
|
||||
})
|
||||
})
|
||||
|
||||
func mapToPort(uris []string) ([]string, error) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
|
||||
"github.com/containers/podman/v6/cmd/podman/registry"
|
||||
"github.com/containers/podman/v6/pkg/machine"
|
||||
"github.com/containers/podman/v6/pkg/machine/certificates"
|
||||
"github.com/containers/podman/v6/pkg/machine/connection"
|
||||
machineDefine "github.com/containers/podman/v6/pkg/machine/define"
|
||||
"github.com/containers/podman/v6/pkg/machine/env"
|
||||
|
|
@ -117,6 +118,7 @@ func Init(opts machineDefine.InitOptions, mp vmconfigs.VMProvider) error {
|
|||
}
|
||||
|
||||
mc.Version = vmconfigs.MachineConfigVersion
|
||||
mc.ImportNativeCA = opts.ImportNativeCA
|
||||
|
||||
createOpts := machineDefine.CreateVMOpts{
|
||||
Name: opts.Name,
|
||||
|
|
@ -655,6 +657,17 @@ func Start(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, opts machine.St
|
|||
return err
|
||||
}
|
||||
|
||||
// Import native CA certificates if enabled
|
||||
if mc.ImportNativeCA {
|
||||
if err := certificates.ImportNativeCertificates(mc, mp.VMType()); err != nil {
|
||||
// Warn the user but continue the machine startup process
|
||||
logrus.Warnf("Failed to import native CA certificates: %v", err)
|
||||
fmt.Println("Warning: Failed to import host trusted CA certificates. The machine will start without them.")
|
||||
} else if !opts.Quiet {
|
||||
fmt.Println("The host trusted CA certificates have been imported successfully")
|
||||
}
|
||||
}
|
||||
|
||||
// mount the volumes to the VM
|
||||
if err := mp.MountVolumesToVM(mc, opts.Quiet); err != nil {
|
||||
return err
|
||||
|
|
@ -731,6 +744,10 @@ func Set(mc *vmconfigs.MachineConfig, mp vmconfigs.VMProvider, opts machineDefin
|
|||
mc.Resources.DiskSize = *opts.DiskSize
|
||||
}
|
||||
|
||||
if opts.ImportNativeCA != nil {
|
||||
mc.ImportNativeCA = *opts.ImportNativeCA
|
||||
}
|
||||
|
||||
if err := mp.SetProviderAttrs(mc, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,30 @@ func LocalhostSSHWithStdin(username, identityPath, name string, sshPort int, inp
|
|||
return localhostBuiltinSSH(username, identityPath, name, sshPort, inputArgs, true, stdin)
|
||||
}
|
||||
|
||||
// LocalhostSSHCopy uses scp to copy files from/to a localhost machine using ssh.
|
||||
func LocalhostSSHCopy(username, identityPath string, sshPort int, srcPath, destPath string, isSrcFromGuest, quiet bool) error {
|
||||
var src, dest string
|
||||
if isSrcFromGuest {
|
||||
src = username + "@localhost:" + srcPath
|
||||
dest = destPath
|
||||
} else {
|
||||
src = srcPath
|
||||
dest = username + "@localhost:" + destPath
|
||||
}
|
||||
args := append(
|
||||
LocalhostSSHArgs(), // Warning: This MUST NOT be generalized to allow communication over untrusted networks.
|
||||
"-r",
|
||||
"-i", identityPath,
|
||||
"-P", strconv.Itoa(sshPort),
|
||||
src, dest)
|
||||
cmd := exec.Command("scp", args...)
|
||||
if !quiet {
|
||||
cmd.Stdout = os.Stdout
|
||||
}
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func localhostBuiltinSSH(username, identityPath, name string, sshPort int, inputArgs []string, passOutput bool, stdin io.Reader) error {
|
||||
config, err := createLocalhostConfig(username, identityPath) // WARNING: This MUST NOT be generalized to allow communication over untrusted networks.
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ type MachineConfig struct {
|
|||
Rosetta bool
|
||||
|
||||
Ansible *AnsibleConfig
|
||||
|
||||
ImportNativeCA bool
|
||||
}
|
||||
|
||||
type VMProvider interface { //nolint:interfacebloat
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue