Skip to content

Commit e7e743a

Browse files
hc-github-team-consul-corenathancolemanblake
authored
Backport of [NET-10985] Fix bug where imagePullSecrets were not set up for Gateways into release/1.4.x (#4373)
[NET-10985] Fix bug where imagePullSecrets were not set up for Gateways (#4316) * Plumb global.imagePullSecrets through to Gateway's ServiceAccount Since pull secrets are a list of structured objects that cannot easily be passed as a flag value to the container, this approach uses a JSON config file that is created as a ConfigMap and then mounted into the connect-injector Pod and parsed on startup. * Leave camp cleaner than I found it * Make path to config file configurable * Add changelog entry * Add note to changelog entry * Ensure ServiceAccount is created if any image pull secrets are provided * Add test coverage for image pull secret inclusion on gateway ServiceAccount * Adjust note in changelog * Add a helpful comment explaining when/why we create a ServiceAccount * Update .changelog/4316.txt * Return ServiceAccount name when image pull secrets warrant it * Improve unit tests to assert presence of ServiceAccount name on Deployment * Copy helpful comment added elsewhere --------- Co-authored-by: Nathan Coleman <[email protected]> Co-authored-by: Blake Covarrubias <[email protected]>
1 parent a582e99 commit e7e743a

File tree

11 files changed

+103
-39
lines changed

11 files changed

+103
-39
lines changed

.changelog/4316.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
```release-note:bug
2+
api-gateway: `global.imagePullSecrets` are now configured on the `ServiceAccount` for `Gateways`.
3+
4+
Note: the referenced image pull Secret(s) must be present in the same namespace the `Gateway` is deployed to.
5+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{{- if .Values.connectInject.enabled }}
2+
apiVersion: v1
3+
kind: ConfigMap
4+
metadata:
5+
name: {{ template "consul.fullname" . }}-connect-inject-config
6+
namespace: {{ .Release.Namespace }}
7+
labels:
8+
app: {{ template "consul.name" . }}
9+
chart: {{ template "consul.chart" . }}
10+
heritage: {{ .Release.Service }}
11+
release: {{ .Release.Name }}
12+
component: connect-injector
13+
data:
14+
config.json: |
15+
{
16+
"image_pull_secrets": {{ .Values.global.imagePullSecrets | toJson }}
17+
}
18+
{{- end }}

charts/consul/templates/connect-inject-deployment.yaml

+7
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ spec:
141141
- "-ec"
142142
- |
143143
exec consul-k8s-control-plane inject-connect \
144+
-config-file=/consul/config/config.json \
144145
{{- if .Values.global.federation.enabled }}
145146
-enable-federation \
146147
{{- end }}
@@ -317,6 +318,9 @@ spec:
317318
successThreshold: 1
318319
timeoutSeconds: 5
319320
volumeMounts:
321+
- name: config
322+
mountPath: /consul/config
323+
readOnly: true
320324
{{- if not (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) }}
321325
- name: certs
322326
mountPath: /etc/connect-injector/certs
@@ -332,6 +336,9 @@ spec:
332336
{{- toYaml . | nindent 12 }}
333337
{{- end }}
334338
volumes:
339+
- name: config
340+
configMap:
341+
name: {{ template "consul.fullname" . }}-connect-inject-config
335342
{{- if not (and .Values.global.secretsBackend.vault.enabled .Values.global.secretsBackend.vault.connectInject.tlsCert.secretName) }}
336343
- name: certs
337344
secret:

control-plane/api-gateway/common/helm_config.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ type HelmConfig struct {
1818
// ImageDataplane is the Consul Dataplane image to use in gateway deployments.
1919
ImageDataplane string
2020
// ImageConsulK8S is the Consul Kubernetes Control Plane image to use in gateway deployments.
21-
ImageConsulK8S string
21+
ImageConsulK8S string
22+
// ImagePullSecrets reference one or more Secret(s) that contain the credentials to pull images from private image repos.
23+
ImagePullSecrets []v1.LocalObjectReference
2224
ConsulDestinationNamespace string
2325
NamespaceMirroringPrefix string
2426
EnableNamespaces bool

control-plane/api-gateway/gatekeeper/gatekeeper.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ func (g *Gatekeeper) namespacedName(gateway gwv1beta1.Gateway) types.NamespacedN
9696
}
9797

9898
func (g *Gatekeeper) serviceAccountName(gateway gwv1beta1.Gateway, config common.HelmConfig) string {
99-
if config.AuthMethod == "" && !config.EnableOpenShift {
99+
// We only create a ServiceAccount if it's needed for RBAC or image pull secrets;
100+
// otherwise, we clean up if one was previously created.
101+
if config.AuthMethod == "" && !config.EnableOpenShift && len(config.ImagePullSecrets) == 0 {
100102
return ""
101103
}
102104
return gateway.Name

control-plane/api-gateway/gatekeeper/gatekeeper_test.go

+29-22
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,13 @@ func TestUpsert(t *testing.T) {
195195
},
196196
},
197197
helmConfig: common.HelmConfig{
198-
ImageDataplane: dataplaneImage,
198+
ImageDataplane: dataplaneImage,
199+
ImagePullSecrets: []corev1.LocalObjectReference{{Name: "my-secret"}},
199200
},
200201
initialResources: resources{},
201202
finalResources: resources{
202203
deployments: []*appsv1.Deployment{
203-
configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"),
204+
configureDeployment(name, namespace, labels, 3, nil, nil, name, "1"),
204205
},
205206
roles: []*rbac.Role{},
206207
services: []*corev1.Service{
@@ -219,7 +220,9 @@ func TestUpsert(t *testing.T) {
219220
},
220221
}, "1", false, false),
221222
},
222-
serviceAccounts: []*corev1.ServiceAccount{},
223+
serviceAccounts: []*corev1.ServiceAccount{
224+
configureServiceAccount(name, namespace, labels, "1", []corev1.LocalObjectReference{{Name: "my-secret"}}),
225+
},
223226
},
224227
},
225228
"create a new gateway deployment with managed Service": {
@@ -271,7 +274,6 @@ func TestUpsert(t *testing.T) {
271274
},
272275
}, "1", false, false),
273276
},
274-
serviceAccounts: []*corev1.ServiceAccount{},
275277
},
276278
},
277279
"create a new gateway deployment with managed Service and ACLs": {
@@ -299,13 +301,14 @@ func TestUpsert(t *testing.T) {
299301
},
300302
},
301303
helmConfig: common.HelmConfig{
302-
AuthMethod: "method",
303-
ImageDataplane: dataplaneImage,
304+
AuthMethod: "method",
305+
ImageDataplane: dataplaneImage,
306+
ImagePullSecrets: []corev1.LocalObjectReference{{Name: "my-secret"}},
304307
},
305308
initialResources: resources{},
306309
finalResources: resources{
307310
deployments: []*appsv1.Deployment{
308-
configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"),
311+
configureDeployment(name, namespace, labels, 3, nil, nil, name, "1"),
309312
},
310313
roles: []*rbac.Role{
311314
configureRole(name, namespace, labels, "1", false),
@@ -330,7 +333,7 @@ func TestUpsert(t *testing.T) {
330333
}, "1", false, false),
331334
},
332335
serviceAccounts: []*corev1.ServiceAccount{
333-
configureServiceAccount(name, namespace, labels, "1"),
336+
configureServiceAccount(name, namespace, labels, "1", []corev1.LocalObjectReference{{Name: "my-secret"}}),
334337
},
335338
},
336339
},
@@ -438,7 +441,7 @@ func TestUpsert(t *testing.T) {
438441
},
439442
initialResources: resources{
440443
deployments: []*appsv1.Deployment{
441-
configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"),
444+
configureDeployment(name, namespace, labels, 3, nil, nil, name, "1"),
442445
},
443446
roles: []*rbac.Role{
444447
configureRole(name, namespace, labels, "1", false),
@@ -456,12 +459,12 @@ func TestUpsert(t *testing.T) {
456459
}, "1", true, false),
457460
},
458461
serviceAccounts: []*corev1.ServiceAccount{
459-
configureServiceAccount(name, namespace, labels, "1"),
462+
configureServiceAccount(name, namespace, labels, "1", nil),
460463
},
461464
},
462465
finalResources: resources{
463466
deployments: []*appsv1.Deployment{
464-
configureDeployment(name, namespace, labels, 3, nil, nil, "", "2"),
467+
configureDeployment(name, namespace, labels, 3, nil, nil, name, "2"),
465468
},
466469
roles: []*rbac.Role{
467470
configureRole(name, namespace, labels, "1", false),
@@ -486,7 +489,7 @@ func TestUpsert(t *testing.T) {
486489
}, "2", false, false),
487490
},
488491
serviceAccounts: []*corev1.ServiceAccount{
489-
configureServiceAccount(name, namespace, labels, "1"),
492+
configureServiceAccount(name, namespace, labels, "1", nil),
490493
},
491494
},
492495
ignoreTimestampOnService: true,
@@ -523,7 +526,7 @@ func TestUpsert(t *testing.T) {
523526
},
524527
initialResources: resources{
525528
deployments: []*appsv1.Deployment{
526-
configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"),
529+
configureDeployment(name, namespace, labels, 3, nil, nil, name, "1"),
527530
},
528531
roles: []*rbac.Role{
529532
configureRole(name, namespace, labels, "1", false),
@@ -546,12 +549,12 @@ func TestUpsert(t *testing.T) {
546549
}, "1", true, false),
547550
},
548551
serviceAccounts: []*corev1.ServiceAccount{
549-
configureServiceAccount(name, namespace, labels, "1"),
552+
configureServiceAccount(name, namespace, labels, "1", nil),
550553
},
551554
},
552555
finalResources: resources{
553556
deployments: []*appsv1.Deployment{
554-
configureDeployment(name, namespace, labels, 3, nil, nil, "", "2"),
557+
configureDeployment(name, namespace, labels, 3, nil, nil, name, "2"),
555558
},
556559
roles: []*rbac.Role{
557560
configureRole(name, namespace, labels, "1", false),
@@ -570,7 +573,7 @@ func TestUpsert(t *testing.T) {
570573
}, "2", false, false),
571574
},
572575
serviceAccounts: []*corev1.ServiceAccount{
573-
configureServiceAccount(name, namespace, labels, "1"),
576+
configureServiceAccount(name, namespace, labels, "1", nil),
574577
},
575578
},
576579
ignoreTimestampOnService: true,
@@ -924,7 +927,7 @@ func TestUpsert(t *testing.T) {
924927
},
925928
finalResources: resources{
926929
deployments: []*appsv1.Deployment{
927-
configureDeployment(name, namespace, labels, 3, nil, nil, "", "1"),
930+
configureDeployment(name, namespace, labels, 3, nil, nil, name, "1"),
928931
},
929932
roles: []*rbac.Role{
930933
configureRole(name, namespace, labels, "1", true),
@@ -934,7 +937,7 @@ func TestUpsert(t *testing.T) {
934937
},
935938
services: []*corev1.Service{},
936939
serviceAccounts: []*corev1.ServiceAccount{
937-
configureServiceAccount(name, namespace, labels, "1"),
940+
configureServiceAccount(name, namespace, labels, "1", nil),
938941
},
939942
},
940943
},
@@ -1114,7 +1117,7 @@ func TestDelete(t *testing.T) {
11141117
}, "1", true, false),
11151118
},
11161119
serviceAccounts: []*corev1.ServiceAccount{
1117-
configureServiceAccount(name, namespace, labels, "1"),
1120+
configureServiceAccount(name, namespace, labels, "1", nil),
11181121
},
11191122
},
11201123
finalResources: resources{
@@ -1210,6 +1213,9 @@ func validateResourcesExist(t *testing.T, client client.Client, helmConfig commo
12101213
require.Equal(t, expected.Spec.Template.ObjectMeta.Annotations, actual.Spec.Template.ObjectMeta.Annotations)
12111214
require.Equal(t, expected.Spec.Template.ObjectMeta.Labels, actual.Spec.Template.Labels)
12121215

1216+
// Ensure the service account is assigned
1217+
require.Equal(t, expected.Spec.Template.Spec.ServiceAccountName, actual.Spec.Template.Spec.ServiceAccountName)
1218+
12131219
// Ensure there is an init container
12141220
hasInitContainer := false
12151221
for _, container := range actual.Spec.Template.Spec.InitContainers {
@@ -1403,7 +1409,7 @@ func validateResourcesAreDeleted(t *testing.T, k8sClient client.Client, resource
14031409
return nil
14041410
}
14051411

1406-
func configureDeployment(name, namespace string, labels map[string]string, replicas int32, nodeSelector map[string]string, tolerations []corev1.Toleration, serviceAccoutName, resourceVersion string) *appsv1.Deployment {
1412+
func configureDeployment(name, namespace string, labels map[string]string, replicas int32, nodeSelector map[string]string, tolerations []corev1.Toleration, serviceAccountName, resourceVersion string) *appsv1.Deployment {
14071413
return &appsv1.Deployment{
14081414
TypeMeta: metav1.TypeMeta{
14091415
APIVersion: "apps/v1",
@@ -1456,7 +1462,7 @@ func configureDeployment(name, namespace string, labels map[string]string, repli
14561462
},
14571463
NodeSelector: nodeSelector,
14581464
Tolerations: tolerations,
1459-
ServiceAccountName: serviceAccoutName,
1465+
ServiceAccountName: serviceAccountName,
14601466
},
14611467
},
14621468
},
@@ -1580,7 +1586,7 @@ func configureService(name, namespace string, labels, annotations map[string]str
15801586
return &service
15811587
}
15821588

1583-
func configureServiceAccount(name, namespace string, labels map[string]string, resourceVersion string) *corev1.ServiceAccount {
1589+
func configureServiceAccount(name, namespace string, labels map[string]string, resourceVersion string, pullSecrets []corev1.LocalObjectReference) *corev1.ServiceAccount {
15841590
return &corev1.ServiceAccount{
15851591
TypeMeta: metav1.TypeMeta{
15861592
APIVersion: "v1",
@@ -1601,6 +1607,7 @@ func configureServiceAccount(name, namespace string, labels map[string]string, r
16011607
},
16021608
},
16031609
},
1610+
ImagePullSecrets: pullSecrets,
16041611
}
16051612
}
16061613

control-plane/api-gateway/gatekeeper/init.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ type initContainerCommandData struct {
3636
LogJSON bool
3737
}
3838

39-
// containerInit returns the init container spec for connect-init that polls for the service and the connect proxy service to be registered
39+
// initContainer returns the init container spec for connect-init that polls for the service and the connect proxy service to be registered
4040
// so that it can save the proxy service id to the shared volume and boostrap Envoy with the proxy-id.
4141
func (g Gatekeeper) initContainer(config common.HelmConfig, name, namespace string) (corev1.Container, error) {
4242
data := initContainerCommandData{

control-plane/api-gateway/gatekeeper/rolebinding.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import (
1010
"k8s.io/apimachinery/pkg/types"
1111
gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
1212

13-
"github.com/hashicorp/consul-k8s/control-plane/api-gateway/common"
14-
"github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1"
1513
rbac "k8s.io/api/rbac/v1"
1614
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1715
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1816
ctrl "sigs.k8s.io/controller-runtime"
17+
18+
"github.com/hashicorp/consul-k8s/control-plane/api-gateway/common"
19+
"github.com/hashicorp/consul-k8s/control-plane/api/v1alpha1"
1920
)
2021

2122
func (g *Gatekeeper) upsertRoleBinding(ctx context.Context, gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) error {
@@ -65,7 +66,7 @@ func (g *Gatekeeper) deleteRoleBinding(ctx context.Context, gwName types.Namespa
6566

6667
func (g *Gatekeeper) roleBinding(gateway gwv1beta1.Gateway, gcc v1alpha1.GatewayClassConfig, config common.HelmConfig) *rbac.RoleBinding {
6768
// Create resources for reference. This avoids bugs if naming patterns change.
68-
serviceAccount := g.serviceAccount(gateway)
69+
serviceAccount := g.serviceAccount(gateway, config)
6970
role := g.role(gateway, gcc, config)
7071

7172
return &rbac.RoleBinding{

control-plane/api-gateway/gatekeeper/serviceaccount.go

+11-11
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,20 @@ import (
77
"context"
88
"errors"
99

10-
"github.com/hashicorp/consul-k8s/control-plane/api-gateway/common"
11-
"k8s.io/apimachinery/pkg/types"
12-
gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
13-
1410
corev1 "k8s.io/api/core/v1"
1511
k8serrors "k8s.io/apimachinery/pkg/api/errors"
1612
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
"k8s.io/apimachinery/pkg/types"
1714
ctrl "sigs.k8s.io/controller-runtime"
15+
gwv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
16+
17+
"github.com/hashicorp/consul-k8s/control-plane/api-gateway/common"
1818
)
1919

2020
func (g *Gatekeeper) upsertServiceAccount(ctx context.Context, gateway gwv1beta1.Gateway, config common.HelmConfig) error {
21-
if config.AuthMethod == "" && !config.EnableOpenShift {
21+
// We only create a ServiceAccount if it's needed for RBAC or image pull secrets;
22+
// otherwise, we clean up if one was previously created.
23+
if config.AuthMethod == "" && !config.EnableOpenShift && len(config.ImagePullSecrets) == 0 {
2224
return g.deleteServiceAccount(ctx, types.NamespacedName{Namespace: gateway.Namespace, Name: gateway.Name})
2325
}
2426

@@ -47,15 +49,12 @@ func (g *Gatekeeper) upsertServiceAccount(ctx context.Context, gateway gwv1beta1
4749
}
4850

4951
// Create the ServiceAccount.
50-
serviceAccount = g.serviceAccount(gateway)
52+
serviceAccount = g.serviceAccount(gateway, config)
5153
if err := ctrl.SetControllerReference(&gateway, serviceAccount, g.Client.Scheme()); err != nil {
5254
return err
5355
}
54-
if err := g.Client.Create(ctx, serviceAccount); err != nil {
55-
return err
56-
}
5756

58-
return nil
57+
return g.Client.Create(ctx, serviceAccount)
5958
}
6059

6160
func (g *Gatekeeper) deleteServiceAccount(ctx context.Context, gwName types.NamespacedName) error {
@@ -69,12 +68,13 @@ func (g *Gatekeeper) deleteServiceAccount(ctx context.Context, gwName types.Name
6968
return nil
7069
}
7170

72-
func (g *Gatekeeper) serviceAccount(gateway gwv1beta1.Gateway) *corev1.ServiceAccount {
71+
func (g *Gatekeeper) serviceAccount(gateway gwv1beta1.Gateway, config common.HelmConfig) *corev1.ServiceAccount {
7372
return &corev1.ServiceAccount{
7473
ObjectMeta: metav1.ObjectMeta{
7574
Name: gateway.Name,
7675
Namespace: gateway.Namespace,
7776
Labels: common.LabelsForGateway(&gateway),
7877
},
78+
ImagePullSecrets: config.ImagePullSecrets,
7979
}
8080
}

control-plane/subcommand/inject-connect/command.go

+2
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type Command struct {
5151
flagListen string
5252
flagCertDir string // Directory with TLS certs for listening (PEM)
5353
flagDefaultInject bool // True to inject by default
54+
flagConfigFile string // Path to a config file in JSON format
5455
flagConsulImage string // Docker image for Consul
5556
flagConsulDataplaneImage string // Docker image for Envoy
5657
flagConsulK8sImage string // Docker image for consul-k8s
@@ -183,6 +184,7 @@ func init() {
183184
func (c *Command) init() {
184185
c.flagSet = flag.NewFlagSet("", flag.ContinueOnError)
185186
c.flagSet.StringVar(&c.flagListen, "listen", ":8080", "Address to bind listener to.")
187+
c.flagSet.StringVar(&c.flagConfigFile, "config-file", "", "Path to a JSON config file.")
186188
c.flagSet.Var((*flags.FlagMapValue)(&c.flagNodeMeta), "node-meta",
187189
"Metadata to set on the node, formatted as key=value. This flag may be specified multiple times to set multiple meta fields.")
188190
c.flagSet.BoolVar(&c.flagDefaultInject, "default-inject", true, "Inject by default.")

0 commit comments

Comments
 (0)