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

Added Container Image Encryption/Decryption support #673

Merged
merged 5 commits into from
Nov 26, 2019

Conversation

lumjjb
Copy link
Contributor

@lumjjb lumjjb commented Aug 2, 2019

This PR contains a POC for image encryption/decryption. Based on encryption work as presented at KubeCon CN 2019. Currently, we have POCs for cri-o and skopeo that can consume this.

Available consumer examples:

Below is a summary of what is modified. We can squash the commits if preferred.

Types - types/types.go

  • Added SupportsEncryption as part of Image interface (backed by genericManifest)
  • Added CryptoConfig as part of SystemContext

Manifest Changes - manifest/

  • Added encryption mediatypes consts
  • Modified OCI1.UpdateLayerInfos to also update Mediatype if provided
  • Added SupportsEncryption into the genericManifest interface

Copy - copy/

  • In general: Added ability to encrypt/decrypt in copy.Image
  • Added parameter to newDigestingReader to skip checks in the case where digest is not known (i.e. during encryption, etc.)
  • Used CryptoConfig from SystemContext
  • Added EncryptConfig and EncryptLayers to copy.Options to specify encryption when calling Image
  • Modified imageCopier and copier constructs to propagate crypto configuration
  • All of the work for actual encryption/decryption of streams are handled in copyLayer, with an added toEncrypt parameter that is passed into the function.
  • Decryption is done on source if encrypted
  • Encryption is done on dest if specified in copy.Options
  • Note: Currently encryption supports OCI image as a source since copy.copyAndUpdateManifest applies changes to the src manifest, and docker schema1/2 lacks a way to define encryption via MediaTypes and Annotations.

@mrunalp mrunalp requested a review from vrothberg August 5, 2019 19:46
@lumjjb lumjjb force-pushed the encryption-policy branch 3 times, most recently from 402841d to 38685e8 Compare August 9, 2019 18:25
@mrunalp
Copy link
Contributor

mrunalp commented Aug 13, 2019

@vrothberg @mtrmac @rhatdan ptal.

@vrothberg
Copy link
Member

Hi @lumjjb, thanks for the PR! I won't find time to review this week to review but will have a look next week.

One thing I noticed while quickly skimming the code is that we add a lot of code from containerd. I wonder if we couldn't vendor in the packages from containerd directly (assuming no code changes needed)? The intention behind is to avoid redundancy and duplicate work, for instance, when fixing a regression.

@mrunalp
Copy link
Contributor

mrunalp commented Aug 13, 2019

I think such code should probably live in a library which can be imported into the CRI implementations as needed. We are happy to host it at github.com/containers if that works for you.

@lumjjb
Copy link
Contributor Author

lumjjb commented Aug 13, 2019

Yup - i am actually working on a commit now to remove that dependency. there are no hard dependencies on containerd. We were only using some errdefs which we didn't clean up properly. Pushing a commit now

EDIT: @vrothberg I think i see what you are talking about... the headers of the files that we had are actually no longer in containerd and will be an independent library. The containerd headers are just left over artifacts from before.

@lumjjb
Copy link
Contributor Author

lumjjb commented Aug 13, 2019

I think such code should probably live in a library which can be imported into the CRI implementations as needed. We are happy to host it at github.com/containers if that works for you.

yep! That sounds good. We have the code living in one of our repositories now... in https://github.com/stefanberger/imgcrypt/tree/master/pkg/encryption... Yea - i think it would be perfect if we could have one living in the containers/ path, and when we get this merged into OCI, we can decide if we should migrate it there.

@mrunalp
Copy link
Contributor

mrunalp commented Aug 13, 2019

@lumjjb sounds good 👍 I can create the repository for you if give me the desired name for the repo.

@lumjjb
Copy link
Contributor Author

lumjjb commented Aug 13, 2019

@lumjjb sounds good 👍 I can create the repository for you if give me the desired name for the repo.

@mrunalp Awesome, ocicrypt should be a good name! Thanks for hosting us :D

@rhatdan
Copy link
Member

rhatdan commented Aug 13, 2019

Yes my first response to this would be to get it into a vendor and then it would be easier to review. We don't want any exposed interfaces to this code in containers/image other then the ones we support.

@mrunalp
Copy link
Contributor

mrunalp commented Aug 13, 2019

@lumjjb I have created https://github.com/containers/ocicrypt. Who all should be added as maintainers? :)

@mrunalp
Copy link
Contributor

mrunalp commented Aug 13, 2019

