-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Controller for automatic image signature import #16293
Controller for automatic image signature import #16293
Conversation
@mfojtik: Your pull request title starts with "WIP", so the do-not-merge/work-in-progress label will be added. This label will ensure that your pull request will not be merged. Remove the prefix from your pull request title to trigger the removal of the label and allow for your pull request to be merged. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
@smarterclayton @deads2k only the last commit is interesting. My assumption is that the resync internal will cause all images to re-get their signatures every 10 minutes, we can bump that to 24h or something if needed. |
} | ||
|
||
updatedImage := t.(*imageapi.Image) | ||
updatedImage.Signatures = signatures |
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.
Does this overwrite all pre-existing signatures?
If so, most of the concerns in #15494 (comment) still apply.
(To be clear, syncing the signature status is a welcome improvement.)
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.
@mtrmac see the comment below.
} | ||
|
||
func (s *SignatureImportController) syncImageSignatures(key string) error { | ||
obj, exists, err := s.imageCache.GetByKey(key) |
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.
use a lister
// this means the request was successfully handled. We should "forget" the item so that any retry | ||
// later on is reset | ||
s.queue.Forget(key) | ||
} else { |
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.
return early and avoid the else
s.queue.Forget(key) | ||
} else { | ||
// if we had an error it means that we didn't handle it, which means that we want to requeue the work | ||
utilruntime.HandleError(fmt.Errorf("error syncing service, it will be retried: %v", err)) |
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.
not a service
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.
dammit... now you know from where I copied this ;-)
return err | ||
} | ||
if !exists { | ||
glog.V(4).Infof("Image has been deleted %v", key) |
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.
non-existence isn't noteworthy v(5) maybe
@@ -405,6 +406,6 @@ func (ui *unparsedImage) Manifest() ([]byte, string, error) { | |||
} | |||
|
|||
// Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need. | |||
func (ui *unparsedImage) Signatures() ([][]byte, error) { | |||
func (ui *unparsedImage) Signatures(context.Context) ([][]byte, error) { |
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.
you going ot use this to pass a timeout or something?
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.
@deads2k this is just implementing the interface in containers/image... this func now require context because it fetches stuff from remote locations...
glog.V(4).Infof("Failed to fetch image %s signatures: %v", image.Name, err) | ||
return err | ||
} | ||
if len(signatures) == 0 { |
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.
This looks odd. If the signatures are gone, why aren't we removing them?
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.
@deads2k that will be tricky... you will have to mark the ones that we imported automatically vs. those that user created manually... see my other comment
} | ||
|
||
updatedImage := t.(*imageapi.Image) | ||
updatedImage.Signatures = signatures |
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 this overwriting image signatures that a user manually added?
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.
good point... i guess what this should do is just add new signatures and do not override the existing once... @mtrmac will that fix your concerns as well? meaning that this is only going to write new signatures and don't remove existing signatures for the image.
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.
@mtrmac I want to sort out how we are going to sync the signatures in this controller.
We can add a common prefix for automatically imported signatures which we can safely remove when the sync runs and replace with new signatures (or delete them when we don't receive any from sigstore). That will make sure we won't touch signatures created by users manually.
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.
Only adding signatures, never removing them, would be a good 90% solution I can be very happy with.
The remaining 10/90% is marking a signature as obsolete, and probably not serving it to clients, when it is removed from the original registry (think a signature for :latest
, which should be served only for the truly latest image). This is more complex if several repos serve the same image (manifest digest) and the same signature, and only one of them stops serving that signature; it’s not really obvious what to do in that case. Ideally, I guess, the signature status for each imagestream should mirror the signature status of the upstream repository for that imagestream, but that seems like a pretty big change to the model.
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.
We can add a common prefix for automatically imported signatures
I’m not entirely sure what you mean; is that a constant like imported-signature
, or some identification of the specific image stream?
7756956
to
bce010b
Compare
} | ||
} | ||
|
||
// Only add the signatures that we received from the signature store. |
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.
Basically, we are not going to touch anything that is not managed by this controller. We going to refresh the signatures that are managed, but ignore those that already exists (and are possible verified). If we don't receive signature that we already have and we manage it, we going to nuke it regardless of its verification status. I guess this is OK (iow. a signature storage remove the signature because it is no longer valid/etc. so there is no reason for keeping it around).
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 agree that local verification status should not be a factor in this.
Removing a signature just because it is not available on one registry seems incorrect: AFAICT if the same image (manifest digest) is served by repo A, which serves signatures SA and S_common, and repo B, which serves signatures SB and S_common, the imported image’s signatures will keep flipping between (SA, S_common) and (SB, S_common), depending on which was the last repo synced. That doesn’t seem right; the imported image should have all of (SA, SB, S_common).
It’s clear enough that when A stops serving SA, the imported image should drop SA. It’s not obvious when S_common should be dropped. One of A/B may be the authoritative repo for S_common (or not truly authoritative but a semi-trusted mirror of the authoritative repo), and dropping the signature by the authoritative repo would be a good reason to drop it everywhere; or it may be a quite untrusted third-party copy (something on Docker Hub perhaps), which should not be able to cause the cluster to drop S_common. I can only think of changing the system to store the existence of S_common not per-manifest-digest, but per-stream, but that’s probably a big change. On balance, I’d prefer never removing signatures to removing shared signatures when they should be kept around; the redesign, or perhaps some other solution, can hopefully be done later.
(Even if a signature has been dropped by the authoritative source, and is no longer valid / applicable, there is a reason to keep the signature around for forensic investigations (“why was this backdoored image allowed to run on the cluster?”). But that’s clearly a lower-priority use case, and it is not necessarily best implemented in the registry: perhaps a per-node audit log would make more sense.)
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.
@mtrmac so to simplify it, we should not remove signatures that we already have but keep them on the image, right?
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.
That’s close enough for me, yes.
@@ -203,6 +205,10 @@ func BuildOpenshiftControllerConfig(options configapi.MasterConfig) (*OpenshiftC | |||
DisableScheduledImport: options.ImagePolicyConfig.DisableScheduledImport, | |||
ScheduledImageImportMinimumIntervalSeconds: options.ImagePolicyConfig.ScheduledImageImportMinimumIntervalSeconds, | |||
} | |||
ret.ImageSignatureImportControllerConfig = ImageSignatureImportControllerConfig{ | |||
ResyncPeriod: 10 * time.Minute, | |||
SignatureFetchTimeout: 1 * time.Minute, |
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.
@deads2k ultimately we passing this to a context of containers/image where we do http.Get to fetch the sig data... This means after 1 minute we cancel the request and retry on next sync.
@smarterclayton one concern: isn't updating the image with signature going to trigger every image trigger? EDIT: I guess not, because the IST will not change, so this is safe. |
51640f1
to
3a3884a
Compare
16acf17
to
1257b26
Compare
// This will use the name of the image (sha256:xxxx) and the SHA256 of the | ||
// signature itself as the signature name has to be unique for each | ||
// signature. | ||
sig.Name = image.Name + "@" + fmt.Sprintf("%x", sha256.Sum256(blob)) |
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.
Use the helpers we have for this
sig.Annotations = map[string]string{ | ||
SignatureManagedAnnotation: "true", | ||
} | ||
sig.CreationTimestamp = metav1.Now() |
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.
Doesn't the save method do this? What about UIDs?
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.
@smarterclayton no, the timestamps are nil..
|
||
// fetchImageSignatures allows to mock the signature download process as the | ||
// container/image requires system config in /etc/containers/registries.d. | ||
fetchImageSignaturesFn func(*imageapi.Image) ([]imageapi.ImageSignature, error) |
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.
make this an interface, not an embedded func.
return nil | ||
} | ||
|
||
func (s *SignatureImportController) fetchImageSignatures(image *imageapi.Image) ([]imageapi.ImageSignature, error) { |
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.
Split this into its own type - it has nothing to do with the controller, it is just consumed by the controller. Put it in its own file.
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.
Cleanup is an issue, we can't have unbounded signature growth on an image. Has to be some control in here.
@mtrmac how will we deal with cleaning up the old signatures? |
“old” is not a matter of age. It would be nice to remove signatures if the registry from which we pulled them [or if we pulled the same signature from multiple registries, all of them] has stopped publishing it — which would require tracking the signatures per registry, or I guess per image stream, not per image. I guess it would be reasonable to place an upper limit of signatures per image, sure; note that right now it may not be obvious to the administrator where the signatures came from / which user to punish / which registry to block. (E.g. if a registry temporarily publishes 1000 signatures claiming an unrelated image identity and then stops, the administrator would see the image with 1000 signatures, but the signatures don’t record which registry they came from and the registry would no longer admit to being the source.) |
1257b26
to
1d6e3a6
Compare
@smarterclayton comments addressed |
} | ||
} | ||
|
||
if len(newImage.Signatures) > s.signatureImportLimit { |
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.
So this means you keep the old signatures. How will admin know this is happening and react? How do they react?
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.
they can prune signatures periodically with verify image signature remove. we can improve later with auto prune.
expectNoUpdate: true, | ||
signatures: singleFakeSignature, | ||
}, | ||
{ |
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.
limit test case testing various outcomes
1d6e3a6
to
8940bb3
Compare
@smarterclayton test updated with limits... i don't think we have to worry about having more than 3 signatures per a single image for now (we can bump the default to 5 to be absolutely safe). In 3.7+ we can figure out smarter way to rotate the signatures, but I believe this is enough for the start and better than what we have now. |
/retest |
Can you add an e2e conformance test? Can do in a follow up, I'll merge this now. I want the conformance test before we ship. /lgtm |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: mfojtik, smarterclayton The full list of commands accepted by this bot can be found here.
Needs approval from an approver in each of these OWNERS Files:
You can indicate your approval by writing |
/retest ratelimiter... |
/test all [submit-queue is verifying that this PR is safe to merge] |
@mfojtik: The following test failed, say
Full PR test history. Your PR dashboard. Please help us cut down on flakes by linking to an open issue when you hit one in your PR. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
Automatic merge from submit-queue (batch tested with PRs 16293, 16455) |
This controller watches all images and automatically imports the image signatures using
container/image
library./etc/containers/registry.d
to work properlycc @bparees