-
Notifications
You must be signed in to change notification settings - Fork 380
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
Conversation
5f3231a
to
ccc935b
Compare
402841d
to
38685e8
Compare
@vrothberg @mtrmac @rhatdan ptal. |
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. |
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. |
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 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 |
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 |
@lumjjb sounds good 👍 I can create the repository for you if give me the desired name for the repo. |
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. |
@lumjjb I have created https://github.com/containers/ocicrypt. Who all should be added as maintainers? :) |
@lumjjb I have given you admin rights, so you should be able to manage the repo :) |
@mrunalp Awesome Thanks!! |
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. |
fbfe8c7
to
8df5dc1
Compare
Updated the PR to vendor |
There was a problem hiding this 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.
} | ||
|
||
srcInfo.Digest = d | ||
srcInfo.Size = -1 |
There was a problem hiding this comment.
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.
copy/copy.go
Outdated
// not allow the image to be provisioned. | ||
if ic.checkAuthorization { | ||
if srcInfo.MediaType == manifest.DockerV2Schema2LayerGzipEncMediaType || | ||
srcInfo.MediaType == manifest.DockerV2Schema2LayerEncMediaType { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
(
Line 554 in 2178abd
pi, err := ic.src.UpdatedImage(ctx, *ic.manifestUpdates) |
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
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(...)
Generate RSA keys for testing
Skopeo (to make, we use
We have a PR open to update that branch to use the new changes with the |
28698e7
to
3165d1b
Compare
@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). |
There was a problem hiding this 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
@@ -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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
5c75f09
to
b333212
Compare
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) { |
There was a problem hiding this comment.
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?
There was a problem hiding this 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.
@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. |
2f8ff59
to
c937289
Compare
96f54f8
to
368d7cc
Compare
rebased |
368d7cc
to
53d9e53
Compare
rebased |
@mrunalp @rhatdan As per discussions. PR is up to date and ready to merge! Testing has been configured in containers/skopeo#732 as 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:
|
Looks like we have one more rebase... |
Signed-off-by: Brandon Lum <[email protected]>
Signed-off-by: Brandon Lum <[email protected]>
Signed-off-by: Brandon Lum <[email protected]>
Signed-off-by: Brandon Lum <[email protected]>
53d9e53
to
d978613
Compare
Signed-off-by: Brandon Lum <[email protected]>
d978613
to
a298da5
Compare
rebased and poked CI, seems to be stuck on "receiving job" |
Rebased and CI ran success! |
As discussed, merging now. Thanks @lumjjb for this great work! |
Awesome! Thank you! 👍 👍 👍 |
Both the referenced features were dropped in the final version of containers#673 which added these comments. Signed-off-by: Miloslav Trmač <[email protected]>
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
SupportsEncryption
as part ofImage
interface (backed bygenericManifest
)CryptoConfig
as part ofSystemContext
Manifest Changes -
manifest/
OCI1.UpdateLayerInfos
to also update Mediatype if providedSupportsEncryption
into thegenericManifest
interfaceCopy -
copy/
copy.Image
newDigestingReader
to skip checks in the case where digest is not known (i.e. during encryption, etc.)CryptoConfig
fromSystemContext
EncryptConfig
andEncryptLayers
tocopy.Options
to specify encryption when callingImage
imageCopier
andcopier
constructs to propagate crypto configurationcopyLayer
, with an addedtoEncrypt
parameter that is passed into the function.copy.Options
copy.copyAndUpdateManifest
applies changes to the src manifest, and docker schema1/2 lacks a way to define encryption via MediaTypes and Annotations.