Skip to content

Commit fd660fe

Browse files
authored
feat: rotate nodes on CA rotation (#135)
1 parent 8954851 commit fd660fe

21 files changed

Lines changed: 783 additions & 58 deletions

.github/workflows/release-please.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,9 @@ jobs:
5151
go-version-file: ./go.mod
5252

5353
- name: Download last metadata
54-
run: gh release download -p metadata.yaml $(gh release list --exclude-drafts --exclude-pre-releases --order desc --limit 1 --json tagName --jq '.[0].tagName')
54+
run: |
55+
mkdir -p build
56+
gh release download -p metadata.yaml --dir build $(gh release list --exclude-drafts --exclude-pre-releases --order desc --limit 1 --json tagName --jq '.[0].tagName')
5557
continue-on-error: true
5658
- run: task build
5759

api/v1alpha1/hostedcontrolplane.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package v1alpha1
22

33
import (
4+
"time"
5+
46
corev1 "k8s.io/api/core/v1"
57
"k8s.io/utils/ptr"
68
)
@@ -91,3 +93,17 @@ func (ebs *ETCDBackupSecret) SecretAccessKeyKeyOrDefault() string {
9193
}
9294
return ptr.Deref(ebs.SecretAccessKeyKey, defaultKey)
9395
}
96+
97+
func (c *CertificatesSpec) RootCACertificateDurationOrDefault() time.Duration {
98+
if c.RootCACertificateDuration != nil {
99+
return c.RootCACertificateDuration.Duration
100+
}
101+
return 2 * 24 * time.Hour
102+
}
103+
104+
func (c *CertificatesSpec) CACertificateDurationOrDefault() time.Duration {
105+
if c.CACertificateDuration != nil {
106+
return c.CACertificateDuration.Duration
107+
}
108+
return 2 * 24 * time.Hour
109+
}

