Skip to content

Commit 51640f1

Browse files
committed
wip: add image signature importer controller
1 parent 0797595 commit 51640f1

File tree

4 files changed

+248
-3
lines changed

4 files changed

+248
-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,219 @@
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+
44+
func NewSignatureImportController(ctx context.Context, imageClient imageclient.Interface, imageInformer informers.ImageInformer, resyncInterval, fetchTimeout time.Duration) *SignatureImportController {
45+
controller := &SignatureImportController{
46+
queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
47+
ctx: ctx,
48+
fetchTimeout: fetchTimeout,
49+
imageClient: imageClient,
50+
imageLister: imageInformer.Lister(),
51+
imageHasSynced: imageInformer.Informer().HasSynced,
52+
}
53+
54+
imageInformer.Informer().AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{
55+
AddFunc: func(obj interface{}) {
56+
image := obj.(*imageapi.Image)
57+
glog.V(4).Infof("Adding image %s", image.Name)
58+
controller.enqueueImage(obj)
59+
},
60+
UpdateFunc: func(old, cur interface{}) {
61+
image := cur.(*imageapi.Image)
62+
glog.V(4).Infof("Updating image %s", image.Name)
63+
controller.enqueueImage(cur)
64+
},
65+
}, resyncInterval)
66+
67+
return controller
68+
}
69+
70+
func (s *SignatureImportController) Run(workers int, stopCh <-chan struct{}) {
71+
defer utilruntime.HandleCrash()
72+
defer s.queue.ShutDown()
73+
74+
if !cache.WaitForCacheSync(stopCh, s.imageHasSynced) {
75+
return
76+
}
77+
78+
glog.V(5).Infof("Starting workers")
79+
for i := 0; i < workers; i++ {
80+
go wait.Until(s.worker, time.Second, stopCh)
81+
}
82+
<-stopCh
83+
glog.V(1).Infof("Shutting down")
84+
85+
}
86+
87+
func (s *SignatureImportController) worker() {
88+
for {
89+
if !s.work() {
90+
return
91+
}
92+
}
93+
}
94+
95+
// work returns true if the worker thread should continue
96+
func (s *SignatureImportController) work() bool {
97+
key, quit := s.queue.Get()
98+
if quit {
99+
return false
100+
}
101+
defer s.queue.Done(key)
102+
103+
err := s.syncImageSignatures(key.(string))
104+
if err != nil {
105+
utilruntime.HandleError(fmt.Errorf("error syncing image %s, it will be retried: %v", key.(string), err))
106+
s.queue.AddRateLimited(key)
107+
return true
108+
}
109+
110+
s.queue.Forget(key)
111+
return true
112+
}
113+
114+
func (s *SignatureImportController) enqueueImage(obj interface{}) {
115+
_, ok := obj.(*imageapi.Image)
116+
if !ok {
117+
return
118+
}
119+
key, err := controller.KeyFunc(obj)
120+
if err != nil {
121+
glog.Errorf("Couldn't get key for object %+v: %v", obj, err)
122+
return
123+
}
124+
s.queue.Add(key)
125+
}
126+
127+
func (s *SignatureImportController) syncImageSignatures(key string) error {
128+
glog.V(4).Infof("Initiating download of signatures for %s", key)
129+
image, err := s.imageLister.Get(key)
130+
if err != nil {
131+
glog.V(4).Infof("Unable to get image %v: %v", key, err)
132+
return err
133+
}
134+
135+
currentSignatures, err := s.fetchImageSignatures(image)
136+
if err != nil {
137+
glog.V(4).Infof("Failed to fetch image %s signatures: %v", image.Name, err)
138+
return err
139+
}
140+
141+
// Having no signatures means no-op (we don't remove stored signatures when
142+
// the sig-store no longer have them).
143+
if len(currentSignatures) == 0 {
144+
glog.V(4).Infof("No signatures dowloaded for %s", image.Name)
145+
return nil
146+
}
147+
148+
t, err := kapi.Scheme.DeepCopy(image)
149+
if err != nil {
150+
return err
151+
}
152+
newImage := t.(*imageapi.Image)
153+
154+
shouldUpdate := false
155+
156+
// Only add new signatures, do not override existing stored signatures as that
157+
// can void their verification status.
158+
for _, c := range currentSignatures {
159+
found := false
160+
for _, s := range newImage.Signatures {
161+
if s.Name == c.Name {
162+
found = true
163+
break
164+
}
165+
}
166+
if !found {
167+
newImage.Signatures = append(newImage.Signatures, c)
168+
shouldUpdate = true
169+
}
170+
}
171+
172+
// Avoid unnecessary updates to images.
173+
if !shouldUpdate {
174+
return nil
175+
}
176+
glog.V(4).Infof("Image %s now has %d signatures", newImage.Name, len(newImage.Signatures))
177+
178+
if _, err := s.imageClient.Image().Images().Update(newImage); err != nil {
179+
return err
180+
}
181+
182+
return nil
183+
}
184+
185+
func (s *SignatureImportController) fetchImageSignatures(image *imageapi.Image) ([]imageapi.ImageSignature, error) {
186+
reference, err := docker.ParseReference("//" + image.DockerImageReference)
187+
if err != nil {
188+
return nil, err
189+
}
190+
source, err := reference.NewImageSource(nil, nil)
191+
if err != nil {
192+
return nil, err
193+
}
194+
defer source.Close()
195+
196+
ctx, cancel := context.WithTimeout(s.ctx, s.fetchTimeout)
197+
defer cancel()
198+
199+
signatures, err := source.GetSignatures(ctx)
200+
if err != nil {
201+
return nil, err
202+
}
203+
204+
ret := []imageapi.ImageSignature{}
205+
for _, blob := range signatures {
206+
sig := imageapi.ImageSignature{Type: imageapi.ImageSignatureTypeAtomicImageV1}
207+
// This will use the name of the image (sha256:xxxx) and the SHA256 of the
208+
// signature itself as the signature name has to be unique for each
209+
// signature.
210+
sig.Name = image.Name + "@" + fmt.Sprintf("%x", sha256.Sum256(blob))
211+
sig.Content = blob
212+
sig.Annotations = map[string]string{
213+
SignatureManagedAnnotation: "true",
214+
}
215+
sig.CreationTimestamp = metav1.Now()
216+
ret = append(ret, sig)
217+
}
218+
return ret, nil
219+
}

pkg/oc/admin/image/verify-signature.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package image
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
67
"io"
@@ -405,6 +406,6 @@ func (ui *unparsedImage) Manifest() ([]byte, string, error) {
405406
}
406407

407408
// Signatures is like ImageSource.GetSignatures, but the result is cached; it is OK to call this however often you need.
408-
func (ui *unparsedImage) Signatures() ([][]byte, error) {
409+
func (ui *unparsedImage) Signatures(context.Context) ([][]byte, error) {
409410
return [][]byte{ui.signature}, nil
410411
}

0 commit comments

Comments
 (0)