Skip to content

Commit 11d7a57

Browse files
committed
feat: expand Nydusify to support encrypted Nydus images
* convert: generate encrypted Ndyus images from OCI images. * check: check encrypted Nydus images. * mount: mount encrypted Nydus images. * build: build Nydus image with encrypted data blobs from a source directory. Signed-off-by: taohong <[email protected]>
1 parent 4876289 commit 11d7a57

File tree

12 files changed

+149
-32
lines changed

12 files changed

+149
-32
lines changed

contrib/nydusify/cmd/nydusify.go

+44-12
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,13 @@ func main() {
424424
Usage: "File path to save the metrics collected during conversion in JSON format, for example: './output.json'",
425425
EnvVars: []string{"OUTPUT_JSON"},
426426
},
427+
&cli.StringSliceFlag{
428+
Name: "encrypt-recipients",
429+
Value: nil,
430+
Usage: "Recipients to encrypt the nydus bootstrap layer, like " +
431+
"jwe:<public-key-file-path>, provider:<cmd/gprc>, pgp:<email-address>, pkcs7:<x509-file-path>",
432+
EnvVars: []string{"ENCRYPT_RECIPIENTS"},
433+
},
427434
},
428435
Action: func(c *cli.Context) error {
429436
setupLogLevel(c)
@@ -506,14 +513,15 @@ func main() {
506513
ChunkDictRef: chunkDictRef,
507514
ChunkDictInsecure: c.Bool("chunk-dict-insecure"),
508515

509-
PrefetchPatterns: prefetchPatterns,
510-
MergePlatform: c.Bool("merge-platform"),
511-
Docker2OCI: docker2OCI,
512-
FsVersion: fsVersion,
513-
FsAlignChunk: c.Bool("backend-aligned-chunk") || c.Bool("fs-align-chunk"),
514-
Compressor: c.String("compressor"),
515-
ChunkSize: c.String("chunk-size"),
516-
BatchSize: c.String("batch-size"),
516+
PrefetchPatterns: prefetchPatterns,
517+
MergePlatform: c.Bool("merge-platform"),
518+
Docker2OCI: docker2OCI,
519+
FsVersion: fsVersion,
520+
FsAlignChunk: c.Bool("backend-aligned-chunk") || c.Bool("fs-align-chunk"),
521+
Compressor: c.String("compressor"),
522+
ChunkSize: c.String("chunk-size"),
523+
BatchSize: c.String("batch-size"),
524+
EncryptRecipients: c.StringSlice("encrypt-recipients"),
517525

518526
OCIRef: c.Bool("oci-ref"),
519527
WithReferrer: c.Bool("with-referrer"),
@@ -605,6 +613,12 @@ func main() {
605613
Usage: "Path to the nydusd binary, default to search in PATH",
606614
EnvVars: []string{"NYDUSD"},
607615
},
616+
&cli.StringSliceFlag{
617+
Name: "decrypt-keys",
618+
Value: nil,
619+
Usage: "Keys to decrypt nydus bootstrap layer.",
620+
EnvVars: []string{"DECRYPT_KEYS"},
621+
},
608622
},
609623
Action: func(c *cli.Context) error {
610624
setupLogLevel(c)
@@ -631,6 +645,7 @@ func main() {
631645
BackendType: backendType,
632646
BackendConfig: backendConfig,
633647
ExpectedArch: arch,
648+
DecryptKeys: c.StringSlice("decrypt-keys"),
634649
})
635650
if err != nil {
636651
return err
@@ -702,6 +717,12 @@ func main() {
702717
Usage: "The nydusd binary path, if unset, search in PATH environment",
703718
EnvVars: []string{"NYDUSD"},
704719
},
720+
&cli.StringSliceFlag{
721+
Name: "decrypt-keys",
722+
Value: nil,
723+
Usage: "Keys to decrypt nydus bootstrap layer.",
724+
EnvVars: []string{"DECRYPT_KEYS"},
725+
},
705726
},
706727
Action: func(c *cli.Context) error {
707728
setupLogLevel(c)
@@ -746,6 +767,7 @@ func main() {
746767
BackendType: backendType,
747768
BackendConfig: backendConfig,
748769
ExpectedArch: arch,
770+
DecryptKeys: c.StringSlice("decrypt-keys"),
749771
})
750772
if err != nil {
751773
return err
@@ -858,6 +880,14 @@ func main() {
858880
Usage: "Path to the nydus-image binary, default to search in PATH",
859881
EnvVars: []string{"NYDUS_IMAGE"},
860882
},
883+
884+
&cli.StringSliceFlag{
885+
Name: "encrypt-recipients",
886+
Value: nil,
887+
Usage: "Recipients to encrypt the nydus bootstrap layer, like " +
888+
"jwe:<public-key-file-path>, provider:<cmd/gprc>, pgp:<email-address>, pkcs7:<x509-file-path>",
889+
EnvVars: []string{"ENCRYPT_RECIPIENTS"},
890+
},
861891
},
862892
Before: func(ctx *cli.Context) error {
863893
sourcePath := ctx.String("source-dir")
@@ -895,10 +925,11 @@ func main() {
895925
}
896926

897927
if p, err = packer.New(packer.Opt{
898-
LogLevel: logrus.GetLevel(),
899-
NydusImagePath: c.String("nydus-image"),
900-
OutputDir: c.String("output-dir"),
901-
BackendConfig: backendConfig,
928+
LogLevel: logrus.GetLevel(),
929+
NydusImagePath: c.String("nydus-image"),
930+
OutputDir: c.String("output-dir"),
931+
BackendConfig: backendConfig,
932+
EncryptRecipients: c.StringSlice("encrypt-recipients"),
902933
}); err != nil {
903934
return err
904935
}
@@ -915,6 +946,7 @@ func main() {
915946
Parent: c.String("parent-bootstrap"),
916947
TryCompact: c.Bool("compact"),
917948
CompactConfigPath: c.String("compact-config-file"),
949+
Encrypt: len(c.StringSlice("encrypt-recipients")) != 0,
918950
}); err != nil {
919951
return err
920952
}

contrib/nydusify/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.56
1111
github.com/aws/aws-sdk-go-v2/service/s3 v1.30.6
1212
github.com/containerd/containerd v1.7.2
13+
github.com/containers/ocicrypt v1.1.7
1314
github.com/docker/cli v23.0.3+incompatible
1415
github.com/docker/distribution v2.8.2+incompatible
1516
github.com/goharbor/acceleration-service v0.2.6
@@ -58,7 +59,6 @@ require (
5859
github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect
5960
github.com/containerd/ttrpc v1.2.2 // indirect
6061
github.com/containerd/typeurl/v2 v2.1.1 // indirect
61-
github.com/containers/ocicrypt v1.1.7 // indirect
6262
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
6363
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
6464
github.com/davecgh/go-spew v1.1.1 // indirect

contrib/nydusify/pkg/build/builder.go

+5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type BuilderOption struct {
2929
Compressor string
3030
ChunkSize string
3131
FsVersion string
32+
Encrypt bool
3233
}
3334

3435
type CompactOption struct {
@@ -139,6 +140,10 @@ func (builder *Builder) Run(option BuilderOption) error {
139140
args = append(args, "--chunk-size", option.ChunkSize)
140141
}
141142

143+
if option.Encrypt {
144+
args = append(args, "--encrypt")
145+
}
146+
142147
args = append(args, option.RootfsPath)
143148

144149
return builder.run(args, option.PrefetchPatterns)

contrib/nydusify/pkg/checker/checker.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type Opt struct {
3434
BackendType string
3535
BackendConfig string
3636
ExpectedArch string
37+
DecryptKeys []string
3738
}
3839

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

122-
if err := checker.Output(ctx, sourceParsed, targetParsed, checker.WorkDir); err != nil {
123+
if err := checker.Output(ctx, sourceParsed, targetParsed, checker.WorkDir, checker.Opt); err != nil {
123124
return errors.Wrap(err, "output image information")
124125
}
125126

contrib/nydusify/pkg/checker/output.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func prettyDump(obj interface{}, name string) error {
2828
// Output outputs OCI and Nydus image manifest, index, config to JSON file.
2929
// Prefer to use source image to output OCI image information.
3030
func (checker *Checker) Output(
31-
ctx context.Context, sourceParsed, targetParsed *parser.Parsed, outputPath string,
31+
ctx context.Context, sourceParsed, targetParsed *parser.Parsed, outputPath string, opt Opt,
3232
) error {
3333
logrus.Infof("Dumping OCI and Nydus manifests to %s", outputPath)
3434

@@ -87,6 +87,14 @@ func (checker *Checker) Output(
8787
}
8888
defer bootstrapReader.Close()
8989

90+
if len(opt.DecryptKeys) != 0 && utils.IsEncryptedNydusImage(&targetParsed.NydusImage.Manifest) {
91+
logrus.Infof("Decrypting Nydus bootstrap layer")
92+
bootstrapReader, err = checker.targetParser.DecryptNydusBootstrap(ctx, bootstrapReader, targetParsed.NydusImage, opt.DecryptKeys)
93+
if err != nil {
94+
return errors.Wrap(err, "decrypt Nydus bootstrap layer")
95+
}
96+
}
97+
9098
if err := utils.UnpackFile(bootstrapReader, utils.BootstrapFileNameInLayer, target); err != nil {
9199
return errors.Wrap(err, "unpack Nydus bootstrap layer")
92100
}

contrib/nydusify/pkg/converter/config.go

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package converter
66

77
import (
88
"strconv"
9+
"strings"
910
)
1011

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

40+
cfg["encrypt_recipients"] = strings.Join(opt.EncryptRecipients, ",")
41+
3942
return cfg
4043
}

contrib/nydusify/pkg/converter/converter.go

+11-10
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,17 @@ type Opt struct {
3636
BackendConfig string
3737
BackendForcePush bool
3838

39-
MergePlatform bool
40-
Docker2OCI bool
41-
FsVersion string
42-
FsAlignChunk bool
43-
Compressor string
44-
ChunkSize string
45-
BatchSize string
46-
PrefetchPatterns string
47-
OCIRef bool
48-
WithReferrer bool
39+
MergePlatform bool
40+
Docker2OCI bool
41+
FsVersion string
42+
FsAlignChunk bool
43+
Compressor string
44+
ChunkSize string
45+
BatchSize string
46+
PrefetchPatterns string
47+
OCIRef bool
48+
WithReferrer bool
49+
EncryptRecipients []string
4950

5051
AllPlatforms bool
5152
Platforms string

contrib/nydusify/pkg/packer/packer.go

+7-4
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ var (
2828
)
2929

3030
type Opt struct {
31-
LogLevel logrus.Level
32-
NydusImagePath string
33-
OutputDir string
34-
BackendConfig BackendConfig
31+
LogLevel logrus.Level
32+
NydusImagePath string
33+
OutputDir string
34+
BackendConfig BackendConfig
35+
EncryptRecipients []string
3536
}
3637

3738
type Builder interface {
@@ -63,6 +64,7 @@ type PackRequest struct {
6364
Parent string
6465
TryCompact bool
6566
CompactConfigPath string
67+
Encrypt bool
6668
}
6769

6870
type PackResult struct {
@@ -253,6 +255,7 @@ func (p *Packer) Pack(_ context.Context, req PackRequest) (PackResult, error) {
253255
Compressor: req.Compressor,
254256
ChunkSize: req.ChunkSize,
255257
FsVersion: req.FsVersion,
258+
Encrypt: req.Encrypt,
256259
}); err != nil {
257260
return PackResult{}, errors.Wrapf(err, "failed to build image from directory %s", req.SourceDir)
258261
}

contrib/nydusify/pkg/parser/parser.go

+20-1
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import (
99
"encoding/json"
1010
"fmt"
1111
"io"
12+
"io/ioutil"
1213
"strings"
1314

15+
"github.com/containers/ocicrypt"
16+
enchelpers "github.com/containers/ocicrypt/helpers"
1417
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/remote"
1518
"github.com/dragonflyoss/image-service/contrib/nydusify/pkg/utils"
1619

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

181+
// Decrypt Nydus bootstrap layer if decryption key is provided.
182+
func (parser *Parser) DecryptNydusBootstrap(ctx context.Context, reader io.Reader, image *Image, decryptKeys []string) (io.ReadCloser, error) {
183+
bootstrapDesc := FindNydusBootstrapDesc(&image.Manifest)
184+
dcc, err := enchelpers.CreateCryptoConfig([]string{}, decryptKeys)
185+
if err != nil {
186+
return nil, errors.Wrap(err, "Create crypto config failed")
187+
}
188+
189+
resultReader, _, err := ocicrypt.DecryptLayer(dcc.DecryptConfig, reader, *bootstrapDesc, false)
190+
if err != nil {
191+
return nil, errors.Wrap(err, "Decrypt Nydus bootstrap layer")
192+
}
193+
return ioutil.NopCloser(resultReader), nil
194+
}
195+
177196
func (parser *Parser) matchImagePlatform(desc *ocispec.Descriptor) bool {
178197
if parser.interestedArch == desc.Platform.Architecture && desc.Platform.OS == "linux" {
179198
return true

contrib/nydusify/pkg/utils/constant.go

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const (
1717
LayerAnnotationNydusBootstrap = "containerd.io/snapshot/nydus-bootstrap"
1818
LayerAnnotationNydusFsVersion = "containerd.io/snapshot/nydus-fs-version"
1919
LayerAnnotationNydusSourceChainID = "containerd.io/snapshot/nydus-source-chainid"
20+
LayerAnnotationNydusEncryptedBlob = "containerd.io/snapshot/nydus-encrypted-blob"
2021

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

contrib/nydusify/pkg/utils/utils.go

+35
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"time"
1616

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

225226
return hasher.Sum(nil), nil
226227
}
228+
229+
// IsEncryptedNydusImage checks if the nydus image is encrypted.
230+
func IsEncryptedNydusImage(manifest *ocispec.Manifest) bool {
231+
layers := manifest.Layers
232+
if len(layers) != 0 {
233+
desc := layers[len(layers)-1]
234+
if IsEncryptedNydusBootstrap(desc) {
235+
return true
236+
}
237+
}
238+
return false
239+
}
240+
241+
// IsEncryptedNydusBlob returns true when the specified descriptor is nydus encrypted blob.
242+
func IsEncryptedNydusBlob(desc ocispec.Descriptor) bool {
243+
if desc.Annotations == nil {
244+
return false
245+
}
246+
247+
_, hasAnno := desc.Annotations[LayerAnnotationNydusEncryptedBlob]
248+
return hasAnno
249+
}
250+
251+
// IsEncryptedNydusBootstrap returns true when the specified descriptor is nydus encrypted bootstrap.
252+
func IsEncryptedNydusBootstrap(desc ocispec.Descriptor) bool {
253+
if desc.Annotations == nil {
254+
return false
255+
}
256+
257+
_, hasAnno := desc.Annotations[LayerAnnotationNydusBootstrap]
258+
encrypted := desc.MediaType == images.MediaTypeImageLayerEncrypted ||
259+
desc.MediaType == images.MediaTypeImageLayerGzipEncrypted
260+
return encrypted && hasAnno
261+
}

0 commit comments

Comments
 (0)