@lumjjb I have given you admin rights, so you should be able to manage the repo :)

@lumjjb
Copy link
Contributor Author

lumjjb commented Aug 13, 2019

@mrunalp Awesome Thanks!!

@mrunalp
Copy link
Contributor

mrunalp commented Aug 13, 2019

I have added Stefan as well but now you should be able to add other people as needed. Let me know if something doesn't work.

@lumjjb lumjjb force-pushed the encryption-policy branch 4 times, most recently from fbfe8c7 to 8df5dc1 Compare August 13, 2019 20:49
@lumjjb
Copy link
Contributor Author

lumjjb commented Aug 14, 2019

Updated the PR to vendor containers/ocicrypt and re-did commits to help reviewing

copy/copy.go Outdated Show resolved Hide resolved
image/docker_schema1.go Outdated Show resolved Hide resolved
image/docker_schema2.go Outdated Show resolved Hide resolved
image/manifest.go Outdated Show resolved Hide resolved
@lumjjb
Copy link
Contributor Author

lumjjb commented Aug 15, 2019

Addressed @rhatdan 's comments in 25b2de6

Copy link
Member

@vrothberg vrothberg left a comment

Choose a reason for hiding this comment

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

Comments from the first round of review. Most comments are minor nits.

Before merging this PR, I want #563 and #639 to be merged, especially for having the fixes for correctly setting the mime types.

@lumjjb, let's wait for @mtrmac's comments before addressing mine.

@lumjjb, could you drop instructions how we can test the code? Ideally, I'd like to use Skopeo to copy an encrypted image from some registry.

copy/copy.go Show resolved Hide resolved
copy/copy.go Outdated Show resolved Hide resolved
copy/copy.go Outdated Show resolved Hide resolved
copy/copy.go Outdated Show resolved Hide resolved
copy/copy.go Show resolved Hide resolved
copy/copy.go Outdated Show resolved Hide resolved
}

srcInfo.Digest = d
srcInfo.Size = -1
Copy link
Member

Choose a reason for hiding this comment

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

Note to self: this looks like it could interfere with the progress bars.

