Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 45 additions & 30 deletions pkg/cmd/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ func initCerts(cfg *config.Config) (*certchains.CertificateChains, error) {
}

func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
// Anchor certificate expiration to the next day. This forces
// homogenous expiry dates for all certificates with the same validity.
startTime := time.Now()
nextMidnight := time.Date(
startTime.Year(),
startTime.Month(),
startTime.Day()+1,
0, 0, 0, 0,
startTime.Location(),
)
alignValidity := func(baseValidity time.Duration) time.Duration {
targetExpiration := nextMidnight.Add(baseValidity)
return time.Until(targetExpiration)
}
Comment on lines +66 to +77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider using UTC to avoid DST drift.

startTime.Location() uses the system's local timezone. If DST transitions occur during the certificate's validity window, nextMidnight.Add(baseValidity) won't land on midnight—durations are absolute, but wall-clock midnight shifts by an hour at DST boundaries.

Using time.Now().UTC() would guarantee the expiry anchor is always exactly midnight.

Proposed fix
-	startTime := time.Now()
+	startTime := time.Now().UTC()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
startTime := time.Now()
nextMidnight := time.Date(
startTime.Year(),
startTime.Month(),
startTime.Day()+1,
0, 0, 0, 0,
startTime.Location(),
)
alignValidity := func(baseValidity time.Duration) time.Duration {
targetExpiration := nextMidnight.Add(baseValidity)
return time.Until(targetExpiration)
}
startTime := time.Now().UTC()
nextMidnight := time.Date(
startTime.Year(),
startTime.Month(),
startTime.Day()+1,
0, 0, 0, 0,
startTime.Location(),
)
alignValidity := func(baseValidity time.Duration) time.Duration {
targetExpiration := nextMidnight.Add(baseValidity)
return time.Until(targetExpiration)
}
🤖 Prompt for AI Agents
In `@pkg/cmd/init.go` around lines 66 - 77, The code builds nextMidnight and
alignValidity using the local time zone via startTime := time.Now(); replace
this with UTC to avoid DST drift: set startTime to time.Now().UTC() (and ensure
nextMidnight is constructed in that same UTC location) so that nextMidnight,
targetExpiration and the alignValidity(baseValidity) calculation are anchored to
a fixed UTC midnight rather than a potentially-shifting local midnight; update
references to startTime, nextMidnight and alignValidity accordingly.


