Skip to content

Commit 1d6e3a6

Browse files
committed
image: add image signature importer controller
1 parent 0314749 commit 1d6e3a6

File tree

6 files changed

+422
-3
lines changed

6 files changed

+422
-3
lines changed

pkg/cmd/server/origin/controller/config.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ type OpenshiftControllerConfig struct {
4848
DeploymentConfigControllerConfig DeploymentConfigControllerConfig
4949
DeploymentTriggerControllerConfig DeploymentTriggerControllerConfig
5050

51-
ImageTriggerControllerConfig ImageTriggerControllerConfig
52-
ImageImportControllerConfig ImageImportControllerConfig
51+
ImageTriggerControllerConfig ImageTriggerControllerConfig
52+
ImageSignatureImportControllerConfig ImageSignatureImportControllerConfig
53+
ImageImportControllerConfig ImageImportControllerConfig
5354

5455
ServiceServingCertsControllerOptions ServiceServingCertsControllerOptions
5556

@@ -80,6 +81,7 @@ func (c *OpenshiftControllerConfig) GetControllerInitializers() (map[string]Init
8081

8182
ret["openshift.io/image-trigger"] = c.ImageTriggerControllerConfig.RunController
8283
ret["openshift.io/image-import"] = c.ImageImportControllerConfig.RunController
84+
ret["openshift.io/image-signature-import"] = c.ImageSignatureImportControllerConfig.RunController
8385

8486
ret["openshift.io/templateinstance"] = RunTemplateInstanceController
8587

@@ -203,6 +205,11 @@ func BuildOpenshiftControllerConfig(options configapi.MasterConfig) (*OpenshiftC
203205
DisableScheduledImport: options.ImagePolicyConfig.DisableScheduledImport,
204206
ScheduledImageImportMinimumIntervalSeconds: options.ImagePolicyConfig.ScheduledImageImportMinimumIntervalSeconds,
205207
}
208+
ret.ImageSignatureImportControllerConfig = ImageSignatureImportControllerConfig{
209+
ResyncPeriod: 10 * time.Minute,
210+
SignatureFetchTimeout: 1 * time.Minute,
211+
SignatureImportLimit: 3,
212+
}
206213

207214
ret.ServiceServingCertsControllerOptions = ServiceServingCertsControllerOptions{
208215
Signer: options.ControllerConfig.ServiceServingCert.Signer,

pkg/cmd/server/origin/controller/image.go

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package controller
22

33
import (
4+
"context"
45
"fmt"
56
"time"
67

@@ -18,6 +19,7 @@ import (
1819
buildclient "github.com/openshift/origin/pkg/build/client"
1920
"github.com/openshift/origin/pkg/cmd/server/bootstrappolicy"
2021
imagecontroller "github.com/openshift/origin/pkg/image/controller"
22+
imagesignaturecontroller "github.com/openshift/origin/pkg/image/controller/signature"
2123
imagetriggercontroller "github.com/openshift/origin/pkg/image/controller/trigger"
2224
triggerannotations "github.com/openshift/origin/pkg/image/trigger/annotations"
2325
triggerbuildconfigs "github.com/openshift/origin/pkg/image/trigger/buildconfigs"
@@ -147,6 +149,25 @@ func (u podSpecUpdater) Update(obj runtime.Object) error {
147149
}
148150
}
149151

152+
type ImageSignatureImportControllerConfig struct {
153+
ResyncPeriod time.Duration
154+
SignatureFetchTimeout time.Duration
155+
SignatureImportLimit int
156+
}
157+
158+
func (c *ImageSignatureImportControllerConfig) RunController(ctx ControllerContext) (bool, error) {
159+
controller := imagesignaturecontroller.NewSignatureImportController(
160+
context.Background(),
161+
ctx.ClientBuilder.OpenshiftInternalImageClientOrDie(bootstrappolicy.InfraImageImportControllerServiceAccountName),
162+
ctx.ImageInformers.Image().InternalVersion().Images(),
163+
c.ResyncPeriod,
164+
c.SignatureFetchTimeout,
165+
c.SignatureImportLimit,
166+
)
167+
go controller.Run(5, ctx.Stop)
168+
return true, nil
169+
}
170+
150171
type ImageImportControllerConfig struct {
151172
MaxScheduledImageImportsPerMinute int
152173
ScheduledImageImportMinimumIntervalSeconds int
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package signature
2+
3+
import (
4+
"context"
5+
"crypto/sha256"
6+
"fmt"
7+
"time"
8+
9+
"github.com/containers/image/docker"
10+
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
13+
imageapi "github.com/openshift/origin/pkg/image/apis/image"
14+
)
15+
16+
type containerImageSignatureDownloader struct {
17+
ctx context.Context
18+
timeout time.Duration
19+
}
20+
21+
func NewContainerImageSignatureDownloader(ctx context.Context, timeout time.Duration) SignatureDownloader {
22+
return &containerImageSignatureDownloader{
23+
ctx: ctx,
24+
timeout: timeout,
25+
}
26+
}
27+
28+
func (s *containerImageSignatureDownloader) DownloadImageSignatures(image *imageapi.Image) ([]imageapi.ImageSignature, error) {
29+
reference, err := docker.ParseReference("//" + image.DockerImageReference)
30+
if err != nil {
31+
return nil, err
32+
}
33+
source, err := reference.NewImageSource(nil, nil)
34+
if err != nil {
35+
return nil, err
36+
}
37+
defer source.Close()
38+
39+
ctx, cancel := context.WithTimeout(s.ctx, s.timeout)
40+
defer cancel()
41+
42+
signatures, err := source.GetSignatures(ctx)
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
ret := []imageapi.ImageSignature{}
48+
for _, blob := range signatures {
49+
sig := imageapi.ImageSignature{Type: imageapi.ImageSignatureTypeAtomicImageV1}
50+
// This will use the name of the image (sha256:xxxx) and the SHA256 of the
51+
// signature itself as the signature name has to be unique for each
52+
// signature.
53+
sig.Name = imageapi.JoinImageStreamImage(image.Name, fmt.Sprintf("%x", sha256.Sum256(blob)))
54+
sig.Content = blob
55+
sig.Annotations = map[string]string{
56+
SignatureManagedAnnotation: "true",
57+
}
58+
sig.CreationTimestamp = metav1.Now()
59+
ret = append(ret, sig)
60+
}
61+
return ret, nil
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package signature
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"time"
7+
8+
"github.com/golang/glog"
9+
10+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
11+
"k8s.io/apimachinery/pkg/util/wait"
12+
"k8s.io/client-go/tools/cache"
13+
"k8s.io/client-go/util/workqueue"
14+
kapi "k8s.io/kubernetes/pkg/api"
15+
"k8s.io/kubernetes/pkg/controller"
16+
17+
imageapi "github.com/openshift/origin/pkg/image/apis/image"
18+
informers "github.com/openshift/origin/pkg/image/generated/informers/internalversion/image/internalversion"
19+
imageclient "github.com/openshift/origin/pkg/image/generated/internalclientset"
20+
imagelister "github.com/openshift/origin/pkg/image/generated/listers/image/internalversion"
21+
)
22+
23+
const (
24+
// SignatureManagedAnnotation marks signatures that were imported by this
25+
// controller.
26+
SignatureManagedAnnotation = "image.openshift.io/managed-signature"
27+
)
28+
29+
type SignatureDownloader interface {
30+
DownloadImageSignatures(*imageapi.Image) ([]imageapi.ImageSignature, error)
31+
}
32+
33+
type SignatureImportController struct {
34+
imageClient imageclient.Interface
35+
imageLister imagelister.ImageLister
36+
37+
imageHasSynced cache.InformerSynced
38+
39+
queue workqueue.RateLimitingInterface
40+
41+
// signatureImportLimit limits amount of signatures we will import.
42+
// By default this is set to 3 signatures.
43+
signatureImportLimit int
44+
45+
fetcher SignatureDownloader
46+
}
47+
48+
func NewSignatureImportController(ctx context.Context, imageClient imageclient.Interface, imageInformer informers.ImageInformer, resyncInterval, fetchTimeout time.Duration, limit int) *SignatureImportController {
49+
controller := &SignatureImportController{
50+
queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
51+
imageClient: imageClient,
52+
imageLister: imageInformer.Lister(),
53+
imageHasSynced: imageInformer.Informer().HasSynced,
54+
signatureImportLimit: limit,
55+
}
56+
controller.fetcher = NewContainerImageSignatureDownloader(ctx, fetchTimeout)
57+
58+
imageInformer.Informer().AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{
59+
AddFunc: func(obj interface{}) {
60+
image := obj.(*imageapi.Image)
61+
glog.V(4).Infof("Adding image %s", image.Name)
62+
controller.enqueueImage(obj)
63+
},
64+
UpdateFunc: func(old, cur interface{}) {
65+
image := cur.(*imageapi.Image)
66+
glog.V(4).Infof("Updating image %s", image.Name)
67+
controller.enqueueImage(cur)
68+
},
69+
}, resyncInterval)
70+
71+
return controller
72+
}
73+
74+
func (s *SignatureImportController) Run(workers int, stopCh <-chan struct{}) {
75+
defer utilruntime.HandleCrash()
76+
defer s.queue.ShutDown()
77+
78+
if !cache.WaitForCacheSync(stopCh, s.imageHasSynced) {
79+
return
80+
}
81+
82+
glog.V(5).Infof("Starting workers")
83+
for i := 0; i < workers; i++ {
84+
go wait.Until(s.worker, time.Second, stopCh)
85+
}
86+
<-stopCh
87+
glog.V(1).Infof("Shutting down")
88+
89+
}
90+
91+
func (s *SignatureImportController) worker() {
92+
for {
93+
if !s.work() {
94+
return
95+
}
96+
}
97+
}
98+
99+
// work returns true if the worker thread should continue
100+
func (s *SignatureImportController) work() bool {
101+
key, quit := s.queue.Get()
102+
if quit {
103+
return false
104+
}
105+
defer s.queue.Done(key)
106+
107+
err := s.syncImageSignatures(key.(string))
108+
if err != nil {
109+
utilruntime.HandleError(fmt.Errorf("error syncing image %s, it will be retried: %v", key.(string), err))
110+
s.queue.AddRateLimited(key)
111+
return true
112+
}
113+
114+
s.queue.Forget(key)
115+
return true
116+
}
117+
118+
func (s *SignatureImportController) enqueueImage(obj interface{}) {
119+
_, ok := obj.(*imageapi.Image)
120+
if !ok {
121+
return
122+
}
123+
key, err := controller.KeyFunc(obj)
124+
if err != nil {
125+
glog.Errorf("Couldn't get key for object %+v: %v", obj, err)
126+
return
127+
}
128+
s.queue.Add(key)
129+
}
130+
131+
func (s *SignatureImportController) syncImageSignatures(key string) error {
132+
glog.V(4).Infof("Initiating download of signatures for %s", key)
133+
image, err := s.imageLister.Get(key)
134+
if err != nil {
135+
glog.V(4).Infof("Unable to get image %v: %v", key, err)
136+
return err
137+
}
138+
139+
currentSignatures, err := s.fetcher.DownloadImageSignatures(image)
140+
if err != nil {
141+
glog.V(4).Infof("Failed to fetch image %s signatures: %v", image.Name, err)
142+
return err
143+
}
144+
145+
// Having no signatures means no-op (we don't remove stored signatures when
146+
// the sig-store no longer have them).
147+
if len(currentSignatures) == 0 {
148+
glog.V(4).Infof("No signatures dowloaded for %s", image.Name)
149+
return nil
150+
}
151+
152+
t, err := kapi.Scheme.DeepCopy(image)
153+
if err != nil {
154+
return err
155+
}
156+
newImage := t.(*imageapi.Image)
157+
158+
shouldUpdate := false
159+
160+
// Only add new signatures, do not override existing stored signatures as that
161+
// can void their verification status.
162+
for _, c := range currentSignatures {
163+
found := false
164+
for _, s := range newImage.Signatures {
165+
if s.Name == c.Name {
166+
found = true
167+
break
168+
}
169+
}
170+
if !found {
171+
newImage.Signatures = append(newImage.Signatures, c)
172+
shouldUpdate = true
173+
}
174+
}
175+
176+
if len(newImage.Signatures) > s.signatureImportLimit {
177+
glog.V(2).Infof("Image %s reached signature limit (max:%d, want:%d)", newImage.Name, s.signatureImportLimit, len(newImage.Signatures))
178+
return nil
179+
}
180+
181+
// Avoid unnecessary updates to images.
182+
if !shouldUpdate {
183+
return nil
184+
}
185+
glog.V(4).Infof("Image %s now has %d signatures", newImage.Name, len(newImage.Signatures))
186+
187+
if _, err := s.imageClient.Image().Images().Update(newImage); err != nil {
188+
return err
189+
}
190+
191+
return nil
192+
}

0 commit comments

Comments
 (0)