Skip to content

Commit 0324055

Browse files
Merge pull request #105 from marun/4.2-automated-rotation
Bug 1774156: 4.2 cherry-picks in support of automated service ca rotation
2 parents f672057 + 15bca69 commit 0324055

File tree

205 files changed

+11166
-796
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

205 files changed

+11166
-796
lines changed

bindata/v4.0.0/service-serving-cert-signer-controller/defaultconfig.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ kind: ServiceServingCertSignerConfig
33
signer:
44
certFile: /var/run/secrets/signing-key/tls.crt
55
keyFile: /var/run/secrets/signing-key/tls.key
6+
intermediateCertFile: /var/run/secrets/signing-key/intermediate-ca.crt
67
authentication:
78
disabled: true
89
authorization:

glide.lock

Lines changed: 17 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

glide.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@ import:
1515
- package: k8s.io/component-base
1616
version: kubernetes-1.14.0
1717
- package: github.com/openshift/api
18-
version: master
18+
version: release-4.2
1919
- package: github.com/openshift/client-go
20-
version: master
20+
version: release-4.2
2121
- package: github.com/openshift/library-go
22-
version: master
22+
version: release-4.2
2323
- package: monis.app/go
2424
version: master
2525

manifests/06_config.yaml

Lines changed: 0 additions & 10 deletions
This file was deleted.

pkg/controller/api/api.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,22 @@ import (
66
"k8s.io/apimachinery/pkg/apis/meta/v1"
77
)
88

9+
// Constants for Service CA
10+
const (
11+
// ForcedRotationReasonAnnotationName is the name of an annotation indicating
12+
// the most recent reason that a service CA rotation was forced. The annotation
13+
// will be set on the signing secret after the successful completion of a forced
14+
// rotation.
15+
ForcedRotationReasonAnnotationName = "service-ca.operators.openshift.io/forced-rotation-reason"
16+
// BundleDataKey is the key used to identify the CA bundle in the signing
17+
// secret.
18+
BundleDataKey = "ca-bundle.crt"
19+
// IntermediateDataKey is the key used to identify the post-rotation
20+
// trust-bridging certificate in the signing secret.
21+
IntermediateDataKey = "intermediate-ca.crt"
22+
)
23+
24+
// Constants for CA bundle injection
925
const (
1026
InjectCABundleAnnotationName = "service.beta.openshift.io/inject-cabundle"
1127
AlphaInjectCABundleAnnotationName = "service.alpha.openshift.io/inject-cabundle"
@@ -23,6 +39,14 @@ func HasInjectCABundleAnnotationUpdate(old, cur v1.Object) bool {
2339

2440
// Annotations on service
2541
const (
42+
// TODO(marun) When adding a GA serving cert annotation, consider
43+
// discontinuing the practice of including the issuing cert with the serving
44+
// cert. This behavior was accidental (at some point the issuing CA was an
45+
// intermediate rather than the current self-signed root) but had to be
46+
// maintained to support legacy clients that ended up reusing the serving cert
47+
// as a CA bundle. Clients of a GA annotation should be expected to use a
48+
// proper CA bundle.
49+
2650
// ServingCertSecretAnnotation stores the name of the secret to generate into.
2751
ServingCertSecretAnnotation = "service.beta.openshift.io/serving-cert-secret-name"
2852
AlphaServingCertSecretAnnotation = "service.alpha.openshift.io/serving-cert-secret-name"

pkg/controller/servingcert/controller/secret_creating_controller.go

Lines changed: 43 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package controller
22

33
import (
4+
"bytes"
5+
"crypto/x509"
46
"fmt"
57
"strconv"
68
"time"
@@ -31,9 +33,10 @@ type serviceServingCertController struct {
3133
serviceLister listers.ServiceLister
3234
secretLister listers.SecretLister
3335

34-
ca *crypto.CA
35-
dnsSuffix string
36-
maxRetries int
36+
ca *crypto.CA
37+
intermediateCACert *x509.Certificate
38+
dnsSuffix string
39+
maxRetries int
3740

3841
// standard controller loop
3942
// services that need to be checked
@@ -43,17 +46,18 @@ type serviceServingCertController struct {
4346
syncHandler controller.SyncFunc
4447
}
4548

46-
func NewServiceServingCertController(services informers.ServiceInformer, secrets informers.SecretInformer, serviceClient kcoreclient.ServicesGetter, secretClient kcoreclient.SecretsGetter, ca *crypto.CA, dnsSuffix string) controller.Runner {
49+
func NewServiceServingCertController(services informers.ServiceInformer, secrets informers.SecretInformer, serviceClient kcoreclient.ServicesGetter, secretClient kcoreclient.SecretsGetter, ca *crypto.CA, intermediateCACert *x509.Certificate, dnsSuffix string) controller.Runner {
4750
sc := &serviceServingCertController{
4851
serviceClient: serviceClient,
4952
secretClient: secretClient,
5053

5154
serviceLister: services.Lister(),
5255
secretLister: secrets.Lister(),
5356

54-
ca: ca,
55-
dnsSuffix: dnsSuffix,
56-
maxRetries: 10,
57+
ca: ca,
58+
intermediateCACert: intermediateCACert,
59+
dnsSuffix: dnsSuffix,
60+
maxRetries: 10,
5761
}
5862

5963
sc.syncHandler = sc.syncService
@@ -129,7 +133,7 @@ func (sc *serviceServingCertController) generateCert(serviceCopy *corev1.Service
129133
}
130134

131135
secret := toBaseSecret(serviceCopy)
132-
if err := toRequiredSecret(sc.dnsSuffix, sc.ca, serviceCopy, secret); err != nil {
136+
if err := toRequiredSecret(sc.dnsSuffix, sc.ca, sc.intermediateCACert, serviceCopy, secret); err != nil {
133137
return err
134138
}
135139

@@ -212,8 +216,13 @@ func (sc *serviceServingCertController) requiresCertGeneration(service *corev1.S
212216
return true
213217
}
214218

215-
// Returns true if the secret certificate has the same issuer common name as the current CA, false
216-
// if not or if there is a parsing error.
219+
// Returns true if the secret certificate was issued by the current CA,
220+
// false if not or if there was a parsing error.
221+
//
222+
// Determination of issuance will default to comparison of the certificate's
223+
// AuthorityKeyID and the CA's SubjectKeyId, and fall back to comparison of the
224+
// certificate's Issuer.CommonName and the CA's Subject.CommonName (in case the CA was
225+
// generated prior to the addition of key identifiers).
217226
func (sc *serviceServingCertController) issuedByCurrentCA(secret *corev1.Secret) bool {
218227
certs, err := cert.ParseCertsPEM(secret.Data[corev1.TLSCertKey])
219228
if err != nil {
@@ -227,6 +236,17 @@ func (sc *serviceServingCertController) issuedByCurrentCA(secret *corev1.Secret)
227236
return false
228237
}
229238

239+
certAuthorityKeyId := certs[0].AuthorityKeyId
240+
caSubjectKeyId := sc.ca.Config.Certs[0].SubjectKeyId
241+
// Use key identifier chaining if the SubjectKeyId is populated in the CA
242+
// certificate. AuthorityKeyId may not be set in the serving certificate if it was
243+
// generated before serving cert generation was updated to include the field.
244+
if len(caSubjectKeyId) > 0 {
245+
return bytes.Compare(certAuthorityKeyId, caSubjectKeyId) == 0
246+
}
247+
248+
// Fall back to name-based chaining for a legacy service CA that was generated
249+
// without SubjectKeyId or AuthorityKeyId.
230250
return certs[0].Issuer.CommonName == sc.commonName()
231251
}
232252

@@ -301,23 +321,31 @@ func toBaseSecret(service *corev1.Service) *corev1.Secret {
301321
}
302322
}
303323

304-
func getServingCert(dnsSuffix string, ca *crypto.CA, service *corev1.Service) (*crypto.TLSCertificateConfig, error) {
305-
dnsName := service.Name + "." + service.Namespace + ".svc"
324+
func MakeServingCert(dnsSuffix string, ca *crypto.CA, intermediateCACert *x509.Certificate, serviceObjectMeta *metav1.ObjectMeta) (*crypto.TLSCertificateConfig, error) {
325+
dnsName := serviceObjectMeta.Name + "." + serviceObjectMeta.Namespace + ".svc"
306326
fqDNSName := dnsName + "." + dnsSuffix
307327
certificateLifetime := 365 * 2 // 2 years
308328
servingCert, err := ca.MakeServerCert(
309329
sets.NewString(dnsName, fqDNSName),
310330
certificateLifetime,
311-
cryptoextensions.ServiceServerCertificateExtensionV1(service),
331+
cryptoextensions.ServiceServerCertificateExtensionV1(serviceObjectMeta.UID),
312332
)
313333
if err != nil {
314334
return nil, err
315335
}
336+
337+
// Including the intermediate cert will ensure that clients with a
338+
// stale ca bundle (containing the previous CA but not the current
339+
// one) will be able to trust the serving cert.
340+
if intermediateCACert != nil {
341+
servingCert.Certs = append(servingCert.Certs, intermediateCACert)
342+
}
343+
316344
return servingCert, nil
317345
}
318346

319-
func toRequiredSecret(dnsSuffix string, ca *crypto.CA, service *corev1.Service, secretCopy *corev1.Secret) error {
320-
servingCert, err := getServingCert(dnsSuffix, ca, service)
347+
func toRequiredSecret(dnsSuffix string, ca *crypto.CA, intermediateCACert *x509.Certificate, service *corev1.Service, secretCopy *corev1.Secret) error {
348+
servingCert, err := MakeServingCert(dnsSuffix, ca, intermediateCACert, &service.ObjectMeta)
321349
if err != nil {
322350
return err
323351
}

pkg/controller/servingcert/controller/secret_creating_controller_test.go

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1818
"k8s.io/apimachinery/pkg/runtime"
1919
"k8s.io/apimachinery/pkg/types"
20+
"k8s.io/apimachinery/pkg/util/sets"
2021
"k8s.io/apimachinery/pkg/watch"
2122
"k8s.io/client-go/informers"
2223
"k8s.io/client-go/kubernetes/fake"
@@ -29,27 +30,6 @@ import (
2930

3031
const signerName = "openshift-service-serving-signer"
3132

32-
const testCert = `
33-
-----BEGIN CERTIFICATE-----
34-
MIIDETCCAfmgAwIBAgIUTNjtvaP8ZzRNabhgLhuqHONxuTYwDQYJKoZIhvcNAQEL
35-
BQAwKzEpMCcGA1UEAwwgb3BlbnNoaWZ0LXNlcnZpY2Utc2VydmluZy1zaWduZXIw
36-
HhcNMTkwNDE3MTkyMjEwWhcNMjAwNDE2MTkyMjEwWjAPMQ0wCwYDVQQDDAR0ZXN0
37-
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6QmfkmO9eaSqONefWAKO
38-
ZGwYe02tBltRCWsE96GslVq+aWVc6SSOVcghv9bL4xZQy2TQNxDRKNBDX0Fwk5TR
39-
Aj2aMzXuJ+HzxwyCK3o5SqwQYnOlgFuUpKShtpM4jye6hxwllFr059MvRAUZZNVX
40-
Fkv0Gh2CJcry/wPAuXVZV03GOixB/TeFKSEmpSSdMyhK3hFve3XkeW88rtuP9cG1
41-
duy3onAGZQ4V86TrwYsPJVo9t7IDS+SheIqHEhbfYouS6zBEvpeZMz+evP4q2AJs
42-
FXfLSQJi+HyHYdGovbBO9+ZotJ609hrkJ4/cMDJOxXeG8YBr6x9hgZtH4GO55jeS
43-
kQIDAQABo0kwRzALBgNVHQ8EBAMCBeAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwCQYD
44-
VR0TBAIwADAYBgNVHREEETAPgg0qLmZvby5iYXIuY29tMA0GCSqGSIb3DQEBCwUA
45-
A4IBAQB9HfCAUdxoVyaA7KxU1a838sC2Z/NrbqC2+u1eR1SVilRjykD+k3v6XnM9
46-
ku7TYpf8YRbgRmbu864zYE1ibxMwVGqQlMR9tNm2cA6nEDke2sDqH0JbS5lZPX+a
47-
DA9tdnJtx+/uxsuz6I68rp5kDPiTjUTjxc9/Ob3vLiCopBikuiC0H9cPdq1lNHFJ
48-
k89OWEXatvLbNvVRioyptH1hf5lweVtDjytnj7gaMchhlH8qK6u4iggLxViVxheO
49-
fiNJr2o4fexMfez1J6u7ZuM2w50CuOHuAGVAdrVdE8LYjr0SouwzVt20Uc0swLRE
50-
GKM7HG83Wj2hA+DWdy9ZJAdBLISB
51-
-----END CERTIFICATE-----
52-
`
5333
const testCertUnknownIssuer = `
5434
-----BEGIN CERTIFICATE-----
5535
MIIDETCCAfmgAwIBAgIUdbBKh0jOJxli4wl34q0TYJu8+n0wDQYJKoZIhvcNAQEL
@@ -107,7 +87,7 @@ func controllerSetup(startingObjects []runtime.Object, t *testing.T) ( /*caName*
10787
controller := NewServiceServingCertController(
10888
informerFactory.Core().V1().Services(),
10989
informerFactory.Core().V1().Secrets(),
110-
kubeclient.CoreV1(), kubeclient.CoreV1(), ca, "cluster.local",
90+
kubeclient.CoreV1(), kubeclient.CoreV1(), ca, nil, "cluster.local",
11191
)
11292

11393
return signerName, kubeclient, fakeWatch, fakeSecretWatch, controller.(*serviceServingCertController), informerFactory
@@ -851,9 +831,22 @@ func TestSkipGenerationControllerFlow(t *testing.T) {
851831
return err
852832
}
853833

834+
// Generate a serving cert from the ca to ensure key identity chaining
835+
newServingCert, err := controller.ca.MakeServerCert(
836+
sets.NewString("foo"),
837+
crypto.DefaultCertificateLifetimeInDays,
838+
)
839+
if err != nil {
840+
t.Fatalf("failed to generate serving cert: %v", err)
841+
}
842+
certPEM, err := crypto.EncodeCertificates(newServingCert.Certs[0])
843+
if err != nil {
844+
t.Fatalf("failed to encode serving cert to PEM: %v", err)
845+
}
846+
854847
secretToAdd := &v1.Secret{
855848
Data: map[string][]byte{
856-
v1.TLSCertKey: []byte(testCert),
849+
v1.TLSCertKey: certPEM,
857850
},
858851
}
859852
secretToAdd.Name = expectedSecretName

0 commit comments

Comments
 (0)