api/v1alpha1/hostedcontrolplane_conditions_consts.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ const (
1313
APIServerDeploymentsFailedReason = "APIServerDeploymentsFailed"
1414
CACertificatesReadyCondition = "CACertificatesReady"
1515
CACertificatesFailedReason = "CACertificatesFailed"
16+
CABundleReadyCondition = "CABundleReady"
17+
CABundleFailedReason = "CABundleFailed"
18+
CARotationAnnotationReadyCondition = "CARotationAnnotationReady"
19+
CARotationAnnotationFailedReason = "CARotationAnnotationFailed"
1620
CertificatesReadyCondition = "CertificatesReady"
1721
CertificatesFailedReason = "CertificatesFailed"
1822
KubeconfigReadyCondition = "KubeconfigReady"

api/v1alpha1/hostedcontrolplane_types.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,24 @@ type HostedControlPlaneInlineSpec struct {
7676
// is generated and passed to the API server via --authentication-config.
7777
//+kubebuilder:validation:Optional
7878
OIDCProviders map[string]OIDCProvider `json:"oidcProviders,omitempty"`
79+
//+kubebuilder:validation:Optional
80+
Certificates CertificatesSpec `json:"certificates,omitempty"`
81+
}
82+
83+
// CertificatesSpec configures the durations of CA certificates managed by cert-manager.
84+
// Renewal always occurs at 50% of the configured duration.
85+
// The root CA must have a duration greater than or equal to the intermediate CA.
86+
type CertificatesSpec struct {
87+
// RootCACertificateDuration is the validity duration of the Kubernetes root CA certificate,
88+
// which signs all intermediate CA certificates (front-proxy, etcd).
89+
// Defaults to 48h (2 days).
90+
//+kubebuilder:validation:Optional
91+
RootCACertificateDuration *metav1.Duration `json:"rootCaCertificateDuration,omitempty"`
92+
// CACertificateDuration is the validity duration of intermediate CA certificates
93+
// (front-proxy CA, etcd CA). Must be less than or equal to RootCACertificateDuration.
94+
// Defaults to 48h (2 days).
95+
//+kubebuilder:validation:Optional
96+
CACertificateDuration *metav1.Duration `json:"caCertificateDuration,omitempty"`
7997
}
8098

8199
type GatewayReference struct {

api/v1alpha1/webhook/hostedcontrolplane_webhook.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ func (w *hostedControlPlaneWebhook) ValidateCreate(
9090
}
9191
}
9292

93+
certs := &newHostedControlPlane.Spec.Certificates
94+
if certs.RootCACertificateDurationOrDefault() < certs.CACertificateDurationOrDefault() {
95+
fieldErrs = append(fieldErrs, field.Invalid(
96+
w.specPath.Child("certificates").Child("rootCaCertificateDuration"),
97+
newHostedControlPlane.Spec.Certificates.RootCACertificateDuration,
98+
"rootCaCertificateDuration must be greater than or equal to caCertificateDuration",
99+
))
100+
}
101+
93102
if len(fieldErrs) > 0 {
94103
return warnings, apierrors.NewInvalid(w.groupKind, newHostedControlPlane.Name, fieldErrs)
95104
}

api/v1alpha1/webhook/hostedcontrolplane_webhook_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package webhook
22

33
import (
44
"testing"
5+
"time"
56

67
. "github.com/onsi/gomega"
78
"github.com/teutonet/cluster-api-provider-hosted-control-plane/api/v1alpha1"
@@ -183,6 +184,79 @@ func TestHostedControlPlaneWebhook_ValidateCreate(t *testing.T) {
183184
},
184185
expectErr: false,
185186
},
187+
{
188+
name: "root CA duration longer than intermediate CA duration - valid",
189+
hcp: &v1alpha1.HostedControlPlane{
190+
ObjectMeta: metav1.ObjectMeta{Name: "test-hcp", Namespace: "default"},
191+
Spec: v1alpha1.HostedControlPlaneSpec{
192+
Version: "v1.28.0",
193+
HostedControlPlaneInlineSpec: v1alpha1.HostedControlPlaneInlineSpec{
194+
Gateway: v1alpha1.GatewayReference{Name: "test-gateway", Namespace: "default"},
195+
ETCD: v1alpha1.ETCDComponent{AutoGrow: ptr.To(true)},
196+
Certificates: v1alpha1.CertificatesSpec{
197+
RootCACertificateDuration: &metav1.Duration{Duration: 10 * 365 * 24 * time.Hour},
198+
CACertificateDuration: &metav1.Duration{Duration: 5 * 365 * 24 * time.Hour},
199+
},
200+
},
201+
},
202+
},
203+
expectErr: false,
204+
},
205+
{
206+
name: "root CA duration equal to intermediate CA duration - valid",
207+
hcp: &v1alpha1.HostedControlPlane{
208+
ObjectMeta: metav1.ObjectMeta{Name: "test-hcp", Namespace: "default"},
209+
Spec: v1alpha1.HostedControlPlaneSpec{
210+
Version: "v1.28.0",
211+
HostedControlPlaneInlineSpec: v1alpha1.HostedControlPlaneInlineSpec{
212+
Gateway: v1alpha1.GatewayReference{Name: "test-gateway", Namespace: "default"},
213+
ETCD: v1alpha1.ETCDComponent{AutoGrow: ptr.To(true)},
214+
Certificates: v1alpha1.CertificatesSpec{
215+
RootCACertificateDuration: &metav1.Duration{Duration: 5 * 365 * 24 * time.Hour},
216+
CACertificateDuration: &metav1.Duration{Duration: 5 * 365 * 24 * time.Hour},
217+
},
218+
},
219+
},
220+
},
221+
expectErr: false,
222+
},
223+
{
224+
name: "root CA duration shorter than intermediate CA duration - invalid",
225+
hcp: &v1alpha1.HostedControlPlane{
226+
ObjectMeta: metav1.ObjectMeta{Name: "test-hcp", Namespace: "default"},
227+
Spec: v1alpha1.HostedControlPlaneSpec{
228+
Version: "v1.28.0",
229+
HostedControlPlaneInlineSpec: v1alpha1.HostedControlPlaneInlineSpec{
230+
Gateway: v1alpha1.GatewayReference{Name: "test-gateway", Namespace: "default"},
231+
ETCD: v1alpha1.ETCDComponent{AutoGrow: ptr.To(true)},
232+
Certificates: v1alpha1.CertificatesSpec{
233+
RootCACertificateDuration: &metav1.Duration{Duration: 1 * 365 * 24 * time.Hour},
234+
CACertificateDuration: &metav1.Duration{Duration: 5 * 365 * 24 * time.Hour},
235+
},
236+
},
237+
},
238+
},
239+
expectErr: true,
240+
errMsg: "rootCaCertificateDuration must be greater than or equal to caCertificateDuration",
241+
},
242+
{
243+
name: "root CA duration set shorter than default intermediate CA duration - invalid",
244+
hcp: &v1alpha1.HostedControlPlane{
245+
ObjectMeta: metav1.ObjectMeta{Name: "test-hcp", Namespace: "default"},
246+
Spec: v1alpha1.HostedControlPlaneSpec{
247+
Version: "v1.28.0",
248+
HostedControlPlaneInlineSpec: v1alpha1.HostedControlPlaneInlineSpec{
249+
Gateway: v1alpha1.GatewayReference{Name: "test-gateway", Namespace: "default"},
250+
ETCD: v1alpha1.ETCDComponent{AutoGrow: ptr.To(true)},
251+
Certificates: v1alpha1.CertificatesSpec{
252+
RootCACertificateDuration: &metav1.Duration{Duration: 24 * time.Hour},
253+
},
254+
},
255+
},
256+
},
257+
expectErr: true,
258+
errMsg: "rootCaCertificateDuration must be greater than or equal to caCertificateDuration",
259+
},
186260
}
187261

