Skip to content

Commit f2ad0ee

Browse files
committed
Add authentication for private index images
Fixes Issue #1536 #1505 This PR attaches the secrets in the spec.Secrets field of the CatalogSource to the default service account in the CatalogSource's namespace, to allow for pulling of index images that are backed by authentication.
1 parent cd0fdf6 commit f2ad0ee

File tree

7 files changed

+140
-24
lines changed

7 files changed

+140
-24
lines changed

pkg/controller/operators/catalog/operator_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1424,7 +1424,7 @@ func toManifest(t *testing.T, obj runtime.Object) string {
14241424
}
14251425

14261426
func pod(s v1alpha1.CatalogSource) *corev1.Pod {
1427-
pod := reconciler.Pod(&s, "registry-server", s.Spec.Image, s.GetLabels(), 5, 10)
1427+
pod := reconciler.Pod(&s, "registry-server", s.Spec.Image, s.GetName(), s.GetLabels(), 5, 10)
14281428
ownerutil.AddOwner(pod, &s, false, false)
14291429
return pod
14301430
}

pkg/controller/registry/reconciler/configmap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ func (s *configMapCatalogSourceDecorator) Service() *v1.Service {
9595
}
9696

9797
func (s *configMapCatalogSourceDecorator) Pod(image string) *v1.Pod {
98-
pod := Pod(s.CatalogSource, "configmap-registry-server", image, s.Labels(), 5, 2)
98+
pod := Pod(s.CatalogSource, "configmap-registry-server", image, "", s.Labels(), 5, 2)
9999
pod.Spec.ServiceAccountName = s.GetName() + ConfigMapServerPostfix
100100
pod.Spec.Containers[0].Command = []string{"configmap-server", "-c", s.Spec.ConfigMap, "-n", s.GetNamespace()}
101101
ownerutil.AddOwner(pod, s.CatalogSource, false, false)

pkg/controller/registry/reconciler/configmap_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ func objectsForCatalogSource(catsrc *v1alpha1.CatalogSource) []runtime.Object {
204204
if catsrc.Spec.Image != "" {
205205
decorated := grpcCatalogSourceDecorator{catsrc}
206206
objs = clientfake.AddSimpleGeneratedNames(
207-
decorated.Pod(),
207+
decorated.Pod(""),
208208
decorated.Service(),
209209
)
210210
}

pkg/controller/registry/reconciler/grpc.go

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import (
1616
"github.com/pkg/errors"
1717
"github.com/sirupsen/logrus"
1818
corev1 "k8s.io/api/core/v1"
19+
v1 "k8s.io/api/core/v1"
20+
k8serror "k8s.io/apimachinery/pkg/api/errors"
1921
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2022
"k8s.io/apimachinery/pkg/labels"
2123
"k8s.io/apimachinery/pkg/util/intstr"
@@ -86,8 +88,34 @@ func (s *grpcCatalogSourceDecorator) Service() *corev1.Service {
8688
return svc
8789
}
8890

89-
func (s *grpcCatalogSourceDecorator) Pod() *corev1.Pod {
90-
pod := Pod(s.CatalogSource, "registry-server", s.Spec.Image, s.Labels(), 5, 10)
91+
func (s *grpcCatalogSourceDecorator) ServiceAccount() *corev1.ServiceAccount {
92+
var secrets []corev1.LocalObjectReference
93+
blockOwnerDeletion := true
94+
isController := true
95+
for _, secretName := range s.CatalogSource.Spec.Secrets {
96+
secrets = append(secrets, corev1.LocalObjectReference{Name: secretName})
97+
}
98+
return &corev1.ServiceAccount{
99+
ObjectMeta: metav1.ObjectMeta{
100+
Name: s.GetName(),
101+
Namespace: s.GetNamespace(),
102+
OwnerReferences: []metav1.OwnerReference{
103+
{
104+
Name: s.GetName(),
105+
Kind: v1alpha1.CatalogSourceKind,
106+
APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
107+
UID: s.GetUID(),
108+
Controller: &isController,
109+
BlockOwnerDeletion: &blockOwnerDeletion,
110+
},
111+
},
112+
},
113+
ImagePullSecrets: secrets,
114+
}
115+
}
116+
117+
func (s *grpcCatalogSourceDecorator) Pod(saName string) *corev1.Pod {
118+
pod := Pod(s.CatalogSource, "registry-server", s.Spec.Image, saName, s.Labels(), 5, 10)
91119
ownerutil.AddOwner(pod, s.CatalogSource, false, false)
92120
return pod
93121
}
@@ -160,14 +188,18 @@ func (c *GrpcRegistryReconciler) EnsureRegistryServer(catalogSource *v1alpha1.Ca
160188
overwritePod := overwrite || len(c.currentPodsWithCorrectImage(source)) == 0
161189

162190
//TODO: if any of these error out, we should write a status back (possibly set RegistryServiceStatus to nil so they get recreated)
163-
if err := c.ensurePod(source, overwritePod); err != nil {
164-
return errors.Wrapf(err, "error ensuring pod: %s", source.Pod().GetName())
191+
sa, err := c.ensureSA(source)
192+
if err != nil && !k8serror.IsAlreadyExists(err) {
193+
return errors.Wrapf(err, "error ensuring service account: %s", source.GetName())
194+
}
195+
if err := c.ensurePod(source, sa.GetName(), overwritePod); err != nil {
196+
return errors.Wrapf(err, "error ensuring pod: %s", source.Pod(sa.Name).GetName())
165197
}
166-
if err := c.ensureUpdatePod(source); err != nil {
198+
if err := c.ensureUpdatePod(source, sa.Name); err != nil {
167199
if _, ok := err.(UpdateNotReadyErr); ok {
168200
return err
169201
}
170-
return errors.Wrapf(err, "error ensuring updated catalog source pod: %s", source.Pod().GetName())
202+
return errors.Wrapf(err, "error ensuring updated catalog source pod: %s", source.Pod(sa.Name).GetName())
171203
}
172204
if err := c.ensureService(source, overwrite); err != nil {
173205
return errors.Wrapf(err, "error ensuring service: %s", source.Service().GetName())
@@ -186,7 +218,7 @@ func (c *GrpcRegistryReconciler) EnsureRegistryServer(catalogSource *v1alpha1.Ca
186218
return nil
187219
}
188220

189-
func (c *GrpcRegistryReconciler) ensurePod(source grpcCatalogSourceDecorator, overwrite bool) error {
221+
func (c *GrpcRegistryReconciler) ensurePod(source grpcCatalogSourceDecorator, saName string, overwrite bool) error {
190222
// currentLivePods refers to the currently live instances of the catalog source
191223
currentLivePods := c.currentPods(source)
192224
if len(currentLivePods) > 0 {
@@ -199,16 +231,16 @@ func (c *GrpcRegistryReconciler) ensurePod(source grpcCatalogSourceDecorator, ov
199231
}
200232
}
201233
}
202-
_, err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Create(context.TODO(), source.Pod(), metav1.CreateOptions{})
234+
_, err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Create(context.TODO(), source.Pod(saName), metav1.CreateOptions{})
203235
if err != nil {
204-
return errors.Wrapf(err, "error creating new pod: %s", source.Pod().GetGenerateName())
236+
return errors.Wrapf(err, "error creating new pod: %s", source.Pod(saName).GetGenerateName())
205237
}
206238

207239
return nil
208240
}
209241

210242
// ensureUpdatePod checks that for the same catalog source version the same container imageID is running
211-
func (c *GrpcRegistryReconciler) ensureUpdatePod(source grpcCatalogSourceDecorator) error {
243+
func (c *GrpcRegistryReconciler) ensureUpdatePod(source grpcCatalogSourceDecorator, saName string) error {
212244
if !source.Poll() {
213245
return nil
214246
}
@@ -218,7 +250,7 @@ func (c *GrpcRegistryReconciler) ensureUpdatePod(source grpcCatalogSourceDecorat
218250

219251
if source.Update() && len(currentUpdatePods) == 0 {
220252
logrus.WithField("CatalogSource", source.GetName()).Infof("catalog update required at %s", time.Now().String())
221-
pod, err := c.createUpdatePod(source)
253+
pod, err := c.createUpdatePod(source, saName)
222254
if err != nil {
223255
return errors.Wrapf(err, "creating update catalog source pod")
224256
}
@@ -283,6 +315,14 @@ func (c *GrpcRegistryReconciler) ensureService(source grpcCatalogSourceDecorator
283315
return err
284316
}
285317

318+
func (c *GrpcRegistryReconciler) ensureSA(source grpcCatalogSourceDecorator) (*v1.ServiceAccount, error) {
319+
sa := source.ServiceAccount()
320+
if _, err := c.OpClient.CreateServiceAccount(sa); err != nil {
321+
return sa, err
322+
}
323+
return sa, nil
324+
}
325+
286326
// ServiceHashMatch will check the hash info in existing Service to ensure its
287327
// hash info matches the desired Service's hash.
288328
func ServiceHashMatch(existing, new *corev1.Service) bool {
@@ -317,14 +357,14 @@ func HashServiceSpec(spec corev1.ServiceSpec) string {
317357
}
318358

319359
// createUpdatePod is an internal method that creates a pod using the latest catalog source.
320-
func (c *GrpcRegistryReconciler) createUpdatePod(source grpcCatalogSourceDecorator) (*corev1.Pod, error) {
360+
func (c *GrpcRegistryReconciler) createUpdatePod(source grpcCatalogSourceDecorator, saName string) (*corev1.Pod, error) {
321361
// remove label from pod to ensure service does not accidentally route traffic to the pod
322-
p := source.Pod()
362+
p := source.Pod(saName)
323363
p = swapLabels(p, "", source.Name)
324364

325365
pod, err := c.OpClient.KubernetesInterface().CoreV1().Pods(source.GetNamespace()).Create(context.TODO(), p, metav1.CreateOptions{})
326366
if err != nil {
327-
logrus.WithField("pod", source.Pod().GetName()).Warn("couldn't create new catalogsource pod")
367+
logrus.WithField("pod", source.Pod(saName).GetName()).Warn("couldn't create new catalogsource pod")
328368
return nil, err
329369
}
330370

pkg/controller/registry/reconciler/grpc_test.go

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,27 @@ func validGrpcCatalogSource(image, address string) *v1alpha1.CatalogSource {
3333
}
3434
}
3535

36+
func grpcCatalogSourceWithSecret(secretName string) *v1alpha1.CatalogSource {
37+
return &v1alpha1.CatalogSource{
38+
ObjectMeta: metav1.ObjectMeta{
39+
Name: "private-catalog",
40+
Namespace: testNamespace,
41+
UID: types.UID("catalog-uid"),
42+
Labels: map[string]string{"olm.catalogSource": "img-catalog"},
43+
},
44+
Spec: v1alpha1.CatalogSourceSpec{
45+
Image: "private-image",
46+
Address: "",
47+
SourceType: v1alpha1.SourceTypeGrpc,
48+
Secrets: []string{secretName},
49+
},
50+
}
51+
}
52+
3653
func TestGrpcRegistryReconciler(t *testing.T) {
3754
now := func() metav1.Time { return metav1.Date(2018, time.January, 26, 20, 40, 0, 0, time.UTC) }
55+
blockOwnerDeletion := true
56+
isController := true
3857

3958
type cluster struct {
4059
k8sObjs []runtime.Object
@@ -44,8 +63,9 @@ func TestGrpcRegistryReconciler(t *testing.T) {
4463
catsrc *v1alpha1.CatalogSource
4564
}
4665
type out struct {
47-
status *v1alpha1.RegistryServiceStatus
48-
err error
66+
status *v1alpha1.RegistryServiceStatus
67+
cluster cluster
68+
err error
4969
}
5070
tests := []struct {
5171
testName string
@@ -186,6 +206,56 @@ func TestGrpcRegistryReconciler(t *testing.T) {
186206
},
187207
},
188208
},
209+
{
210+
testName: "Grpc/PrivateRegistry/SAHasSecrets",
211+
in: in{
212+
cluster: cluster{
213+
k8sObjs: []runtime.Object{
214+
&corev1.Secret{
215+
ObjectMeta: metav1.ObjectMeta{
216+
Name: "test-secret",
217+
Namespace: testNamespace,
218+
},
219+
},
220+
},
221+
},
222+
catsrc: grpcCatalogSourceWithSecret("test-secret"),
223+
},
224+
out: out{
225+
status: &v1alpha1.RegistryServiceStatus{
226+
CreatedAt: now(),
227+
Protocol: "grpc",
228+
ServiceName: "private-catalog",
229+
ServiceNamespace: testNamespace,
230+
Port: "50051",
231+
},
232+
cluster: cluster{
233+
k8sObjs: []runtime.Object{
234+
&corev1.ServiceAccount{
235+
ObjectMeta: metav1.ObjectMeta{
236+
Name: "private-catalog",
237+
Namespace: testNamespace,
238+
OwnerReferences: []metav1.OwnerReference{
239+
{
240+
Name: "private-catalog",
241+
UID: types.UID("catalog-uid"),
242+
Kind: v1alpha1.CatalogSourceKind,
243+
APIVersion: v1alpha1.CatalogSourceCRDAPIVersion,
244+
BlockOwnerDeletion: &blockOwnerDeletion,
245+
Controller: &isController,
246+
},
247+
},
248+
},
249+
ImagePullSecrets: []corev1.LocalObjectReference{
250+
{
251+
Name: "test-secret",
252+
},
253+
},
254+
},
255+
},
256+
},
257+
},
258+
},
189259
}
190260
for _, tt := range tests {
191261
t.Run(tt.testName, func(t *testing.T) {
@@ -206,12 +276,13 @@ func TestGrpcRegistryReconciler(t *testing.T) {
206276

207277
// Check for resource existence
208278
decorated := grpcCatalogSourceDecorator{tt.in.catsrc}
209-
pod := decorated.Pod()
279+
pod := decorated.Pod(tt.in.catsrc.GetName())
210280
service := decorated.Service()
281+
sa := decorated.ServiceAccount()
211282
listOptions := metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labels.Set{CatalogSourceLabelKey: tt.in.catsrc.GetName()}).String()}
212283
outPods, podErr := client.KubernetesInterface().CoreV1().Pods(pod.GetNamespace()).List(context.TODO(), listOptions)
213284
outService, serviceErr := client.KubernetesInterface().CoreV1().Services(service.GetNamespace()).Get(context.TODO(), service.GetName(), metav1.GetOptions{})
214-
285+
outsa, saerr := client.KubernetesInterface().CoreV1().ServiceAccounts(sa.GetNamespace()).Get(context.TODO(), sa.GetName(), metav1.GetOptions{})
215286
switch rec.(type) {
216287
case *GrpcRegistryReconciler:
217288
// Should be created by a GrpcRegistryReconciler
@@ -223,6 +294,10 @@ func TestGrpcRegistryReconciler(t *testing.T) {
223294
require.Equal(t, pod.Spec, outPod.Spec)
224295
require.NoError(t, serviceErr)
225296
require.Equal(t, service, outService)
297+
require.NoError(t, saerr)
298+
if len(tt.in.catsrc.Spec.Secrets) > 0 {
299+
require.Equal(t, tt.out.cluster.k8sObjs[0], outsa)
300+
}
226301
case *GrpcAddressRegistryReconciler:
227302
// Should not be created by a GrpcAddressRegistryReconciler
228303
require.NoError(t, podErr)

pkg/controller/registry/reconciler/reconciler.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func NewRegistryReconcilerFactory(lister operatorlister.OperatorLister, opClient
9191
}
9292
}
9393

94-
func Pod(source *v1alpha1.CatalogSource, name string, image string, labels map[string]string, readinessDelay int32, livenessDelay int32) *v1.Pod {
94+
func Pod(source *v1alpha1.CatalogSource, name string, image string, saName string, labels map[string]string, readinessDelay int32, livenessDelay int32) *v1.Pod {
9595
// Ensure the catalog image is always pulled if the image is not based on a digest, measured by whether an "@" is included.
9696
// See https://github.com/docker/distribution/blob/master/reference/reference.go for more info.
9797
// This means recreating non-digest based catalog pods will result in the latest version of the catalog content being delivered on-cluster.
@@ -148,6 +148,7 @@ func Pod(source *v1alpha1.CatalogSource, name string, image string, labels map[s
148148
NodeSelector: map[string]string{
149149
"kubernetes.io/os": "linux",
150150
},
151+
ServiceAccountName: saName,
151152
},
152153
}
153154
return pod

pkg/controller/registry/reconciler/reconciler_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestPodNodeSelector(t *testing.T) {
1919
key := "kubernetes.io/os"
2020
value := "linux"
2121

22-
gotCatSrcPod := Pod(catsrc, "hello", "busybox", map[string]string{}, int32(0), int32(0))
22+
gotCatSrcPod := Pod(catsrc, "hello", "busybox", "", map[string]string{}, int32(0), int32(0))
2323
gotCatSrcPodSelector := gotCatSrcPod.Spec.NodeSelector
2424

2525
if gotCatSrcPodSelector[key] != value {
@@ -67,7 +67,7 @@ func TestPullPolicy(t *testing.T) {
6767
}
6868

6969
for _, tt := range table {
70-
p := Pod(source, "catalog", tt.image, nil, int32(0), int32(0))
70+
p := Pod(source, "catalog", tt.image, "", nil, int32(0), int32(0))
7171
policy := p.Spec.Containers[0].ImagePullPolicy
7272
if policy != tt.policy {
7373
t.Fatalf("expected pull policy %s for image %s", tt.policy, tt.image)

0 commit comments

Comments
 (0)