_, svcNet, err := net.ParseCIDR(cfg.Network.ServiceNetwork[0])
if err != nil {
return nil, err
Expand Down Expand Up @@ -97,33 +112,33 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"kube-control-plane-signer",
cryptomaterial.KubeControlPlaneSignerCertDir(certsDir),
cryptomaterial.ShortLivedCertificateValidity,
alignValidity(cryptomaterial.ShortLivedCertificateValidity),
).WithClientCertificates(
&certchains.ClientCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "kube-controller-manager",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
UserInfo: &user.DefaultInfo{Name: "system:kube-controller-manager"},
},
&certchains.ClientCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "kube-scheduler",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
UserInfo: &user.DefaultInfo{Name: "system:kube-scheduler"},
},
&certchains.ClientCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "cluster-policy-controller",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
UserInfo: &user.DefaultInfo{Name: "system:kube-controller-manager"},
},
&certchains.ClientCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "route-controller-manager",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
UserInfo: serviceaccount.UserInfo("openshift-route-controller-manager", "route-controller-manager-sa", ""),
}),
Expand All @@ -132,12 +147,12 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"kube-apiserver-to-kubelet-signer",
cryptomaterial.KubeAPIServerToKubeletSignerCertDir(certsDir),
cryptomaterial.ShortLivedCertificateValidity,
alignValidity(cryptomaterial.ShortLivedCertificateValidity),
).WithClientCertificates(
&certchains.ClientCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "kube-apiserver-to-kubelet-client",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
UserInfo: &user.DefaultInfo{Name: "system:kube-apiserver", Groups: []string{"kube-master"}},
}),
Expand All @@ -146,19 +161,19 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"admin-kubeconfig-signer",
cryptomaterial.AdminKubeconfigSignerDir(certsDir),
cryptomaterial.LongLivedCertificateValidity,
alignValidity(cryptomaterial.LongLivedCertificateValidity),
).WithClientCertificates(
&certchains.ClientCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "admin-kubeconfig-client",
Validity: cryptomaterial.LongLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.LongLivedCertificateValidity),
},
UserInfo: &user.DefaultInfo{Name: "system:admin", Groups: []string{"system:masters"}},
}).WithClientCertificates(
&certchains.ClientCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "openshift-observability-client",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
UserInfo: &user.DefaultInfo{Name: "openshift-observability-client", Groups: []string{""}},
},
Expand All @@ -168,17 +183,17 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"kubelet-signer",
cryptomaterial.KubeletCSRSignerSignerCertDir(certsDir),
cryptomaterial.ShortLivedCertificateValidity,
alignValidity(cryptomaterial.ShortLivedCertificateValidity),
).WithSubCAs(
certchains.NewCertificateSigner(
"kube-csr-signer",
cryptomaterial.CSRSignerCertDir(certsDir),
cryptomaterial.ShortLivedCertificateValidity,
alignValidity(cryptomaterial.ShortLivedCertificateValidity),
).WithClientCertificates(
&certchains.ClientCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "kubelet-client",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
// userinfo per https://kubernetes.io/docs/reference/access-authn-authz/node/#overview
UserInfo: &user.DefaultInfo{Name: "system:node:" + cfg.CanonicalNodeName(), Groups: []string{"system:nodes"}},
Expand All @@ -187,7 +202,7 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
&certchains.ServingCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "kubelet-server",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
Hostnames: []string{cfg.Node.HostnameOverride, cfg.Node.NodeIP},
},
Expand All @@ -196,12 +211,12 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"aggregator-signer",
cryptomaterial.AggregatorSignerDir(certsDir),
cryptomaterial.ShortLivedCertificateValidity,
alignValidity(cryptomaterial.ShortLivedCertificateValidity),
).WithClientCertificates(
&certchains.ClientCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "aggregator-client",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
UserInfo: &user.DefaultInfo{Name: "system:openshift-aggregator"},
},
Expand All @@ -213,12 +228,12 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"service-ca",
cryptomaterial.ServiceCADir(certsDir),
cryptomaterial.LongLivedCertificateValidity,
alignValidity(cryptomaterial.LongLivedCertificateValidity),
).WithServingCertificates(
&certchains.ServingCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "route-controller-manager-serving",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
Hostnames: []string{
"route-controller-manager.openshift-route-controller-manager.svc",
Expand All @@ -230,12 +245,12 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"ingress-ca",
cryptomaterial.IngressCADir(certsDir),
cryptomaterial.LongLivedCertificateValidity,
alignValidity(cryptomaterial.LongLivedCertificateValidity),
).WithServingCertificates(
&certchains.ServingCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "router-default-serving",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
Hostnames: []string{
"*.apps." + cfg.DNS.BaseDomain, // wildcard for any additional auto-generated domains
Expand All @@ -248,12 +263,12 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"kube-apiserver-external-signer",
cryptomaterial.KubeAPIServerExternalSigner(certsDir),
cryptomaterial.LongLivedCertificateValidity,
alignValidity(cryptomaterial.LongLivedCertificateValidity),
).WithServingCertificates(
&certchains.ServingCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "kube-external-serving",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
Hostnames: externalCertNames,
},
Expand All @@ -262,12 +277,12 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"kube-apiserver-localhost-signer",
cryptomaterial.KubeAPIServerLocalhostSigner(certsDir),
cryptomaterial.LongLivedCertificateValidity,
alignValidity(cryptomaterial.LongLivedCertificateValidity),
).WithServingCertificates(
&certchains.ServingCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "kube-apiserver-localhost-serving",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
Hostnames: []string{
"localhost",
Expand All @@ -278,12 +293,12 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"kube-apiserver-service-network-signer",
cryptomaterial.KubeAPIServerServiceNetworkSigner(certsDir),
cryptomaterial.LongLivedCertificateValidity,
alignValidity(cryptomaterial.LongLivedCertificateValidity),
).WithServingCertificates(
&certchains.ServingCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "kube-apiserver-service-network-serving",
Validity: cryptomaterial.ShortLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.ShortLivedCertificateValidity),
},
Hostnames: []string{
"kubernetes",
Expand All @@ -308,28 +323,28 @@ func certSetup(cfg *config.Config) (*certchains.CertificateChains, error) {
certchains.NewCertificateSigner(
"etcd-signer",
cryptomaterial.EtcdSignerDir(certsDir),
cryptomaterial.LongLivedCertificateValidity,
alignValidity(cryptomaterial.LongLivedCertificateValidity),
).WithClientCertificates(
&certchains.ClientCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "apiserver-etcd-client",
Validity: cryptomaterial.LongLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.LongLivedCertificateValidity),
},
UserInfo: &user.DefaultInfo{Name: "etcd", Groups: []string{"etcd"}},
},
).WithPeerCertificiates(
&certchains.PeerCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "etcd-peer",
Validity: cryptomaterial.LongLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.LongLivedCertificateValidity),
},
UserInfo: &user.DefaultInfo{Name: "system:etcd-peer:etcd-client", Groups: []string{"system:etcd-peers"}},
Hostnames: []string{"localhost", cfg.Node.HostnameOverride, cfg.Node.NodeIP},
},
&certchains.PeerCertificateSigningRequestInfo{
CSRMeta: certchains.CSRMeta{
Name: "etcd-serving",
Validity: cryptomaterial.LongLivedCertificateValidity,
Validity: alignValidity(cryptomaterial.LongLivedCertificateValidity),
},
UserInfo: &user.DefaultInfo{Name: "system:etcd-server:etcd-client", Groups: []string{"system:etcd-servers"}},
Hostnames: []string{"localhost", cfg.Node.HostnameOverride, cfg.Node.NodeIP},
Expand Down
13 changes: 9 additions & 4 deletions test/suites/standard2/validate-certificate-rotation.robot
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ ${FUTURE_DAYS} 150
*** Test Cases ***
Certificate Rotation
[Documentation] Performs Certificate Expiration Rotation test
# Certificates expire at midnight of (tomorrow + validity). For short-lived certs,
# validity is 365 days, so expiry = tomorrow + 365 days.
${first_cert_date}= Compute Date After Days 365 ${OSSL_DATE_FORMAT}
Certs Should Expire On ${KUBE_SCHEDULER_CLIENT_CERT} ${first_cert_date}
${cert_should_expire_in_days}= Evaluate 365+${FUTURE_DAYS}
# After moving the clock forward by FUTURE_DAYS, regenerated certs will expire at
# (new tomorrow + 365). Since "new tomorrow" is (original tomorrow + FUTURE_DAYS + 1),
# the expected expiry from the original date is: tomorrow + 366 + FUTURE_DAYS.
${cert_should_expire_in_days}= Evaluate 366+${FUTURE_DAYS}
${cert_expiry_date}= Compute Date After Days ${cert_should_expire_in_days} ${OSSL_DATE_FORMAT}
${future_date}= Compute Date After Days ${FUTURE_DAYS} ${TIMEDATECTL_DATE_FORMAT}
Change System Date To ${future_date}
Expand Down Expand Up @@ -67,10 +72,10 @@ Change System Date To
Wait For MicroShift

Compute Date After Days
[Documentation] return system date after number of days elapsed
[Documentation] return system date after number of days elapsed from midnight tomorrow
[Arguments] ${number_of_days} ${date_format}
# date command is used here because we need to consider the remote vm timezone .
${future_date}= Command Should Work TZ=UTC date "+${date_format}" -d "$(date) + ${number_of_days} day"
# Certificates are aligned to expire at midnight of the next day + validity
${future_date}= Command Should Work TZ=UTC date "+${date_format}" -d "tomorrow + ${number_of_days} day"
RETURN ${future_date}

Certs Should Expire On
Expand Down