vendor.conf Outdated Show resolved Hide resolved
copy/copy.go Outdated
// not allow the image to be provisioned.
if ic.checkAuthorization {
if srcInfo.MediaType == manifest.DockerV2Schema2LayerGzipEncMediaType ||
srcInfo.MediaType == manifest.DockerV2Schema2LayerEncMediaType {
Copy link
Member

Choose a reason for hiding this comment

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

It's unclear to me how this can work. The hard-coded mime types indicate that schema 2 would support encryption but ...

// SupportsEncryption returns if encryption is supported for the manifest type
func (m *manifestSchema2) SupportsEncryption(context.Context) bool {
	return false
}

Regarding hard-coding the .MediaType. I prefer adding an Encrypted bool field to BlobInfo and let the Manifest update it, so that the individual implementations can take care of it. We do something similar in https://github.com/containers/image/pull/563/files#diff-b3c9ca9ec48c31df0619e70595f4bd62R84-R91.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is a mistake on our part. We should rename DockerV2Schema2LayerGzipEncMediaType to use OCI instead.

I prefer adding an Encrypted bool field to BlobInfo and let the Manifest update it
I think this would work as well!

Kind of related to this - there is one issue that we were running into that is bugging us a little.

copyUpdatedConfigAndManifest(

pi, err := ic.src.UpdatedImage(ctx, *ic.manifestUpdates)
) updates the manifest of the SRC and then copies it over to DST. Which is a bit tricky here because dockerv2 doesn't support encryption, but it should not prevent encrypting a dockerv2 image into an OCI encrypted image. I was wondering if it could be done by copying SRC to DST then applying the layer updates? What are your thoughts about this?

Copy link
Member

Choose a reason for hiding this comment

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

I think this can be solved by doing something similar to https://github.com/containers/image/pull/563/files#diff-b3c9ca9ec48c31df0619e70595f4bd62R84-R91. While updating the manifests, we check each BlobInfo and update the mime types accordingly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think the src vs dst update would still be an issue - would you think we should move the code block from

image/copy/copy.go

Lines 541 to 559 in 2178abd

if !reflect.DeepEqual(*ic.manifestUpdates, types.ManifestUpdateOptions{InformationOnly: ic.manifestUpdates.InformationOnly}) {
if !ic.canModifyManifest {
return nil, errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden")
}
if !ic.diffIDsAreNeeded && ic.src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates) {
// We have set ic.diffIDsAreNeeded based on the preferred MIME type returned by determineManifestConversion.
// So, this can only happen if we are trying to upload using one of the other MIME type candidates.
// Because UpdatedImageNeedsLayerDiffIDs is true only when converting from s1 to s2, this case should only arise
// when ic.c.dest.SupportedManifestMIMETypes() includes both s1 and s2, the upload using s1 failed, and we are now trying s2.
// Supposedly s2-only registries do not exist or are extremely rare, so failing with this error message is good enough for now.
// If handling such registries turns out to be necessary, we could compute ic.diffIDsAreNeeded based on the full list of manifest MIME type candidates.
return nil, errors.Errorf("Can not convert image to %s, preparing DiffIDs for this case is not supported", ic.manifestUpdates.ManifestMIMEType)
}
pi, err := ic.src.UpdatedImage(ctx, *ic.manifestUpdates)
if err != nil {
return nil, errors.Wrap(err, "Error creating an updated image manifest")
}
pendingImage = pi
}

down, and so do something like

manifest, err := ic.c.src.Manifest(...)
...
if err := ic.c.dest.PutManifest(ctx, manifest); err != nil {
		return nil, errors.Wrap(err, "Error writing manifest")
}

_, err := ic.c.dest.UpdatedImage(ctx, *ic.manifestUpdates)
if err != nil {
	return nil, errors.Wrap(err, "Error creating an updated image manifest")
}
manifest, err := ic.c.src.Manifest(...)

copy/copy.go Outdated Show resolved Hide resolved
@lumjjb
Copy link
Contributor Author

lumjjb commented Aug 19, 2019

Generate RSA keys for testing

openssl genrsa -out private.key 1024
openssl rsa -in private.key -pubout > public.key

Skopeo (to make, we use make binary-local due to some issue with container build).

# Encrypting image
./skopeo copy --dest-tls-verify=false --dest-recipient jwe:./public.key oci:nginx_local:latest  oci:skopeo_nginx_jwe:latest

# Decrypting image
./skopeo copy --src-tls-verify=false --src-key ./private.key  oci:skopeo_nginx_jwe:latest oci:nginx_ocilayout:latest

We have a PR open to update that branch to use the new changes with the ocicrypt refactor, PR is as follows: harche/skopeo#3. It's night time IST timezone, so will be merged in tomorrow probably.

@lumjjb
Copy link
Contributor Author

lumjjb commented Sep 11, 2019

@vrothberg addressed the nits and most of the comments in lumjjb#4. I think we can discuss the other ones during the call tomorrow!

@vrothberg
Copy link
Member

vrothberg commented Sep 12, 2019

@vrothberg addressed the nits and most of the comments in lumjjb#4. I think we can discuss the other ones during the call tomorrow!

Thanks, @lumjjb! I will have a look at the code today before the meeting; looking forward to it.

I was focusing on #563 which should give you a good base for setting the new media type(s).

Copy link
Member

@vrothberg vrothberg left a comment

Choose a reason for hiding this comment

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

A couple of (very) minor nits.

I need to have a close look at copyBlobFromStream(...) but need a fresher brain. I'm especially interested if we can simplify setting the media types a bit more.

@lumjjb, how would we expose encryption support on the CLI (e.g., skopeo copy ...)?

// 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.
Copy link
Member

Choose a reason for hiding this comment

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

Is the negative indexing a requirement or nice-to-have? It's a common source of bugs and unless needed, I'd prefer to have a positive indexing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's helpful in the containers images usecase, because people want to usually encrypt the top-most layers. If we can do this to the caller of copy.Image, that is fine as well, and we move the abstraction of negative indexing to the tool.

copy/copy.go Outdated Show resolved Hide resolved
copy/copy.go Outdated
@@ -117,6 +141,16 @@ type Options struct {
Progress chan types.ProgressProperties // Reported to when ProgressInterval has arrived for a single artifact+offset.
// manifest MIME type of image set by user. "" is default and means use the autodetection to the the manifest MIME type
ForceManifestMIMEType string
CheckAuthorization bool
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a description for it?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can make it a member of encconfig.EncryptConfig?

Copy link
Contributor Author

@lumjjb lumjjb Sep 12, 2019

Choose a reason for hiding this comment

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

Adding a description for CheckAuthorization.

The use of CheckAuthorization is a concept only for runtimes, so we abstracted it away from the main encryption library config. The main reason it's there is due to the implementation of caching in the runtime instead of something that is spec related. The runtimes use the higher level interface, which directly takes in a bool for CheckAuthorization (called unwrapOnly):

https://github.com/containers/ocicrypt/blob/master/encryption.go#L174


Extra info if you are curious about why encconfig.CryptoConfig is a public interface:

A little segway about the design of the encryption library. There are 2 layers of it, the low level layer only deals with crypto - nothing about OCI or container images. This has interfaces for crypto primitives. This is the main consumer of encconfig.CryptoConfig.

I think we will add a note to the file. The encconfig.* is package is designed for extensibility for crypto primitives - for better crypto agility.

https://github.com/containers/ocicrypt/blob/master/keywrap/keywrap.go#L25

The runtimes use the higher level interface, which directly takes in a bool for CheckAuthorization:

https://github.com/containers/ocicrypt/blob/master/encryption.go#L174

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding the comment:

    // CheckAuthorization is an indication if the image does not need to be decrypted, but only
    // requires checking if the it can be decrypted.
    // This is an optimization for cached images to see if provided keys have the authorization
    // to use the cached content without having to decrypt the layers again.

copy/copy.go Outdated Show resolved Hide resolved
copy/copy.go Outdated Show resolved Hide resolved
copy/copy.go Outdated Show resolved Hide resolved
copy/copy.go Outdated Show resolved Hide resolved
copy/copy.go Show resolved Hide resolved
copy/copy.go Outdated Show resolved Hide resolved
copy/copy.go Outdated Show resolved Hide resolved
copy/copy.go Outdated
@@ -257,6 +297,10 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli
return nil, errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(c.rawSource.Reference()))
}