188262
for _, tt := range tests {

api/v1alpha1/zz_generated.deepcopy.go

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

go.mod

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ require (
2929
go.opentelemetry.io/otel/trace v1.43.0
3030
go.uber.org/zap v1.27.1
3131
go4.org v0.0.0-20260112195520-a5071408f32f
32-
golang.org/x/text v0.35.0
32+
golang.org/x/text v0.38.0
3333
google.golang.org/grpc v1.79.3
3434
k8s.io/api v0.35.3
3535
k8s.io/apiextensions-apiserver v0.35.3
@@ -170,15 +170,15 @@ require (
170170
go.yaml.in/yaml/v2 v2.4.3 // indirect
171171
go.yaml.in/yaml/v3 v3.0.4 // indirect
172172
go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect
173-
golang.org/x/crypto v0.49.0 // indirect
173+
golang.org/x/crypto v0.53.0 // indirect
174174
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
175-
golang.org/x/net v0.52.0 // indirect
175+
golang.org/x/net v0.56.0 // indirect
176176
golang.org/x/oauth2 v0.35.0 // indirect
177-
golang.org/x/sync v0.20.0 // indirect
178-
golang.org/x/sys v0.42.0 // indirect
179-
golang.org/x/term v0.41.0 // indirect
177+
golang.org/x/sync v0.21.0 // indirect
178+
golang.org/x/sys v0.46.0 // indirect
179+
golang.org/x/term v0.44.0 // indirect
180180
golang.org/x/time v0.14.0 // indirect
181-
golang.org/x/tools v0.42.0 // indirect
181+
golang.org/x/tools v0.45.0 // indirect
182182
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
183183
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
184184
google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect

go.sum

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -423,47 +423,47 @@ go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/W
423423
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
424424
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
425425
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
426-
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
427-
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
426+
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
427+
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
428428
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
429429
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
430430
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
431431
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
432-
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
433-
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
432+
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
433+
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
434434
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
435435
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
436436
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
437437
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
438-
golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0=
439-
golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw=
438+
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
439+
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
440440
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
441441
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
442442
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
443443
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
444444
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
445-
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
446-
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
445+
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
446+
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
447447
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
448448
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
449449
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
450450
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
451-
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
452-
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
453-
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
454-
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
451+
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
452+
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
453+
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
454+
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
455455
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
456456
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
457-
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
458-
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
457+
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
458+
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
459459
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
460460
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
461461
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
462462
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
463463
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
464464
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
465-
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
466-
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
465+
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
466+
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
467467
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
468468
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
469469
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

0 commit comments

Comments
 (0)