Skip to content

Commit b1ebcd7

Browse files
committed
Restrict which packages are aware of the keys used in Pinniped-generated certificate secrets
1 parent f4b90f8 commit b1ebcd7

15 files changed

+196
-39
lines changed

internal/concierge/server/prepare_controllers.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) { //nol
162162
apicerts.NewAPIServiceUpdaterController(
163163
c.ServerInstallationInfo.Namespace,
164164
c.NamesConfig.ServingCertificateSecret,
165+
apicerts.RetrieveCAFromSecret,
165166
loginConciergeGroupData.APIServiceName(),
166167
client.Aggregation,
167168
informers.installationNamespaceK8s.Core().V1().Secrets(),
@@ -173,6 +174,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) { //nol
173174
apicerts.NewAPIServiceUpdaterController(
174175
c.ServerInstallationInfo.Namespace,
175176
c.NamesConfig.ServingCertificateSecret,
177+
apicerts.RetrieveCAFromSecret,
176178
identityConciergeGroupData.APIServiceName(),
177179
client.Aggregation,
178180
informers.installationNamespaceK8s.Core().V1().Secrets(),
@@ -184,6 +186,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) { //nol
184186
apicerts.NewCertsObserverController(
185187
c.ServerInstallationInfo.Namespace,
186188
c.NamesConfig.ServingCertificateSecret,
189+
apicerts.RetrieveCertificateFromSecret,
187190
c.DynamicServingCertProvider,
188191
informers.installationNamespaceK8s.Core().V1().Secrets(),
189192
controllerlib.WithInformer,
@@ -198,7 +201,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) { //nol
198201
informers.installationNamespaceK8s.Core().V1().Secrets(),
199202
controllerlib.WithInformer,
200203
c.ServingCertRenewBefore,
201-
apicerts.TLSCertificateChainSecretKey,
204+
apicerts.RetrieveCertificateFromSecret,
202205
plog.New(),
203206
),
204207
singletonWorker,
@@ -281,6 +284,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) { //nol
281284
clock.RealClock{},
282285
impersonator.New,
283286
c.NamesConfig.ImpersonationSignerSecret,
287+
apicerts.RetrieveCAFromSecret,
284288
c.ImpersonationSigningCertProvider,
285289
plog.New(),
286290
c.ImpersonationProxyTokenCache,
@@ -310,7 +314,7 @@ func PrepareControllers(c *Config) (controllerinit.RunnerBuilder, error) { //nol
310314
informers.installationNamespaceK8s.Core().V1().Secrets(),
311315
controllerlib.WithInformer,
312316
365*24*time.Hour-time.Hour, // 1 year minus 1 hour hard coded value (i.e. wait until the last moment to break the signer)
313-
apicerts.CACertificateSecretKey,
317+
apicerts.RetrieveCAFromSecret,
314318
plog.New(),
315319
),
316320
singletonWorker,

internal/controller/apicerts/apiservice_updater.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
type apiServiceUpdaterController struct {
1919
namespace string
2020
certsSecretResourceName string
21+
certificateRetriever RetrieveFromSecretFunc
2122
aggregatorClient aggregatorclient.Interface
2223
secretInformer corev1informers.SecretInformer
2324
apiServiceName string
@@ -26,6 +27,7 @@ type apiServiceUpdaterController struct {
2627
func NewAPIServiceUpdaterController(
2728
namespace string,
2829
certsSecretResourceName string,
30+
certificateRetriever RetrieveFromSecretFunc,
2931
apiServiceName string,
3032
aggregatorClient aggregatorclient.Interface,
3133
secretInformer corev1informers.SecretInformer,
@@ -37,6 +39,7 @@ func NewAPIServiceUpdaterController(
3739
Syncer: &apiServiceUpdaterController{
3840
namespace: namespace,
3941
certsSecretResourceName: certsSecretResourceName,
42+
certificateRetriever: certificateRetriever,
4043
aggregatorClient: aggregatorClient,
4144
secretInformer: secretInformer,
4245
apiServiceName: apiServiceName,
@@ -63,13 +66,15 @@ func (c *apiServiceUpdaterController) Sync(ctx controllerlib.Context) error {
6366
return nil
6467
}
6568

69+
caCertPEM, _ := c.certificateRetriever(certSecret)
70+
6671
// Update the APIService to give it the new CA bundle.
6772
if err := UpdateAPIService(
6873
ctx.Context,
6974
c.aggregatorClient,
7075
c.apiServiceName,
7176
c.namespace,
72-
certSecret.Data[CACertificateSecretKey],
77+
caCertPEM,
7378
); err != nil {
7479
return fmt.Errorf("could not update the API service: %w", err)
7580
}

internal/controller/apicerts/apiservice_updater_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
1+
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

44
package apicerts
@@ -42,6 +42,9 @@ func TestAPIServiceUpdaterControllerOptions(t *testing.T) {
4242
_ = NewAPIServiceUpdaterController(
4343
installedInNamespace,
4444
certsSecretResourceName,
45+
func(secret *corev1.Secret) ([]byte, []byte) {
46+
return secret.Data["some-key-for-ca-certificate"], []byte("this value does not matter")
47+
},
4548
loginv1alpha1.SchemeGroupVersion.Version+"."+loginv1alpha1.GroupName,
4649
nil,
4750
secretsInformer,
@@ -122,6 +125,9 @@ func TestAPIServiceUpdaterControllerSync(t *testing.T) {
122125
subject = NewAPIServiceUpdaterController(
123126
installedInNamespace,
124127
certsSecretResourceName,
128+
func(secret *corev1.Secret) ([]byte, []byte) {
129+
return secret.Data["some-key-for-ca-certificate"], []byte("this value does not matter")
130+
},
125131
loginv1alpha1.SchemeGroupVersion.Version+"."+loginv1alpha1.GroupName,
126132
aggregatorAPIClient,
127133
kubeInformers.Core().V1().Secrets(),
@@ -185,9 +191,9 @@ func TestAPIServiceUpdaterControllerSync(t *testing.T) {
185191
Namespace: installedInNamespace,
186192
},
187193
Data: map[string][]byte{
188-
"caCertificate": []byte("fake CA cert"),
189-
"tlsPrivateKey": []byte("fake private key"),
190-
"tlsCertificateChain": []byte("fake cert chain"),
194+
"some-key-for-ca-certificate": []byte("fake CA cert"),
195+
"serving-cert-key-EXTRA": []byte("fake cert chain"),
196+
"private-key-EXTRA": []byte("fake private key"),
191197
},
192198
}
193199
err := kubeInformerClient.Tracker().Add(apiServingCertSecret)

internal/controller/apicerts/certs_creator.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,17 @@ import (
1919
"go.pinniped.dev/internal/plog"
2020
)
2121

22+
// The following key names are unexported, to prevent a leaky abstraction.
23+
// Even the string literals should only be used in a very limited set of places:
24+
// - The unit tests for this file
25+
// - The unit tests for retrieve_from_secret.go
26+
// - Integration tests
27+
// Comment must end in a period, so here's a period: .
2228
const (
23-
CACertificateSecretKey = "caCertificate"
24-
CACertificatePrivateKeySecretKey = "caCertificatePrivateKey"
29+
caCertificateSecretKey = "caCertificate"
30+
caCertificatePrivateKeySecretKey = "caCertificatePrivateKey"
31+
tlsCertificateChainSecretKey = "tlsCertificateChain"
2532
tlsPrivateKeySecretKey = "tlsPrivateKey"
26-
TLSCertificateChainSecretKey = "tlsCertificateChain"
2733
)
2834

2935
type certsCreatorController struct {
@@ -111,8 +117,8 @@ func (c *certsCreatorController) Sync(ctx controllerlib.Context) error {
111117
Labels: c.certsSecretLabels,
112118
},
113119
Data: map[string][]byte{
114-
CACertificateSecretKey: ca.Bundle(),
115-
CACertificatePrivateKeySecretKey: caPrivateKeyPEM,
120+
caCertificateSecretKey: ca.Bundle(),
121+
caCertificatePrivateKeySecretKey: caPrivateKeyPEM,
116122
},
117123
}
118124

@@ -131,7 +137,7 @@ func (c *certsCreatorController) Sync(ctx controllerlib.Context) error {
131137
}
132138

133139
secret.Data[tlsPrivateKeySecretKey] = tlsPrivateKeyPEM
134-
secret.Data[TLSCertificateChainSecretKey] = tlsCertChainPEM
140+
secret.Data[tlsCertificateChainSecretKey] = tlsCertChainPEM
135141
}
136142

137143
_, err = c.k8sClient.CoreV1().Secrets(c.namespace).Create(ctx.Context, &secret, metav1.CreateOptions{})

internal/controller/apicerts/certs_expirer.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type certsExpirerController struct {
3131
// this controller will start to try to rotate it.
3232
renewBefore time.Duration
3333

34-
secretKey string
34+
certificateRetriever RetrieveFromSecretFunc
3535

3636
logger plog.Logger
3737
}
@@ -46,7 +46,7 @@ func NewCertsExpirerController(
4646
secretInformer corev1informers.SecretInformer,
4747
withInformer pinnipedcontroller.WithInformerOptionFunc,
4848
renewBefore time.Duration,
49-
secretKey string,
49+
certificateRetriever RetrieveFromSecretFunc,
5050
logger plog.Logger,
5151
) controllerlib.Controller {
5252
const name = "certs-expirer-controller"
@@ -59,7 +59,7 @@ func NewCertsExpirerController(
5959
k8sClient: k8sClient,
6060
secretInformer: secretInformer,
6161
renewBefore: renewBefore,
62-
secretKey: secretKey,
62+
certificateRetriever: certificateRetriever,
6363
logger: logger.WithName(name),
6464
},
6565
},
@@ -83,15 +83,14 @@ func (c *certsExpirerController) Sync(ctx controllerlib.Context) error {
8383
"controller", ctx.Name,
8484
"namespace", c.namespace,
8585
"name", c.certsSecretResourceName,
86-
"key", c.secretKey,
8786
"renewBefore", c.renewBefore.String(),
8887
)
8988
return nil
9089
}
9190

9291
notBefore, notAfter, err := c.getCertBounds(secret)
9392
if err != nil {
94-
return fmt.Errorf("failed to get cert bounds for secret %q with key %q: %w", secret.Name, c.secretKey, err)
93+
return fmt.Errorf("failed to get cert bounds for secret %q: %w", secret.Name, err)
9594
}
9695

9796
certAge := time.Since(notBefore)
@@ -100,7 +99,6 @@ func (c *certsExpirerController) Sync(ctx controllerlib.Context) error {
10099
"controller", ctx.Name,
101100
"namespace", c.namespace,
102101
"name", c.certsSecretResourceName,
103-
"key", c.secretKey,
104102
"renewBefore", c.renewBefore.String(),
105103
"notBefore", notBefore.String(),
106104
"notAfter", notAfter.String(),
@@ -130,7 +128,7 @@ func (c *certsExpirerController) Sync(ctx controllerlib.Context) error {
130128
// getCertBounds returns the NotBefore and NotAfter fields of the TLS
131129
// certificate in the provided secret, or an error.
132130
func (c *certsExpirerController) getCertBounds(secret *corev1.Secret) (time.Time, time.Time, error) {
133-
certPEM := secret.Data[c.secretKey]
131+
certPEM, _ := c.certificateRetriever(secret)
134132
if certPEM == nil {
135133
return time.Time{}, time.Time{}, constable.Error("failed to find certificate")
136134
}

internal/controller/apicerts/certs_expirer_test.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ func TestExpirerControllerFilters(t *testing.T) {
102102
nil, // k8sClient, not needed
103103
secretsInformer,
104104
withInformer.WithInformer,
105-
0, // renewBefore, not needed
106-
"", // not needed
105+
0, // renewBefore, not needed
106+
nil, // not needed
107107
plog.TestLogger(t, io.Discard),
108108
)
109109

@@ -134,14 +134,14 @@ func TestExpirerControllerSync(t *testing.T) {
134134
}{
135135
{
136136
name: "secret does not exist",
137-
wantLog: `{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"certs-expirer-controller","caller":"apicerts/certs_expirer.go:<line>$apicerts.(*certsExpirerController).Sync","message":"secret does not exist yet or was deleted","controller":"","namespace":"some-namespace","name":"some-resource-name","key":"some-awesome-key","renewBefore":"0s"}`,
137+
wantLog: `{"level":"info","timestamp":"2099-08-08T13:57:36.123456Z","logger":"certs-expirer-controller","caller":"apicerts/certs_expirer.go:<line>$apicerts.(*certsExpirerController).Sync","message":"secret does not exist yet or was deleted","controller":"","namespace":"some-namespace","name":"some-resource-name","renewBefore":"0s"}`,
138138
wantDelete: false,
139139
},
140140
{
141141
name: "secret missing key",
142142
fillSecretData: func(t *testing.T, m map[string][]byte) {},
143143
wantDelete: false,
144-
wantError: `failed to get cert bounds for secret "some-resource-name" with key "some-awesome-key": failed to find certificate`,
144+
wantError: `failed to get cert bounds for secret "some-resource-name": failed to find certificate`,
145145
},
146146
{
147147
name: "lifetime below threshold",
@@ -214,7 +214,7 @@ func TestExpirerControllerSync(t *testing.T) {
214214
require.NoError(t, err)
215215
},
216216
wantDelete: false,
217-
wantError: `failed to get cert bounds for secret "some-resource-name" with key "some-awesome-key": failed to decode certificate PEM`,
217+
wantError: `failed to get cert bounds for secret "some-resource-name": failed to decode certificate PEM`,
218218
},
219219
}
220220
for _, test := range tests {
@@ -265,7 +265,9 @@ func TestExpirerControllerSync(t *testing.T) {
265265
kubeInformers.Core().V1().Secrets(),
266266
controllerlib.WithInformer,
267267
test.renewBefore,
268-
fakeTestKey,
268+
func(secret *corev1.Secret) ([]byte, []byte) {
269+
return secret.Data[fakeTestKey], nil
270+
},
269271
plog.TestLogger(t, &log),
270272
)
271273

internal/controller/apicerts/certs_observer.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ import (
1818
type certsObserverController struct {
1919
namespace string
2020
certsSecretResourceName string
21+
certificateRetriever RetrieveFromSecretFunc
2122
dynamicCertProvider dynamiccert.Private
2223
secretInformer corev1informers.SecretInformer
2324
}
2425

2526
func NewCertsObserverController(
2627
namespace string,
2728
certsSecretResourceName string,
29+
certificateRetriever RetrieveFromSecretFunc,
2830
dynamicCertProvider dynamiccert.Private,
2931
secretInformer corev1informers.SecretInformer,
3032
withInformer pinnipedcontroller.WithInformerOptionFunc,
@@ -35,6 +37,7 @@ func NewCertsObserverController(
3537
Syncer: &certsObserverController{
3638
namespace: namespace,
3739
certsSecretResourceName: certsSecretResourceName,
40+
certificateRetriever: certificateRetriever,
3841
dynamicCertProvider: dynamicCertProvider,
3942
secretInformer: secretInformer,
4043
},
@@ -62,7 +65,7 @@ func (c *certsObserverController) Sync(_ controllerlib.Context) error {
6265
}
6366

6467
// Mutate the in-memory cert provider to update with the latest cert values.
65-
if err := c.dynamicCertProvider.SetCertKeyContent(certSecret.Data[TLSCertificateChainSecretKey], certSecret.Data[tlsPrivateKeySecretKey]); err != nil {
68+
if err := c.dynamicCertProvider.SetCertKeyContent(c.certificateRetriever(certSecret)); err != nil {
6669
return fmt.Errorf("failed to set serving cert/key content from secret %s/%s: %w", c.namespace, c.certsSecretResourceName, err)
6770
}
6871

internal/controller/apicerts/certs_observer_test.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
// Copyright 2020-2023 the Pinniped contributors. All Rights Reserved.
1+
// Copyright 2020-2024 the Pinniped contributors. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

44
package apicerts
55

66
import (
77
"context"
8+
_ "embed"
89
"strings"
910
"testing"
1011
"time"
@@ -23,6 +24,9 @@ import (
2324
"go.pinniped.dev/internal/testutil"
2425
)
2526

27+
//go:embed testdata/private_key_prefix.txt
28+
var privateKeyPrefix string
29+
2630
func TestObserverControllerInformerFilters(t *testing.T) {
2731
spec.Run(t, "informer filters", func(t *testing.T, when spec.G, it spec.S) {
2832
const installedInNamespace = "some-namespace"
@@ -40,6 +44,7 @@ func TestObserverControllerInformerFilters(t *testing.T) {
4044
installedInNamespace,
4145
certsSecretResourceName,
4246
nil,
47+
nil,
4348
secretsInformer,
4449
observableWithInformerOption.WithInformer, // make it possible to observe the behavior of the Filters
4550
)
@@ -119,6 +124,9 @@ func TestObserverControllerSync(t *testing.T) {
119124
subject = NewCertsObserverController(
120125
installedInNamespace,
121126
certsSecretResourceName,
127+
func(secret *corev1.Secret) ([]byte, []byte) {
128+
return secret.Data["some-key-for-certificate"], secret.Data["some-key-for-private-key"]
129+
},
122130
dynamicCertProvider,
123131
kubeInformers.Core().V1().Secrets(),
124132
controllerlib.WithInformer,
@@ -211,9 +219,9 @@ func TestObserverControllerSync(t *testing.T) {
211219
Namespace: installedInNamespace,
212220
},
213221
Data: map[string][]byte{
214-
"caCertificate": []byte("fake cert"),
215-
"tlsPrivateKey": key,
216-
"tlsCertificateChain": crt,
222+
"some-pretend-ca-EXTRA": []byte("fake cert"),
223+
"some-key-for-certificate": crt,
224+
"some-key-for-private-key": key,
217225
},
218226
}
219227
err = kubeInformerClient.Tracker().Add(apiServingCertSecret)
@@ -234,7 +242,7 @@ func TestObserverControllerSync(t *testing.T) {
234242

235243
actualCertChain, actualKey = dynamicCertProvider.CurrentCertKeyContent()
236244
r.True(strings.HasPrefix(string(actualCertChain), `-----BEGIN CERTIFICATE-----`), "not a cert:\n%s", string(actualCertChain))
237-
r.True(strings.HasPrefix(string(actualKey), `-----BEGIN PRIVATE KEY-----`), "not a key:\n%s", string(actualKey))
245+
r.True(strings.HasPrefix(string(actualKey), privateKeyPrefix), "not a key:\n%s", string(actualKey))
238246
})
239247
})
240248

0 commit comments

Comments
 (0)