if options.EncryptLayers != nil && !src.SupportsEncryption(ctx) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

  • Is the “TODO: Remove src.SupportsEncryption call…” item supposed to stay there, or should that already have been removed?

Copy link
Collaborator

@mtrmac mtrmac left a comment

Choose a reason for hiding this comment

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

Just one random implementation comment for now.

@lumjjb
Copy link
Contributor Author

lumjjb commented Nov 7, 2019

@mtrmac Updated with the changes we talked about during the call. - I think there's somethign we can do with the blob info cache, but I think it would be practically useful only when we get to cri-o, so we can look at that when we look at cri-o and KEP integration.

@lumjjb lumjjb force-pushed the encryption-policy branch 2 times, most recently from 2f8ff59 to c937289 Compare November 12, 2019 04:15
@lumjjb lumjjb force-pushed the encryption-policy branch 2 times, most recently from 96f54f8 to 368d7cc Compare November 22, 2019 20:50
@lumjjb
Copy link
Contributor Author

lumjjb commented Nov 22, 2019

rebased

@lumjjb
Copy link
Contributor Author

lumjjb commented Nov 24, 2019

rebased

@lumjjb
Copy link
Contributor Author

lumjjb commented Nov 24, 2019

@mrunalp @rhatdan As per discussions. PR is up to date and ready to merge!

Testing has been configured in containers/skopeo#732 as CopySuite.TestCopyEncryption , which we can merge right after this (CI passing).

In addition, several manual tests have also been carried out for additional verification: https://gist.github.com/lumjjb/bda1faf8086a57c1a9261d951b9ddb6a

The outstanding discussions are documented in:

@rhatdan
Copy link
Member

rhatdan commented Nov 25, 2019

Looks like we have one more rebase...

Signed-off-by: Brandon Lum <lumjjb@gmail.com>
Signed-off-by: Brandon Lum <lumjjb@gmail.com>
Signed-off-by: Brandon Lum <lumjjb@gmail.com>
Signed-off-by: Brandon Lum <lumjjb@gmail.com>
Signed-off-by: Brandon Lum <lumjjb@gmail.com>
@lumjjb
Copy link
Contributor Author

lumjjb commented Nov 25, 2019

rebased and poked CI, seems to be stuck on "receiving job"

@lumjjb
Copy link
Contributor Author

lumjjb commented Nov 26, 2019

Looks like we have one more rebase...

Rebased and CI ran success!

@vrothberg
Copy link
Member

As discussed, merging now. Thanks @lumjjb for this great work!

@vrothberg vrothberg merged commit 502848a into containers:master Nov 26, 2019
@lumjjb
Copy link
Contributor Author

lumjjb commented Nov 26, 2019

Awesome! Thank you! 👍 👍 👍

mtrmac added a commit to mtrmac/image that referenced this pull request Mar 10, 2023
Both the referenced features were dropped
in the final version of containers#673
which added these comments.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants