Skip to content

Commit 1257b26

Browse files
committed
image: add image signature importer controller
1 parent 0797595 commit 1257b26

File tree

5 files changed

+388
-3
lines changed

5 files changed

+388
-3
lines changed

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

+8-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,10 @@ 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+
}
206212

207213
ret.ServiceServingCertsControllerOptions = ServiceServingCertsControllerOptions{
208214
Signer: options.ControllerConfig.ServiceServingCert.Signer,

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

+19
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"
@@ -142,6 +144,23 @@ func (u podSpecUpdater) Update(obj runtime.Object) error {
142144
}
143145
}
144146

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

0 commit comments

Comments
 (0)