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

Controller for automatic image signature import #16293

Merged
merged 4 commits into from
Sep 27, 2017

Conversation

mfojtik
Copy link
Contributor

@mfojtik mfojtik commented Sep 12, 2017

This controller watches all images and automatically imports the image signatures using container/image library.

  • Sync never remove/override existing imported signatures
  • This needs a config file in /etc/containers/registry.d to work properly

cc @bparees

@openshift-ci-robot openshift-ci-robot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Sep 12, 2017
@openshift-ci-robot
Copy link

@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.

@openshift-ci-robot openshift-ci-robot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Sep 12, 2017
@openshift-merge-robot openshift-merge-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 12, 2017
@mfojtik
Copy link
Contributor Author

mfojtik commented Sep 12, 2017

@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.

@mfojtik
Copy link
Contributor Author

mfojtik commented Sep 12, 2017

}

updatedImage := t.(*imageapi.Image)
updatedImage.Signatures = signatures
Copy link
Contributor

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.)

Copy link
Contributor Author

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)
Copy link
Contributor

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 {
Copy link
Contributor

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))
Copy link
Contributor

Choose a reason for hiding this comment

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

not a service

Copy link
Contributor Author

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)
Copy link
Contributor

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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

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
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor

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?

@mfojtik mfojtik force-pushed the signature-controller branch from 7756956 to bce010b Compare September 12, 2017 15:32
@mfojtik
Copy link
Contributor Author

mfojtik commented Sep 12, 2017

@mtrmac @deads2k addressed comments, refactored how we store the signatures a little bit

}
}

// Only add the signatures that we received from the signature store.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mtrmac @deads2k FYI

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).

Copy link
Contributor

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.)

Copy link
Contributor Author

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?

Copy link
Contributor

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,
Copy link
Contributor Author

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.

@mfojtik
Copy link
Contributor Author

mfojtik commented Sep 12, 2017

@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.

@mfojtik mfojtik force-pushed the signature-controller branch 4 times, most recently from 51640f1 to 3a3884a Compare September 12, 2017 21:10
@mfojtik mfojtik changed the title WIP: Controller for automatic image signature import Controller for automatic image signature import Sep 12, 2017
@openshift-ci-robot openshift-ci-robot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Sep 12, 2017
@mfojtik mfojtik force-pushed the signature-controller branch 2 times, most recently from 16acf17 to 1257b26 Compare September 13, 2017 08:59
// 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))
Copy link
Contributor

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()
Copy link
Contributor

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?

Copy link
Contributor Author

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)
Copy link
Contributor

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) {
Copy link
Contributor

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.

Copy link
Contributor

@smarterclayton smarterclayton left a 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.

@mfojtik
Copy link
Contributor Author

mfojtik commented Sep 25, 2017

@mtrmac how will we deal with cleaning up the old signatures?

@mtrmac
Copy link
Contributor

mtrmac commented Sep 25, 2017

“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.)

@mfojtik mfojtik force-pushed the signature-controller branch from 1257b26 to 1d6e3a6 Compare September 26, 2017 17:52
@mfojtik
Copy link
Contributor Author

mfojtik commented Sep 26, 2017

@smarterclayton comments addressed

}
}

if len(newImage.Signatures) > s.signatureImportLimit {
Copy link
Contributor

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?

Copy link
Contributor Author

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,
},
{
Copy link
Contributor

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

@mfojtik mfojtik force-pushed the signature-controller branch from 1d6e3a6 to 8940bb3 Compare September 27, 2017 09:13
@mfojtik
Copy link
Contributor Author

mfojtik commented Sep 27, 2017

@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.

@mfojtik
Copy link
Contributor Author

mfojtik commented Sep 27, 2017

/retest

@smarterclayton
Copy link
Contributor

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

@openshift-ci-robot openshift-ci-robot added the lgtm Indicates that a PR is ready to be merged. label Sep 27, 2017
@openshift-merge-robot
Copy link
Contributor

[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:
  • OWNERS [mfojtik,smarterclayton]

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@mfojtik
Copy link
Contributor Author

mfojtik commented Sep 27, 2017

/retest

ratelimiter...

@openshift-merge-robot
Copy link
Contributor

/test all [submit-queue is verifying that this PR is safe to merge]

@openshift-ci-robot
Copy link

openshift-ci-robot commented Sep 27, 2017

@mfojtik: The following test failed, say /retest to rerun them all:

Test name Commit Details Rerun command
ci/openshift-jenkins/cmd 8940bb3 link /test cmd

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.

@openshift-merge-robot
Copy link
Contributor

Automatic merge from submit-queue (batch tested with PRs 16293, 16455)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants