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

feat: expand Nydusify to support encrypted Nydus images #1386

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
56 changes: 44 additions & 12 deletions contrib/nydusify/cmd/nydusify.go
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,13 @@ func main() {
Usage: "File path to save the metrics collected during conversion in JSON format, for example: './output.json'",
EnvVars: []string{"OUTPUT_JSON"},
},
&cli.StringSliceFlag{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

smoke test is required. Please also update doc

Name: "encrypt-recipients",
Value: nil,
Usage: "Recipients to encrypt the nydus bootstrap layer, like " +
"jwe:<public-key-file-path>, provider:<cmd/gprc>, pgp:<email-address>, pkcs7:<x509-file-path>",
EnvVars: []string{"ENCRYPT_RECIPIENTS"},
},
},
Action: func(c *cli.Context) error {
setupLogLevel(c)
Expand Down Expand Up @@ -507,14 +514,15 @@ func main() {
ChunkDictRef: chunkDictRef,
ChunkDictInsecure: c.Bool("chunk-dict-insecure"),

PrefetchPatterns: prefetchPatterns,
MergePlatform: c.Bool("merge-platform"),
Docker2OCI: docker2OCI,
FsVersion: fsVersion,
FsAlignChunk: c.Bool("backend-aligned-chunk") || c.Bool("fs-align-chunk"),
Compressor: c.String("compressor"),
ChunkSize: c.String("chunk-size"),
BatchSize: c.String("batch-size"),
PrefetchPatterns: prefetchPatterns,
MergePlatform: c.Bool("merge-platform"),
Docker2OCI: docker2OCI,
FsVersion: fsVersion,
FsAlignChunk: c.Bool("backend-aligned-chunk") || c.Bool("fs-align-chunk"),
Compressor: c.String("compressor"),
ChunkSize: c.String("chunk-size"),
BatchSize: c.String("batch-size"),
EncryptRecipients: c.StringSlice("encrypt-recipients"),

OCIRef: c.Bool("oci-ref"),
WithReferrer: c.Bool("with-referrer"),
Expand Down Expand Up @@ -606,6 +614,12 @@ func main() {
Usage: "Path to the nydusd binary, default to search in PATH",
EnvVars: []string{"NYDUSD"},
},
&cli.StringSliceFlag{
Name: "decrypt-keys",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Value: nil,
Usage: "Keys to decrypt nydus bootstrap layer.",
EnvVars: []string{"DECRYPT_KEYS"},
},
},
Action: func(c *cli.Context) error {
setupLogLevel(c)
Expand All @@ -632,6 +646,7 @@ func main() {
BackendType: backendType,
BackendConfig: backendConfig,
ExpectedArch: arch,
DecryptKeys: c.StringSlice("decrypt-keys"),
})
if err != nil {
return err
Expand Down Expand Up @@ -765,6 +780,12 @@ func main() {
Usage: "The nydusd binary path, if unset, search in PATH environment",
EnvVars: []string{"NYDUSD"},
},
&cli.StringSliceFlag{
Name: "decrypt-keys",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Value: nil,
Usage: "Keys to decrypt nydus bootstrap layer.",
EnvVars: []string{"DECRYPT_KEYS"},
},
},
Action: func(c *cli.Context) error {
setupLogLevel(c)
Expand Down Expand Up @@ -809,6 +830,7 @@ func main() {
BackendType: backendType,
BackendConfig: backendConfig,
ExpectedArch: arch,
DecryptKeys: c.StringSlice("decrypt-keys"),
})
if err != nil {
return err
Expand Down Expand Up @@ -921,6 +943,14 @@ func main() {
Usage: "Path to the nydus-image binary, default to search in PATH",
EnvVars: []string{"NYDUS_IMAGE"},
},

&cli.StringSliceFlag{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Name: "encrypt-recipients",
Value: nil,
Usage: "Recipients to encrypt the nydus bootstrap layer, like " +
"jwe:<public-key-file-path>, provider:<cmd/gprc>, pgp:<email-address>, pkcs7:<x509-file-path>",
EnvVars: []string{"ENCRYPT_RECIPIENTS"},
},
},
Before: func(ctx *cli.Context) error {
sourcePath := ctx.String("source-dir")
Expand Down Expand Up @@ -958,10 +988,11 @@ func main() {
}

if p, err = packer.New(packer.Opt{
LogLevel: logrus.GetLevel(),
NydusImagePath: c.String("nydus-image"),
OutputDir: c.String("output-dir"),
BackendConfig: backendConfig,
LogLevel: logrus.GetLevel(),
NydusImagePath: c.String("nydus-image"),
OutputDir: c.String("output-dir"),
BackendConfig: backendConfig,
EncryptRecipients: c.StringSlice("encrypt-recipients"),
}); err != nil {
return err
}
Expand All @@ -978,6 +1009,7 @@ func main() {
Parent: c.String("parent-bootstrap"),
TryCompact: c.Bool("compact"),
CompactConfigPath: c.String("compact-config-file"),
Encrypt: len(c.StringSlice("encrypt-recipients")) != 0,
}); err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion contrib/nydusify/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.56
github.com/aws/aws-sdk-go-v2/service/s3 v1.30.6
github.com/containerd/containerd v1.7.2
github.com/containers/ocicrypt v1.1.7
github.com/docker/cli v23.0.3+incompatible
github.com/docker/distribution v2.8.2+incompatible
github.com/goharbor/acceleration-service v0.2.6
Expand Down Expand Up @@ -58,7 +59,6 @@ require (
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
github.com/containerd/ttrpc v1.2.2 // indirect
github.com/containerd/typeurl/v2 v2.1.1 // indirect
github.com/containers/ocicrypt v1.1.7 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
Expand Down
5 changes: 5 additions & 0 deletions contrib/nydusify/pkg/build/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type BuilderOption struct {
Compressor string
ChunkSize string
FsVersion string
Encrypt bool
}

type CompactOption struct {
Expand Down Expand Up @@ -143,6 +144,10 @@ func (builder *Builder) Run(option BuilderOption) error {
args = append(args, "--chunk-size", option.ChunkSize)
}

if option.Encrypt {
args = append(args, "--encrypt")
}

args = append(args, option.RootfsPath)

return builder.run(args, option.PrefetchPatterns)
Expand Down
3 changes: 2 additions & 1 deletion contrib/nydusify/pkg/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Opt struct {
BackendType string
BackendConfig string
ExpectedArch string
DecryptKeys []string
}

// Checker validates Nydus image manifest, bootstrap and mounts filesystem
Expand Down Expand Up @@ -119,7 +120,7 @@ func (checker *Checker) check(ctx context.Context) error {
return errors.Wrap(err, "create work directory")
}

if err := checker.Output(ctx, sourceParsed, targetParsed, checker.WorkDir); err != nil {
if err := checker.Output(ctx, sourceParsed, targetParsed, checker.WorkDir, checker.Opt); err != nil {
return errors.Wrap(err, "output image information")
}

Expand Down
10 changes: 9 additions & 1 deletion contrib/nydusify/pkg/checker/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ func prettyDump(obj interface{}, name string) error {
// Output outputs OCI and Nydus image manifest, index, config to JSON file.
// Prefer to use source image to output OCI image information.
func (checker *Checker) Output(
ctx context.Context, sourceParsed, targetParsed *parser.Parsed, outputPath string,
ctx context.Context, sourceParsed, targetParsed *parser.Parsed, outputPath string, opt Opt,
) error {
logrus.Infof("Dumping OCI and Nydus manifests to %s", outputPath)

Expand Down Expand Up @@ -87,6 +87,14 @@ func (checker *Checker) Output(
}
defer bootstrapReader.Close()

if len(opt.DecryptKeys) != 0 && utils.IsEncryptedNydusImage(&targetParsed.NydusImage.Manifest) {
logrus.Infof("Decrypting Nydus bootstrap layer")
bootstrapReader, err = checker.targetParser.DecryptNydusBootstrap(ctx, bootstrapReader, targetParsed.NydusImage, opt.DecryptKeys)
if err != nil {
return errors.Wrap(err, "decrypt Nydus bootstrap layer")
}
}

if err := utils.UnpackFile(bootstrapReader, utils.BootstrapFileNameInLayer, target); err != nil {
return errors.Wrap(err, "unpack Nydus bootstrap layer")
}
Expand Down
3 changes: 3 additions & 0 deletions contrib/nydusify/pkg/converter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package converter

import (
"strconv"
"strings"
)

func getConfig(opt Opt) map[string]string {
Expand Down Expand Up @@ -36,5 +37,7 @@ func getConfig(opt Opt) map[string]string {
cfg["cache_version"] = opt.CacheVersion
cfg["cache_max_records"] = strconv.FormatUint(uint64(opt.CacheMaxRecords), 10)

cfg["encrypt_recipients"] = strings.Join(opt.EncryptRecipients, ",")

return cfg
}
21 changes: 11 additions & 10 deletions contrib/nydusify/pkg/converter/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@ type Opt struct {
BackendConfig string
BackendForcePush bool

MergePlatform bool
Docker2OCI bool
FsVersion string
FsAlignChunk bool
Compressor string
ChunkSize string
BatchSize string
PrefetchPatterns string
OCIRef bool
WithReferrer bool
MergePlatform bool
Docker2OCI bool
FsVersion string
FsAlignChunk bool
Compressor string
ChunkSize string
BatchSize string
PrefetchPatterns string
OCIRef bool
WithReferrer bool
EncryptRecipients []string

AllPlatforms bool
Platforms string
Expand Down
11 changes: 7 additions & 4 deletions contrib/nydusify/pkg/packer/packer.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ var (
)

type Opt struct {
LogLevel logrus.Level
NydusImagePath string
OutputDir string
BackendConfig BackendConfig
LogLevel logrus.Level
NydusImagePath string
OutputDir string
BackendConfig BackendConfig
EncryptRecipients []string
}

type Builder interface {
Expand Down Expand Up @@ -63,6 +64,7 @@ type PackRequest struct {
Parent string
TryCompact bool
CompactConfigPath string
Encrypt bool
}

type PackResult struct {
Expand Down Expand Up @@ -253,6 +255,7 @@ func (p *Packer) Pack(_ context.Context, req PackRequest) (PackResult, error) {
Compressor: req.Compressor,
ChunkSize: req.ChunkSize,
FsVersion: req.FsVersion,
Encrypt: req.Encrypt,
}); err != nil {
return PackResult{}, errors.Wrapf(err, "failed to build image from directory %s", req.SourceDir)
}
Expand Down
21 changes: 20 additions & 1 deletion contrib/nydusify/pkg/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"strings"

"github.com/containers/ocicrypt"
enchelpers "github.com/containers/ocicrypt/helpers"
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/remote"
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/utils"

Expand Down Expand Up @@ -66,7 +69,8 @@ func FindNydusBootstrapDesc(manifest *ocispec.Manifest) *ocispec.Descriptor {
if len(layers) != 0 {
desc := &layers[len(layers)-1]
if (desc.MediaType == ocispec.MediaTypeImageLayerGzip ||
desc.MediaType == images.MediaTypeDockerSchema2LayerGzip) &&
desc.MediaType == images.MediaTypeDockerSchema2LayerGzip ||
desc.MediaType == images.MediaTypeImageLayerGzipEncrypted) &&
desc.Annotations[utils.LayerAnnotationNydusBootstrap] == "true" {
return desc
}
Expand Down Expand Up @@ -174,6 +178,21 @@ func (parser *Parser) PullNydusBootstrap(ctx context.Context, image *Image) (io.
return reader, nil
}

// Decrypt Nydus bootstrap layer if decryption key is provided.
func (parser *Parser) DecryptNydusBootstrap(ctx context.Context, reader io.Reader, image *Image, decryptKeys []string) (io.ReadCloser, error) {
bootstrapDesc := FindNydusBootstrapDesc(&image.Manifest)
dcc, err := enchelpers.CreateCryptoConfig([]string{}, decryptKeys)
if err != nil {
return nil, errors.Wrap(err, "Create crypto config failed")
}

resultReader, _, err := ocicrypt.DecryptLayer(dcc.DecryptConfig, reader, *bootstrapDesc, false)
if err != nil {
return nil, errors.Wrap(err, "Decrypt Nydus bootstrap layer")
}
return ioutil.NopCloser(resultReader), nil
}

func (parser *Parser) matchImagePlatform(desc *ocispec.Descriptor) bool {
if parser.interestedArch == desc.Platform.Architecture && desc.Platform.OS == "linux" {
return true
Expand Down
1 change: 1 addition & 0 deletions contrib/nydusify/pkg/utils/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
LayerAnnotationNydusBootstrap = "containerd.io/snapshot/nydus-bootstrap"
LayerAnnotationNydusFsVersion = "containerd.io/snapshot/nydus-fs-version"
LayerAnnotationNydusSourceChainID = "containerd.io/snapshot/nydus-source-chainid"
LayerAnnotationNydusEncryptedBlob = "containerd.io/snapshot/nydus-encrypted-blob"

LayerAnnotationNydusReferenceBlobIDs = "containerd.io/snapshot/nydus-reference-blob-ids"

Expand Down
35 changes: 35 additions & 0 deletions contrib/nydusify/pkg/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/containerd/containerd/archive/compression"
"github.com/containerd/containerd/images"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
Expand Down Expand Up @@ -224,3 +225,37 @@ func HashFile(path string) ([]byte, error) {

return hasher.Sum(nil), nil
}

// IsEncryptedNydusImage checks if the nydus image is encrypted.
func IsEncryptedNydusImage(manifest *ocispec.Manifest) bool {
layers := manifest.Layers
if len(layers) != 0 {
desc := layers[len(layers)-1]
if IsEncryptedNydusBootstrap(desc) {
return true
}
}
return false
}

// IsEncryptedNydusBlob returns true when the specified descriptor is nydus encrypted blob.
func IsEncryptedNydusBlob(desc ocispec.Descriptor) bool {
if desc.Annotations == nil {
return false
}

_, hasAnno := desc.Annotations[LayerAnnotationNydusEncryptedBlob]
return hasAnno
}

// IsEncryptedNydusBootstrap returns true when the specified descriptor is nydus encrypted bootstrap.
func IsEncryptedNydusBootstrap(desc ocispec.Descriptor) bool {
if desc.Annotations == nil {
return false
}

_, hasAnno := desc.Annotations[LayerAnnotationNydusBootstrap]
encrypted := desc.MediaType == images.MediaTypeImageLayerEncrypted ||
desc.MediaType == images.MediaTypeImageLayerGzipEncrypted
return encrypted && hasAnno
}
Loading