Skip to content
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
33 changes: 29 additions & 4 deletions pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@ type PackManifestOptions struct {

// ConfigDescriptor is a pointer to the descriptor of the config blob.
// If not nil, ConfigAnnotations will be ignored.
//
// When set, the caller is responsible for pushing the referenced config
// blob to the target and for populating the descriptor's Digest and Size.
// PackManifest uses the descriptor as-is and does NOT push the config blob.
// The descriptor's Digest MUST be valid, otherwise PackManifest returns an
// error wrapping [errdef.ErrInvalidDigest].
ConfigDescriptor *ocispec.Descriptor

// ConfigAnnotations is the annotation map of the config descriptor.
Expand Down Expand Up @@ -225,8 +231,8 @@ func packManifestV1_0(ctx context.Context, pusher content.Pusher, artifactType s
// prepare config
var configDesc ocispec.Descriptor
if opts.ConfigDescriptor != nil {
if err := validateMediaType(opts.ConfigDescriptor.MediaType); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("invalid config mediaType format: %w", err)
if err := validateConfigDescriptor(*opts.ConfigDescriptor); err != nil {
return ocispec.Descriptor{}, err
}
configDesc = *opts.ConfigDescriptor
} else {
Expand Down Expand Up @@ -272,6 +278,9 @@ func packManifestV1_1_RC2(ctx context.Context, pusher content.Pusher, configMedi
// prepare config
var configDesc ocispec.Descriptor
if opts.ConfigDescriptor != nil {
if err := validateConfigDescriptor(*opts.ConfigDescriptor); err != nil {
return ocispec.Descriptor{}, err
}
configDesc = *opts.ConfigDescriptor
} else {
var err error
Expand Down Expand Up @@ -318,8 +327,8 @@ func packManifestV1_1(ctx context.Context, pusher content.Pusher, artifactType s
var emptyBlobExists bool
var configDesc ocispec.Descriptor
if opts.ConfigDescriptor != nil {
if err := validateMediaType(opts.ConfigDescriptor.MediaType); err != nil {
return ocispec.Descriptor{}, fmt.Errorf("invalid config mediaType format: %w", err)
if err := validateConfigDescriptor(*opts.ConfigDescriptor); err != nil {
return ocispec.Descriptor{}, err
}
configDesc = *opts.ConfigDescriptor
} else {
Expand Down Expand Up @@ -446,3 +455,19 @@ func validateMediaType(mediaType string) error {
}
return nil
}

// validateConfigDescriptor validates a caller-supplied config descriptor.
// When a config descriptor is provided to Pack or PackManifest, the caller is
// responsible for pushing the config blob and populating the descriptor's
// Digest and Size. Validating the descriptor here prevents silently producing
// a manifest that references a non-existent config blob (e.g. an empty digest),
// which some registries accept while dropping the descriptor's annotations.
func validateConfigDescriptor(config ocispec.Descriptor) error {
if err := validateMediaType(config.MediaType); err != nil {
return fmt.Errorf("invalid config mediaType format: %w", err)
}
if err := config.Digest.Validate(); err != nil {
return fmt.Errorf("invalid config descriptor digest %q: %w", config.Digest, errdef.ErrInvalidDigest)
}
return nil
}
61 changes: 61 additions & 0 deletions pack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,27 @@ func Test_Pack_ImageV1_1_RC2_InvalidDateTimeFormat(t *testing.T) {
}
}

func Test_Pack_ImageV1_1_RC2_InvalidConfigDigest(t *testing.T) {
s := memory.New()

ctx := context.Background()
// a config descriptor with a valid media type but an empty (invalid) digest,
// as produced when the caller forgets to push the config blob and populate
// the descriptor. See https://github.com/oras-project/oras-go/issues/1236
configDesc := ocispec.Descriptor{
MediaType: "application/vnd.test.config",
Annotations: map[string]string{"foo": "bar"},
}
opts := PackOptions{
PackImageManifest: true,
ConfigDescriptor: &configDesc,
}
_, err := Pack(ctx, s, "", nil, opts)
if wantErr := errdef.ErrInvalidDigest; !errors.Is(err, wantErr) {
t.Errorf("Oras.Pack() error = %v, wantErr = %v", err, wantErr)
}
}

func Test_PackManifest_ImageV1_0(t *testing.T) {
s := memory.New()

Expand Down Expand Up @@ -786,6 +807,26 @@ func Test_PackManifest_ImageV1_0_InvalidMediaType(t *testing.T) {
}
}

func Test_PackManifest_ImageV1_0_InvalidConfigDigest(t *testing.T) {
s := memory.New()

ctx := context.Background()
// a config descriptor with a valid media type but an empty (invalid) digest,
// as produced when the caller forgets to push the config blob and populate
// the descriptor. See https://github.com/oras-project/oras-go/issues/1236
configDesc := ocispec.Descriptor{
MediaType: "application/vnd.test.config",
Annotations: map[string]string{"foo": "bar"},
}
opts := PackManifestOptions{
ConfigDescriptor: &configDesc,
}
_, err := PackManifest(ctx, s, PackManifestVersion1_0, "", opts)
if wantErr := errdef.ErrInvalidDigest; !errors.Is(err, wantErr) {
t.Errorf("Oras.PackManifest() error = %v, wantErr = %v", err, wantErr)
}
}

func Test_PackManifest_ImageV1_0_InvalidDateTimeFormat(t *testing.T) {
s := memory.New()

Expand Down Expand Up @@ -1065,6 +1106,26 @@ func Test_PackManifest_ImageV1_1_InvalidMediaType(t *testing.T) {
}
}

func Test_PackManifest_ImageV1_1_InvalidConfigDigest(t *testing.T) {
s := memory.New()

ctx := context.Background()
// a config descriptor with a valid media type but an empty (invalid) digest,
// as produced when the caller forgets to push the config blob and populate
// the descriptor. See https://github.com/oras-project/oras-go/issues/1236
configDesc := ocispec.Descriptor{
MediaType: "application/vnd.test.config",
Annotations: map[string]string{"foo": "bar"},
}
opts := PackManifestOptions{
ConfigDescriptor: &configDesc,
}
_, err := PackManifest(ctx, s, PackManifestVersion1_1, "application/vnd.test", opts)
if wantErr := errdef.ErrInvalidDigest; !errors.Is(err, wantErr) {
t.Errorf("Oras.PackManifest() error = %v, wantErr = %v", err, wantErr)
}
}

func Test_PackManifest_ImageV1_1_InvalidDateTimeFormat(t *testing.T) {
s := memory.New()

Expand Down
Loading