Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add encryption decryption feature #16329

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions cmd/podman/common/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
22 changes: 14 additions & 8 deletions cmd/podman/containers/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions cmd/podman/images/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type pullOptionsWrapper struct {
entities.ImagePullOptions
TLSVerifyCLI bool // CLI only
CredentialsCLI string
DecryptionKeys []string
}

var (
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
}
Expand Down
20 changes: 20 additions & 0 deletions cmd/podman/images/push.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package images

import (
"fmt"
"os"

"github.com/containers/common/pkg/auth"
Expand All @@ -20,6 +21,8 @@ type pushOptionsWrapper struct {
TLSVerifyCLI bool // CLI only
CredentialsCLI string
SignPassphraseFileCLI string
EncryptionKeys []string
EncryptLayers []int
}

var (
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions docs/source/markdown/options/decryption-key.md
Original file line number Diff line number Diff line change
@@ -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.
TomSweeneyRedHat marked this conversation as resolved.
Show resolved Hide resolved
#### **--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.
2 changes: 2 additions & 0 deletions docs/source/markdown/podman-create.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/source/markdown/podman-pull.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ All tagged images in the repository will be pulled.

@@option creds

@@option decryption-key

@@option disable-content-trust

#### **--help**, **-h**
Expand Down
8 changes: 8 additions & 0 deletions docs/source/markdown/podman-push.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -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:[email protected] or pkcs7:/path/to/x509-file.

#### **--format**, **-f**=*format*

Manifest Type (oci, v2s2, or v2s1) to use when pushing an image.
Expand Down
2 changes: 2 additions & 0 deletions docs/source/markdown/podman-run.1.md.in
Original file line number Diff line number Diff line change
Expand Up @@ -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*.
Expand Down
13 changes: 13 additions & 0 deletions pkg/domain/entities/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions pkg/domain/entities/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ type ContainerCreateOptions struct {
ChrootDirs []string
IsInfra bool
IsClone bool
DecryptionKeys []string

Net *NetOptions `json:"net,omitempty"`

Expand Down
3 changes: 3 additions & 0 deletions pkg/domain/infra/abi/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 == "" {
Expand Down
8 changes: 8 additions & 0 deletions pkg/domain/infra/tunnel/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)

Expand Down
36 changes: 36 additions & 0 deletions pkg/util/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Loading