mirror of
https://github.com/containers/podman
synced 2024-10-19 08:44:11 +00:00
Adding encryption decryption feature
Signed-off-by: Tarun1 Gupta <gupttaru@deshaw.com>
This commit is contained in:
parent
f50ce4aea4
commit
3bb9ed4f09
|
@ -571,9 +571,18 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
|
|||
createFlags.StringVar(&cf.PasswdEntry, passwdEntryName, "", "Entry to write to /etc/passwd")
|
||||
_ = cmd.RegisterFlagCompletionFunc(passwdEntryName, completion.AutocompleteNone)
|
||||
|
||||
decryptionKeysFlagName := "decryption-key"
|
||||
createFlags.StringSliceVar(
|
||||
&cf.DecryptionKeys,
|
||||
decryptionKeysFlagName, []string{},
|
||||
"Key needed to decrypt the image (e.g. /path/to/key.pem)",
|
||||
)
|
||||
_ = cmd.RegisterFlagCompletionFunc(decryptionKeysFlagName, completion.AutocompleteNone)
|
||||
|
||||
if registry.IsRemote() {
|
||||
_ = createFlags.MarkHidden("env-host")
|
||||
_ = createFlags.MarkHidden("http-proxy")
|
||||
_ = createFlags.MarkHidden(decryptionKeysFlagName)
|
||||
} else {
|
||||
createFlags.StringVar(
|
||||
&cf.SignaturePolicy,
|
||||
|
|
|
@ -334,15 +334,21 @@ func PullImage(imageName string, cliVals *entities.ContainerCreateOptions) (stri
|
|||
skipTLSVerify = types.NewOptionalBool(!cliVals.TLSVerify.Value())
|
||||
}
|
||||
|
||||
decConfig, err := util.DecryptConfig(cliVals.DecryptionKeys)
|
||||
if err != nil {
|
||||
return "unable to obtain decryption config", err
|
||||
}
|
||||
|
||||
pullReport, pullErr := registry.ImageEngine().Pull(registry.GetContext(), imageName, entities.ImagePullOptions{
|
||||
Authfile: cliVals.Authfile,
|
||||
Quiet: cliVals.Quiet,
|
||||
Arch: cliVals.Arch,
|
||||
OS: cliVals.OS,
|
||||
Variant: cliVals.Variant,
|
||||
SignaturePolicy: cliVals.SignaturePolicy,
|
||||
PullPolicy: pullPolicy,
|
||||
SkipTLSVerify: skipTLSVerify,
|
||||
Authfile: cliVals.Authfile,
|
||||
Quiet: cliVals.Quiet,
|
||||
Arch: cliVals.Arch,
|
||||
OS: cliVals.OS,
|
||||
Variant: cliVals.Variant,
|
||||
SignaturePolicy: cliVals.SignaturePolicy,
|
||||
PullPolicy: pullPolicy,
|
||||
SkipTLSVerify: skipTLSVerify,
|
||||
OciDecryptConfig: decConfig,
|
||||
})
|
||||
if pullErr != nil {
|
||||
return "", pullErr
|
||||
|
|
|
@ -23,6 +23,7 @@ type pullOptionsWrapper struct {
|
|||
entities.ImagePullOptions
|
||||
TLSVerifyCLI bool // CLI only
|
||||
CredentialsCLI string
|
||||
DecryptionKeys []string
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -107,6 +108,13 @@ func pullFlags(cmd *cobra.Command) {
|
|||
flags.StringVar(&pullOptions.Authfile, authfileFlagName, auth.GetDefaultAuthFile(), "Path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override")
|
||||
_ = cmd.RegisterFlagCompletionFunc(authfileFlagName, completion.AutocompleteDefault)
|
||||
|
||||
decryptionKeysFlagName := "decryption-key"
|
||||
flags.StringSliceVar(&pullOptions.DecryptionKeys, decryptionKeysFlagName, nil, "Key needed to decrypt the image (e.g. /path/to/key.pem)")
|
||||
_ = cmd.RegisterFlagCompletionFunc(decryptionKeysFlagName, completion.AutocompleteDefault)
|
||||
|
||||
if registry.IsRemote() {
|
||||
_ = flags.MarkHidden(decryptionKeysFlagName)
|
||||
}
|
||||
if !registry.IsRemote() {
|
||||
certDirFlagName := "cert-dir"
|
||||
flags.StringVar(&pullOptions.CertDir, certDirFlagName, "", "`Pathname` of a directory containing TLS certificates and keys")
|
||||
|
@ -156,6 +164,12 @@ func imagePull(cmd *cobra.Command, args []string) error {
|
|||
pullOptions.Password = creds.Password
|
||||
}
|
||||
|
||||
decConfig, err := util.DecryptConfig(pullOptions.DecryptionKeys)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to obtain decryption config: %w", err)
|
||||
}
|
||||
pullOptions.OciDecryptConfig = decConfig
|
||||
|
||||
if !pullOptions.Quiet {
|
||||
pullOptions.Writer = os.Stderr
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containers/common/pkg/auth"
|
||||
|
@ -20,6 +21,8 @@ type pushOptionsWrapper struct {
|
|||
TLSVerifyCLI bool // CLI only
|
||||
CredentialsCLI string
|
||||
SignPassphraseFileCLI string
|
||||
EncryptionKeys []string
|
||||
EncryptLayers []int
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -121,6 +124,14 @@ func pushFlags(cmd *cobra.Command) {
|
|||
flags.StringVar(&pushOptions.CompressionFormat, compressionFormat, "", "compression format to use")
|
||||
_ = cmd.RegisterFlagCompletionFunc(compressionFormat, common.AutocompleteCompressionFormat)
|
||||
|
||||
encryptionKeysFlagName := "encryption-key"
|
||||
flags.StringSliceVar(&pushOptions.EncryptionKeys, encryptionKeysFlagName, nil, "Key with the encryption protocol to use to encrypt the image (e.g. jwe:/path/to/key.pem)")
|
||||
_ = cmd.RegisterFlagCompletionFunc(encryptionKeysFlagName, completion.AutocompleteDefault)
|
||||
|
||||
encryptLayersFlagName := "encrypt-layer"
|
||||
flags.IntSliceVar(&pushOptions.EncryptLayers, encryptLayersFlagName, nil, "Layers to encrypt, 0-indexed layer indices with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer). If not defined, will encrypt all layers if encryption-key flag is specified")
|
||||
_ = cmd.RegisterFlagCompletionFunc(encryptLayersFlagName, completion.AutocompleteDefault)
|
||||
|
||||
if registry.IsRemote() {
|
||||
_ = flags.MarkHidden("cert-dir")
|
||||
_ = flags.MarkHidden("compress")
|
||||
|
@ -129,6 +140,8 @@ func pushFlags(cmd *cobra.Command) {
|
|||
_ = flags.MarkHidden(signByFlagName)
|
||||
_ = flags.MarkHidden(signBySigstorePrivateKeyFlagName)
|
||||
_ = flags.MarkHidden(signPassphraseFileFlagName)
|
||||
_ = flags.MarkHidden(encryptionKeysFlagName)
|
||||
_ = flags.MarkHidden(encryptLayersFlagName)
|
||||
}
|
||||
if !registry.IsRemote() {
|
||||
flags.StringVar(&pushOptions.SignaturePolicy, "signature-policy", "", "Path to a signature-policy file")
|
||||
|
@ -172,6 +185,13 @@ func imagePush(cmd *cobra.Command, args []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
encConfig, encLayers, err := util.EncryptConfig(pushOptions.EncryptionKeys, pushOptions.EncryptLayers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to obtain encryption config: %w", err)
|
||||
}
|
||||
pushOptions.OciEncryptConfig = encConfig
|
||||
pushOptions.OciEncryptLayers = encLayers
|
||||
|
||||
// Let's do all the remaining Yoga in the API to prevent us from scattering
|
||||
// logic across (too) many parts of the code.
|
||||
return registry.ImageEngine().Push(registry.GetContext(), source, destination, pushOptions.ImagePushOptions)
|
||||
|
|
7
docs/source/markdown/options/decryption-key.md
Normal file
7
docs/source/markdown/options/decryption-key.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
####> This option file is used in:
|
||||
####> podman create, pull, run
|
||||
####> If you edit this file, make sure your changes
|
||||
####> are applicable to all of those.
|
||||
#### **--decryption-key**=*key[:passphrase]*
|
||||
|
||||
The [key[:passphrase]] to be used for decryption of images. Key can point to keys and/or certificates. Decryption will be tried with all keys. If the key is protected by a passphrase, it is required to be passed in the argument and omitted otherwise.
|
|
@ -114,6 +114,8 @@ and specified with a _tag_.
|
|||
|
||||
@@option cpuset-mems
|
||||
|
||||
@@option decryption-key
|
||||
|
||||
@@option device
|
||||
|
||||
Note: if the user only has access rights via a group, accessing the device
|
||||
|
|
|
@ -57,6 +57,8 @@ All tagged images in the repository will be pulled.
|
|||
|
||||
@@option creds
|
||||
|
||||
@@option decryption-key
|
||||
|
||||
@@option disable-content-trust
|
||||
|
||||
#### **--help**, **-h**
|
||||
|
|
|
@ -64,6 +64,14 @@ Note: This flag can only be set when using the **dir** transport
|
|||
|
||||
@@option disable-content-trust
|
||||
|
||||
#### **--encrypt-layer**=*layer(s)*
|
||||
|
||||
Layer(s) to encrypt: 0-indexed layer indices with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer). If not defined, will encrypt all layers if encryption-key flag is specified.
|
||||
|
||||
#### **--encryption-key**=*key*
|
||||
|
||||
The [protocol:keyfile] specifies the encryption protocol, which can be JWE (RFC7516), PGP (RFC4880), and PKCS7 (RFC2315) and the key material required for image encryption. For instance, jwe:/path/to/key.pem or pgp:admin@example.com or pkcs7:/path/to/x509-file.
|
||||
|
||||
#### **--format**, **-f**=*format*
|
||||
|
||||
Manifest Type (oci, v2s2, or v2s1) to use when pushing an image.
|
||||
|
|
|
@ -131,6 +131,8 @@ and specified with a _tag_.
|
|||
|
||||
@@option cpuset-mems
|
||||
|
||||
@@option decryption-key
|
||||
|
||||
#### **--detach**, **-d**
|
||||
|
||||
Detached mode: run the container in the background and print the new container ID. The default is *false*.
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/containers/common/pkg/config"
|
||||
"github.com/containers/image/v5/manifest"
|
||||
"github.com/containers/image/v5/types"
|
||||
encconfig "github.com/containers/ocicrypt/config"
|
||||
"github.com/containers/podman/v4/pkg/inspect"
|
||||
"github.com/containers/podman/v4/pkg/trust"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
|
@ -158,6 +159,9 @@ type ImagePullOptions struct {
|
|||
PullPolicy config.PullPolicy
|
||||
// Writer is used to display copy information including progress bars.
|
||||
Writer io.Writer
|
||||
// OciDecryptConfig contains the config that can be used to decrypt an image if it is
|
||||
// encrypted if non-nil. If nil, it does not attempt to decrypt an image.
|
||||
OciDecryptConfig *encconfig.DecryptConfig
|
||||
}
|
||||
|
||||
// ImagePullReport is the response from pulling one or more images.
|
||||
|
@ -227,6 +231,15 @@ type ImagePushOptions struct {
|
|||
CompressionFormat string
|
||||
// Writer is used to display copy information including progress bars.
|
||||
Writer io.Writer
|
||||
// OciEncryptConfig when non-nil indicates that an image should be encrypted.
|
||||
// The encryption options is derived from the construction of EncryptConfig object.
|
||||
OciEncryptConfig *encconfig.EncryptConfig
|
||||
// OciEncryptLayers represents the list of layers to encrypt.
|
||||
// If nil, don't encrypt any layers.
|
||||
// If non-nil and len==0, denotes encrypt all layers.
|
||||
// integers in the slice represent 0-indexed layer indices, with support for negative
|
||||
// indexing. i.e. 0 is the first layer, -1 is the last (top-most) layer.
|
||||
OciEncryptLayers *[]int
|
||||
}
|
||||
|
||||
// ImagePushReport is the response from pushing an image.
|
||||
|
|
|
@ -290,6 +290,7 @@ type ContainerCreateOptions struct {
|
|||
ChrootDirs []string
|
||||
IsInfra bool
|
||||
IsClone bool
|
||||
DecryptionKeys []string
|
||||
|
||||
Net *NetOptions `json:"net,omitempty"`
|
||||
|
||||
|
|
|
@ -236,6 +236,7 @@ func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, options entiti
|
|||
pullOptions.SignaturePolicyPath = options.SignaturePolicy
|
||||
pullOptions.InsecureSkipTLSVerify = options.SkipTLSVerify
|
||||
pullOptions.Writer = options.Writer
|
||||
pullOptions.OciDecryptConfig = options.OciDecryptConfig
|
||||
|
||||
if !options.Quiet && pullOptions.Writer == nil {
|
||||
pullOptions.Writer = os.Stderr
|
||||
|
@ -309,6 +310,8 @@ func (ir *ImageEngine) Push(ctx context.Context, source string, destination stri
|
|||
pushOptions.SignSigstorePrivateKeyPassphrase = options.SignSigstorePrivateKeyPassphrase
|
||||
pushOptions.InsecureSkipTLSVerify = options.SkipTLSVerify
|
||||
pushOptions.Writer = options.Writer
|
||||
pushOptions.OciEncryptConfig = options.OciEncryptConfig
|
||||
pushOptions.OciEncryptLayers = options.OciEncryptLayers
|
||||
|
||||
compressionFormat := options.CompressionFormat
|
||||
if compressionFormat == "" {
|
||||
|
|
|
@ -105,6 +105,10 @@ func (ir *ImageEngine) Prune(ctx context.Context, opts entities.ImagePruneOption
|
|||
}
|
||||
|
||||
func (ir *ImageEngine) Pull(ctx context.Context, rawImage string, opts entities.ImagePullOptions) (*entities.ImagePullReport, error) {
|
||||
if opts.OciDecryptConfig != nil {
|
||||
return nil, fmt.Errorf("decryption is not supported for remote clients")
|
||||
}
|
||||
|
||||
options := new(images.PullOptions)
|
||||
options.WithAllTags(opts.AllTags).WithAuthfile(opts.Authfile).WithArch(opts.Arch).WithOS(opts.OS)
|
||||
options.WithVariant(opts.Variant).WithPassword(opts.Password)
|
||||
|
@ -240,6 +244,10 @@ func (ir *ImageEngine) Import(ctx context.Context, opts entities.ImageImportOpti
|
|||
}
|
||||
|
||||
func (ir *ImageEngine) Push(ctx context.Context, source string, destination string, opts entities.ImagePushOptions) error {
|
||||
if opts.OciEncryptConfig != nil {
|
||||
return fmt.Errorf("encryption is not supported for remote clients")
|
||||
}
|
||||
|
||||
options := new(images.PushOptions)
|
||||
options.WithAll(opts.All).WithCompress(opts.Compress).WithUsername(opts.Username).WithPassword(opts.Password).WithAuthfile(opts.Authfile).WithFormat(opts.Format).WithRemoveSignatures(opts.RemoveSignatures).WithQuiet(opts.Quiet).WithCompressionFormat(opts.CompressionFormat).WithProgressWriter(opts.Writer)
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
"github.com/containers/common/pkg/config"
|
||||
"github.com/containers/common/pkg/util"
|
||||
"github.com/containers/image/v5/types"
|
||||
encconfig "github.com/containers/ocicrypt/config"
|
||||
enchelpers "github.com/containers/ocicrypt/helpers"
|
||||
"github.com/containers/podman/v4/pkg/errorhandling"
|
||||
"github.com/containers/podman/v4/pkg/namespaces"
|
||||
"github.com/containers/podman/v4/pkg/rootless"
|
||||
|
@ -756,3 +758,37 @@ func SizeOfPath(path string) (uint64, error) {
|
|||
})
|
||||
return size, err
|
||||
}
|
||||
|
||||
// EncryptConfig translates encryptionKeys into a EncriptionsConfig structure
|
||||
func EncryptConfig(encryptionKeys []string, encryptLayers []int) (*encconfig.EncryptConfig, *[]int, error) {
|
||||
var encLayers *[]int
|
||||
var encConfig *encconfig.EncryptConfig
|
||||
|
||||
if len(encryptionKeys) > 0 {
|
||||
// encryption
|
||||
encLayers = &encryptLayers
|
||||
ecc, err := enchelpers.CreateCryptoConfig(encryptionKeys, []string{})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("invalid encryption keys: %w", err)
|
||||
}
|
||||
cc := encconfig.CombineCryptoConfigs([]encconfig.CryptoConfig{ecc})
|
||||
encConfig = cc.EncryptConfig
|
||||
}
|
||||
return encConfig, encLayers, nil
|
||||
}
|
||||
|
||||
// DecryptConfig translates decryptionKeys into a DescriptionConfig structure
|
||||
func DecryptConfig(decryptionKeys []string) (*encconfig.DecryptConfig, error) {
|
||||
var decryptConfig *encconfig.DecryptConfig
|
||||
if len(decryptionKeys) > 0 {
|
||||
// decryption
|
||||
dcc, err := enchelpers.CreateCryptoConfig([]string{}, decryptionKeys)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid decryption keys: %w", err)
|
||||
}
|
||||
cc := encconfig.CombineCryptoConfigs([]encconfig.CryptoConfig{dcc})
|
||||
decryptConfig = cc.DecryptConfig
|
||||
}
|
||||
|
||||
return decryptConfig, nil
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/containers/podman/v4/pkg/rootless"
|
||||
. "github.com/containers/podman/v4/test/utils"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
@ -559,4 +560,89 @@ var _ = Describe("Podman pull", func() {
|
|||
Expect(session).Should(Exit(0))
|
||||
Expect(session.ErrorToString()).To(BeEmpty())
|
||||
})
|
||||
|
||||
Describe("podman pull and decrypt", func() {
|
||||
|
||||
decryptionTestHelper := func(imgPath string) *PodmanSessionIntegration {
|
||||
bitSize := 1024
|
||||
keyFileName := filepath.Join(podmanTest.TempDir, "key")
|
||||
publicKeyFileName, privateKeyFileName, err := WriteRSAKeyPair(keyFileName, bitSize)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
wrongKeyFileName := filepath.Join(podmanTest.TempDir, "wrong_key")
|
||||
_, wrongPrivateKeyFileName, err := WriteRSAKeyPair(wrongKeyFileName, bitSize)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
session := podmanTest.Podman([]string{"push", "--encryption-key", "jwe:" + publicKeyFileName, "--tls-verify=false", "--remove-signatures", ALPINE, imgPath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
|
||||
session = podmanTest.Podman([]string{"rmi", ALPINE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
// Pulling encrypted image without key should fail
|
||||
session = podmanTest.Podman([]string{"pull", imgPath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(125))
|
||||
|
||||
// Pulling encrypted image with wrong key should fail
|
||||
session = podmanTest.Podman([]string{"pull", "--decryption-key", wrongPrivateKeyFileName, imgPath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(125))
|
||||
|
||||
// Pulling encrypted image with correct key should pass
|
||||
session = podmanTest.Podman([]string{"pull", "--decryption-key", privateKeyFileName, imgPath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
session = podmanTest.Podman([]string{"images"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
return session
|
||||
}
|
||||
|
||||
It("From oci", func() {
|
||||
SkipIfRemote("Remote pull neither supports oci transport, nor decryption")
|
||||
|
||||
podmanTest.AddImageToRWStore(ALPINE)
|
||||
|
||||
bbdir := filepath.Join(podmanTest.TempDir, "busybox-oci")
|
||||
imgPath := fmt.Sprintf("oci:%s", bbdir)
|
||||
|
||||
session := decryptionTestHelper(imgPath)
|
||||
|
||||
Expect(session.LineInOutputContainsTag(filepath.Join("localhost", bbdir), "latest")).To(BeTrue())
|
||||
})
|
||||
|
||||
It("From local registry", func() {
|
||||
SkipIfRemote("Remote pull does not support decryption")
|
||||
|
||||
if podmanTest.Host.Arch == "ppc64le" {
|
||||
Skip("No registry image for ppc64le")
|
||||
}
|
||||
|
||||
podmanTest.AddImageToRWStore(ALPINE)
|
||||
|
||||
if rootless.IsRootless() {
|
||||
err := podmanTest.RestoreArtifact(REGISTRY_IMAGE)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
lock := GetPortLock("5000")
|
||||
defer lock.Unlock()
|
||||
session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", REGISTRY_IMAGE, "/entrypoint.sh", "/etc/docker/registry/config.yml"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) {
|
||||
Skip("Cannot start docker registry.")
|
||||
}
|
||||
|
||||
imgPath := "localhost:5000/my-alpine"
|
||||
|
||||
session = decryptionTestHelper(imgPath)
|
||||
|
||||
Expect(session.LineInOutputContainsTag(imgPath, "latest")).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
@ -127,6 +127,17 @@ var _ = Describe("Podman push", func() {
|
|||
Expect(output).To(ContainSubstring("Writing manifest to image destination"))
|
||||
Expect(output).To(ContainSubstring("Storing signatures"))
|
||||
|
||||
bitSize := 1024
|
||||
keyFileName := filepath.Join(podmanTest.TempDir, "key")
|
||||
publicKeyFileName, _, err := WriteRSAKeyPair(keyFileName, bitSize)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
if !IsRemote() { // Remote does not support --encryption-key
|
||||
push = podmanTest.Podman([]string{"push", "--encryption-key", "jwe:" + publicKeyFileName, "--tls-verify=false", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
|
||||
push.WaitWithDefaultTimeout()
|
||||
Expect(push).Should(Exit(0))
|
||||
}
|
||||
|
||||
if !IsRemote() { // Remote does not support --digestfile
|
||||
// Test --digestfile option
|
||||
push2 := podmanTest.Podman([]string{"push", "--tls-verify=false", "--digestfile=/tmp/digestfile.txt", "--remove-signatures", ALPINE, "localhost:5000/my-alpine"})
|
||||
|
@ -261,6 +272,25 @@ var _ = Describe("Podman push", func() {
|
|||
Expect(push).Should(Exit(0))
|
||||
})
|
||||
|
||||
It("podman push and encrypt to oci", func() {
|
||||
SkipIfRemote("Remote push neither supports oci transport, nor encryption")
|
||||
|
||||
bbdir := filepath.Join(podmanTest.TempDir, "busybox-oci")
|
||||
|
||||
bitSize := 1024
|
||||
keyFileName := filepath.Join(podmanTest.TempDir, "key")
|
||||
publicKeyFileName, _, err := WriteRSAKeyPair(keyFileName, bitSize)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
session := podmanTest.Podman([]string{"push", "--encryption-key", "jwe:" + publicKeyFileName, ALPINE, fmt.Sprintf("oci:%s", bbdir)})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"rmi", ALPINE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
})
|
||||
|
||||
It("podman push to docker-archive", func() {
|
||||
SkipIfRemote("Remote push does not support docker-archive transport")
|
||||
tarfn := filepath.Join(podmanTest.TempDir, "alp.tar")
|
||||
|
|
|
@ -1996,4 +1996,46 @@ WORKDIR /madethis`, BB)
|
|||
Expect(output).To(ContainSubstring("noexec"))
|
||||
Expect(output).To(ContainSubstring("nodev"))
|
||||
})
|
||||
|
||||
It("podman run and decrypt from local registry", func() {
|
||||
SkipIfRemote("Remote run does not support decryption")
|
||||
|
||||
if podmanTest.Host.Arch == "ppc64le" {
|
||||
Skip("No registry image for ppc64le")
|
||||
}
|
||||
|
||||
podmanTest.AddImageToRWStore(ALPINE)
|
||||
|
||||
if rootless.IsRootless() {
|
||||
err := podmanTest.RestoreArtifact(REGISTRY_IMAGE)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
}
|
||||
lock := GetPortLock("5000")
|
||||
defer lock.Unlock()
|
||||
session := podmanTest.Podman([]string{"run", "-d", "--name", "registry", "-p", "5000:5000", REGISTRY_IMAGE, "/entrypoint.sh", "/etc/docker/registry/config.yml"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
if !WaitContainerReady(podmanTest, "registry", "listening on", 20, 1) {
|
||||
Skip("Cannot start docker registry.")
|
||||
}
|
||||
|
||||
bitSize := 1024
|
||||
keyFileName := filepath.Join(podmanTest.TempDir, "key")
|
||||
publicKeyFileName, privateKeyFileName, err := WriteRSAKeyPair(keyFileName, bitSize)
|
||||
Expect(err).To(BeNil())
|
||||
|
||||
imgPath := "localhost:5000/my-alpine"
|
||||
session = podmanTest.Podman([]string{"push", "--encryption-key", "jwe:" + publicKeyFileName, "--tls-verify=false", "--remove-signatures", ALPINE, imgPath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
|
||||
session = podmanTest.Podman([]string{"rmi", ALPINE})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
|
||||
session = podmanTest.Podman([]string{"run", "--decryption-key", privateKeyFileName, imgPath})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
Expect(session.ErrorToString()).To(ContainSubstring("Trying to pull"))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -151,4 +151,20 @@ var _ = Describe("Common functions test", func() {
|
|||
Entry("Docker not in cgroup file", "/tmp/cgroup.test", false, true, false),
|
||||
)
|
||||
|
||||
It("Test WriteRSAKeyPair", func() {
|
||||
fileName := "/tmp/test_key"
|
||||
bitSize := 1024
|
||||
|
||||
publicKeyFileName, privateKeyFileName, err := WriteRSAKeyPair(fileName, bitSize)
|
||||
Expect(err).To(BeNil(), "Failed to write RSA key pair to files.")
|
||||
|
||||
read, err := os.Open(publicKeyFileName)
|
||||
Expect(err).To(BeNil(), "Cannot find the public key file after we write it.")
|
||||
defer read.Close()
|
||||
|
||||
read, err = os.Open(privateKeyFileName)
|
||||
Expect(err).To(BeNil(), "Cannot find the private key file after we write it.")
|
||||
defer read.Close()
|
||||
})
|
||||
|
||||
})
|
||||
|
|
|
@ -11,6 +11,11 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
crypto_rand "crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/containers/storage/pkg/parsers/kernel"
|
||||
|
@ -523,3 +528,76 @@ func RandomString(n int) string {
|
|||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// Encode *rsa.PublicKey and store it in a file.
|
||||
// Adds appropriate extension to the fileName, and returns the complete fileName of
|
||||
// the file storing the public key.
|
||||
func savePublicKey(fileName string, publicKey *rsa.PublicKey) (string, error) {
|
||||
// Encode public key to PKIX, ASN.1 DER form
|
||||
pubBytes, err := x509.MarshalPKIXPublicKey(publicKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
pubPEM := pem.EncodeToMemory(
|
||||
&pem.Block{
|
||||
Type: "RSA PUBLIC KEY",
|
||||
Bytes: pubBytes,
|
||||
},
|
||||
)
|
||||
|
||||
// Write public key to file
|
||||
publicKeyFileName := fileName + ".rsa.pub"
|
||||
if err := os.WriteFile(publicKeyFileName, pubPEM, 0600); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return publicKeyFileName, nil
|
||||
}
|
||||
|
||||
// Encode *rsa.PrivateKey and store it in a file.
|
||||
// Adds appropriate extension to the fileName, and returns the complete fileName of
|
||||
// the file storing the private key.
|
||||
func savePrivateKey(fileName string, privateKey *rsa.PrivateKey) (string, error) {
|
||||
// Encode private key to PKCS#1, ASN.1 DER form
|
||||
privBytes := x509.MarshalPKCS1PrivateKey(privateKey)
|
||||
keyPEM := pem.EncodeToMemory(
|
||||
&pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: privBytes,
|
||||
},
|
||||
)
|
||||
|
||||
// Write private key to file
|
||||
privateKeyFileName := fileName + ".rsa"
|
||||
if err := os.WriteFile(privateKeyFileName, keyPEM, 0600); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return privateKeyFileName, nil
|
||||
}
|
||||
|
||||
// Generate RSA key pair of specified bit size and write them to files.
|
||||
// Adds appropriate extension to the fileName, and returns the complete fileName of
|
||||
// the files storing the public and private key respectively.
|
||||
func WriteRSAKeyPair(fileName string, bitSize int) (string, string, error) {
|
||||
// Generate RSA key
|
||||
privateKey, err := rsa.GenerateKey(crypto_rand.Reader, bitSize)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
publicKey := privateKey.Public().(*rsa.PublicKey)
|
||||
|
||||
publicKeyFileName, err := savePublicKey(fileName, publicKey)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
privateKeyFileName, err := savePrivateKey(fileName, privateKey)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return publicKeyFileName, privateKeyFileName, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue