kubeadm: add support for custom cert validity period in v1beta4

Allow the user to pass custom cert validity period with
ClusterConfiguration.CertificateValidityPeriod and
CACertificateValidityPeriod.

The defaults remain 1 year for regular cert and 10 years for CA.
Show warnings if the provided values are more than the defaults.

Additional changes:
- In "certs show-expiration" use HumanDuration() to print
more detailed durations instead of ShortHumanDuration().
- Add a new kubeadm util GetStartTime() which can be used
to consistently get a UTC time for tasks like writing certs
and unit tests.
- Update unit tests to validate the new customizable NotAfter.
This commit is contained in:
Lubomir I. Ivanov 2024-04-30 15:56:13 +03:00
parent 031e6c3f4d
commit 74e1438d86
27 changed files with 574 additions and 127 deletions

View File

@ -26,6 +26,7 @@ import (
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
// Funcs returns the fuzzer functions for the kubeadm apis.
@ -97,6 +98,8 @@ func fuzzClusterConfiguration(obj *kubeadm.ClusterConfiguration, c fuzz.Continue
obj.Etcd.Local.ExtraEnvs = []kubeadm.EnvVar{}
obj.EncryptionAlgorithm = kubeadm.EncryptionAlgorithmRSA2048
obj.Proxy.Disabled = false
obj.CertificateValidityPeriod = &metav1.Duration{Duration: constants.CertificateValidityPeriod}
obj.CACertificateValidityPeriod = &metav1.Duration{Duration: constants.CACertificateValidityPeriod}
}
func fuzzDNS(obj *kubeadm.DNS, c fuzz.Continue) {

View File

@ -149,6 +149,14 @@ type ClusterConfiguration struct {
// EncryptionAlgorithm holds the type of asymmetric encryption algorithm used for keys and certificates.
// Can be one of "RSA-2048" (default), "RSA-3072", "RSA-4096" or "ECDSA-P256".
EncryptionAlgorithm EncryptionAlgorithmType
// CertificateValidityPeriod specifies the validity period for a non-CA certificate generated by kubeadm.
// Default value: 8760h (365 days * 24 hours = 1 year)
CertificateValidityPeriod *metav1.Duration
// CACertificateValidityPeriod specifies the validity period for a CA certificate generated by kubeadm.
// Default value: 87600h (365 days * 24 hours * 10 = 10 years)
CACertificateValidityPeriod *metav1.Duration
}
// ControlPlaneComponent holds settings common to control plane component of the cluster

View File

@ -19,10 +19,12 @@ package v1beta3
import (
"sort"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/utils/ptr"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
// Convert_kubeadm_InitConfiguration_To_v1beta3_InitConfiguration converts a private InitConfiguration to public InitConfiguration.
@ -38,9 +40,11 @@ func Convert_v1beta3_InitConfiguration_To_kubeadm_InitConfiguration(in *InitConf
}
err = Convert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration(&ClusterConfiguration{}, &out.ClusterConfiguration, s)
// Required to pass fuzzer tests. This ClusterConfiguration is empty and is never defaulted.
// If we call Convert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration() it will receive
// a default value, thus here we need to reset it back to "".
// If we call Convert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration() these fields will receive
// a default value, thus here we need to reset them back to empty.
out.EncryptionAlgorithm = ""
out.CertificateValidityPeriod = nil
out.CACertificateValidityPeriod = nil
// Set default timeouts.
kubeadm.SetDefaultTimeouts(&out.Timeouts)
return err
@ -64,9 +68,11 @@ func Convert_kubeadm_ClusterConfiguration_To_v1beta3_ClusterConfiguration(in *ku
// Convert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration is required due to missing EncryptionAlgorithm in v1beta3.
func Convert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in *ClusterConfiguration, out *kubeadm.ClusterConfiguration, s conversion.Scope) error {
// Required to pass validation and fuzzer tests. The field is missing in v1beta3, thus we have to
// default it to a sane (default) value in the internal type.
// Required to pass validation and fuzzer tests. These fields are missing in v1beta3, thus we have to
// default them to a sane (default) value in the internal type.
out.EncryptionAlgorithm = kubeadm.EncryptionAlgorithmRSA2048
out.CertificateValidityPeriod = &metav1.Duration{Duration: constants.CertificateValidityPeriod}
out.CACertificateValidityPeriod = &metav1.Duration{Duration: constants.CACertificateValidityPeriod}
return autoConvert_v1beta3_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in, out, s)
}

View File

@ -361,6 +361,8 @@ func autoConvert_kubeadm_ClusterConfiguration_To_v1beta3_ClusterConfiguration(in
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
out.ClusterName = in.ClusterName
// WARNING: in.EncryptionAlgorithm requires manual conversion: does not exist in peer-type
// WARNING: in.CertificateValidityPeriod requires manual conversion: does not exist in peer-type
// WARNING: in.CACertificateValidityPeriod requires manual conversion: does not exist in peer-type
return nil
}

View File

@ -108,6 +108,17 @@ func SetDefaults_ClusterConfiguration(obj *ClusterConfiguration) {
obj.EncryptionAlgorithm = DefaultEncryptionAlgorithm
}
if obj.CertificateValidityPeriod == nil {
obj.CertificateValidityPeriod = &metav1.Duration{
Duration: constants.CertificateValidityPeriod,
}
}
if obj.CACertificateValidityPeriod == nil {
obj.CACertificateValidityPeriod = &metav1.Duration{
Duration: constants.CACertificateValidityPeriod,
}
}
SetDefaults_Etcd(obj)
}

View File

@ -44,6 +44,10 @@ limitations under the License.
// and will be ignored when passing --config to upgrade subcommands.
// - Add a `Timeouts` structure to `InitConfiguration`, `JoinConfiguration`, `ResetConfiguration` and `UpgradeConfiguration`
// that can be used to configure various timeouts.
// - Add a `CertificateValidityPeriod` and `CACertificateValidityPeriod` fields to `ClusterConfiguration`. These fields
// can be used to control the validity period of certificates generated by kubeadm during sub-commands such as `init`,
// `join`, `upgrade` and `certs`. Default values continue to be 1 year for non-CA certificates and 10 years for CA
// certificates. Only non-CA certificates continue to be renewable by `kubeadm certs renew`.
//
// Migration from old kubeadm config versions
//

View File

@ -152,6 +152,16 @@ type ClusterConfiguration struct {
// Can be one of "RSA-2048" (default), "RSA-3072", "RSA-4096" or "ECDSA-P256".
// +optional
EncryptionAlgorithm EncryptionAlgorithmType `json:"encryptionAlgorithm,omitempty"`
// CertificateValidityPeriod specifies the validity period for a non-CA certificate generated by kubeadm.
// Default value: 8760h (365 days * 24 hours = 1 year)
// +optional
CertificateValidityPeriod *metav1.Duration `json:"certificateValidityPeriod,omitempty"`
// CACertificateValidityPeriod specifies the validity period for a CA certificate generated by kubeadm.
// Default value: 87600h (365 days * 24 hours * 10 = 10 years)
// +optional
CACertificateValidityPeriod *metav1.Duration `json:"caCertificateValidityPeriod,omitempty"`
}
// ControlPlaneComponent holds settings common to control plane component of the cluster

View File

@ -24,8 +24,8 @@ package v1beta4
import (
unsafe "unsafe"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
@ -453,6 +453,8 @@ func autoConvert_v1beta4_ClusterConfiguration_To_kubeadm_ClusterConfiguration(in
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
out.ClusterName = in.ClusterName
out.EncryptionAlgorithm = kubeadm.EncryptionAlgorithmType(in.EncryptionAlgorithm)
out.CertificateValidityPeriod = (*v1.Duration)(unsafe.Pointer(in.CertificateValidityPeriod))
out.CACertificateValidityPeriod = (*v1.Duration)(unsafe.Pointer(in.CACertificateValidityPeriod))
return nil
}
@ -488,6 +490,8 @@ func autoConvert_kubeadm_ClusterConfiguration_To_v1beta4_ClusterConfiguration(in
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
out.ClusterName = in.ClusterName
out.EncryptionAlgorithm = EncryptionAlgorithmType(in.EncryptionAlgorithm)
out.CertificateValidityPeriod = (*v1.Duration)(unsafe.Pointer(in.CertificateValidityPeriod))
out.CACertificateValidityPeriod = (*v1.Duration)(unsafe.Pointer(in.CACertificateValidityPeriod))
return nil
}
@ -654,7 +658,7 @@ func autoConvert_v1beta4_HostPathMount_To_kubeadm_HostPathMount(in *HostPathMoun
out.HostPath = in.HostPath
out.MountPath = in.MountPath
out.ReadOnly = in.ReadOnly
out.PathType = v1.HostPathType(in.PathType)
out.PathType = corev1.HostPathType(in.PathType)
return nil
}
@ -668,7 +672,7 @@ func autoConvert_kubeadm_HostPathMount_To_v1beta4_HostPathMount(in *kubeadm.Host
out.HostPath = in.HostPath
out.MountPath = in.MountPath
out.ReadOnly = in.ReadOnly
out.PathType = v1.HostPathType(in.PathType)
out.PathType = corev1.HostPathType(in.PathType)
return nil
}
@ -856,10 +860,10 @@ func Convert_kubeadm_Networking_To_v1beta4_Networking(in *kubeadm.Networking, ou
func autoConvert_v1beta4_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(in *NodeRegistrationOptions, out *kubeadm.NodeRegistrationOptions, s conversion.Scope) error {
out.Name = in.Name
out.CRISocket = in.CRISocket
out.Taints = *(*[]v1.Taint)(unsafe.Pointer(&in.Taints))
out.Taints = *(*[]corev1.Taint)(unsafe.Pointer(&in.Taints))
out.KubeletExtraArgs = *(*[]kubeadm.Arg)(unsafe.Pointer(&in.KubeletExtraArgs))
out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors))
out.ImagePullPolicy = v1.PullPolicy(in.ImagePullPolicy)
out.ImagePullPolicy = corev1.PullPolicy(in.ImagePullPolicy)
out.ImagePullSerial = (*bool)(unsafe.Pointer(in.ImagePullSerial))
return nil
}
@ -872,10 +876,10 @@ func Convert_v1beta4_NodeRegistrationOptions_To_kubeadm_NodeRegistrationOptions(
func autoConvert_kubeadm_NodeRegistrationOptions_To_v1beta4_NodeRegistrationOptions(in *kubeadm.NodeRegistrationOptions, out *NodeRegistrationOptions, s conversion.Scope) error {
out.Name = in.Name
out.CRISocket = in.CRISocket
out.Taints = *(*[]v1.Taint)(unsafe.Pointer(&in.Taints))
out.Taints = *(*[]corev1.Taint)(unsafe.Pointer(&in.Taints))
out.KubeletExtraArgs = *(*[]Arg)(unsafe.Pointer(&in.KubeletExtraArgs))
out.IgnorePreflightErrors = *(*[]string)(unsafe.Pointer(&in.IgnorePreflightErrors))
out.ImagePullPolicy = v1.PullPolicy(in.ImagePullPolicy)
out.ImagePullPolicy = corev1.PullPolicy(in.ImagePullPolicy)
out.ImagePullSerial = (*bool)(unsafe.Pointer(in.ImagePullSerial))
return nil
}
@ -962,13 +966,13 @@ func Convert_kubeadm_ResetConfiguration_To_v1beta4_ResetConfiguration(in *kubead
}
func autoConvert_v1beta4_Timeouts_To_kubeadm_Timeouts(in *Timeouts, out *kubeadm.Timeouts, s conversion.Scope) error {
out.ControlPlaneComponentHealthCheck = (*metav1.Duration)(unsafe.Pointer(in.ControlPlaneComponentHealthCheck))
out.KubeletHealthCheck = (*metav1.Duration)(unsafe.Pointer(in.KubeletHealthCheck))
out.KubernetesAPICall = (*metav1.Duration)(unsafe.Pointer(in.KubernetesAPICall))
out.EtcdAPICall = (*metav1.Duration)(unsafe.Pointer(in.EtcdAPICall))
out.TLSBootstrap = (*metav1.Duration)(unsafe.Pointer(in.TLSBootstrap))
out.Discovery = (*metav1.Duration)(unsafe.Pointer(in.Discovery))
out.UpgradeManifests = (*metav1.Duration)(unsafe.Pointer(in.UpgradeManifests))
out.ControlPlaneComponentHealthCheck = (*v1.Duration)(unsafe.Pointer(in.ControlPlaneComponentHealthCheck))
out.KubeletHealthCheck = (*v1.Duration)(unsafe.Pointer(in.KubeletHealthCheck))
out.KubernetesAPICall = (*v1.Duration)(unsafe.Pointer(in.KubernetesAPICall))
out.EtcdAPICall = (*v1.Duration)(unsafe.Pointer(in.EtcdAPICall))
out.TLSBootstrap = (*v1.Duration)(unsafe.Pointer(in.TLSBootstrap))
out.Discovery = (*v1.Duration)(unsafe.Pointer(in.Discovery))
out.UpgradeManifests = (*v1.Duration)(unsafe.Pointer(in.UpgradeManifests))
return nil
}
@ -978,13 +982,13 @@ func Convert_v1beta4_Timeouts_To_kubeadm_Timeouts(in *Timeouts, out *kubeadm.Tim
}
func autoConvert_kubeadm_Timeouts_To_v1beta4_Timeouts(in *kubeadm.Timeouts, out *Timeouts, s conversion.Scope) error {
out.ControlPlaneComponentHealthCheck = (*metav1.Duration)(unsafe.Pointer(in.ControlPlaneComponentHealthCheck))
out.KubeletHealthCheck = (*metav1.Duration)(unsafe.Pointer(in.KubeletHealthCheck))
out.KubernetesAPICall = (*metav1.Duration)(unsafe.Pointer(in.KubernetesAPICall))
out.EtcdAPICall = (*metav1.Duration)(unsafe.Pointer(in.EtcdAPICall))
out.TLSBootstrap = (*metav1.Duration)(unsafe.Pointer(in.TLSBootstrap))
out.Discovery = (*metav1.Duration)(unsafe.Pointer(in.Discovery))
out.UpgradeManifests = (*metav1.Duration)(unsafe.Pointer(in.UpgradeManifests))
out.ControlPlaneComponentHealthCheck = (*v1.Duration)(unsafe.Pointer(in.ControlPlaneComponentHealthCheck))
out.KubeletHealthCheck = (*v1.Duration)(unsafe.Pointer(in.KubeletHealthCheck))
out.KubernetesAPICall = (*v1.Duration)(unsafe.Pointer(in.KubernetesAPICall))
out.EtcdAPICall = (*v1.Duration)(unsafe.Pointer(in.EtcdAPICall))
out.TLSBootstrap = (*v1.Duration)(unsafe.Pointer(in.TLSBootstrap))
out.Discovery = (*v1.Duration)(unsafe.Pointer(in.Discovery))
out.UpgradeManifests = (*v1.Duration)(unsafe.Pointer(in.UpgradeManifests))
return nil
}

View File

@ -23,9 +23,9 @@ package v1beta4
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
v1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
@ -121,6 +121,16 @@ func (in *ClusterConfiguration) DeepCopyInto(out *ClusterConfiguration) {
(*out)[key] = val
}
}
if in.CertificateValidityPeriod != nil {
in, out := &in.CertificateValidityPeriod, &out.CertificateValidityPeriod
*out = new(v1.Duration)
**out = **in
}
if in.CACertificateValidityPeriod != nil {
in, out := &in.CACertificateValidityPeriod, &out.CACertificateValidityPeriod
*out = new(v1.Duration)
**out = **in
}
return
}
@ -336,7 +346,7 @@ func (in *InitConfiguration) DeepCopyInto(out *InitConfiguration) {
out.TypeMeta = in.TypeMeta
if in.BootstrapTokens != nil {
in, out := &in.BootstrapTokens, &out.BootstrapTokens
*out = make([]v1.BootstrapToken, len(*in))
*out = make([]bootstraptokenv1.BootstrapToken, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
@ -618,37 +628,37 @@ func (in *Timeouts) DeepCopyInto(out *Timeouts) {
*out = *in
if in.ControlPlaneComponentHealthCheck != nil {
in, out := &in.ControlPlaneComponentHealthCheck, &out.ControlPlaneComponentHealthCheck
*out = new(metav1.Duration)
*out = new(v1.Duration)
**out = **in
}
if in.KubeletHealthCheck != nil {
in, out := &in.KubeletHealthCheck, &out.KubeletHealthCheck
*out = new(metav1.Duration)
*out = new(v1.Duration)
**out = **in
}
if in.KubernetesAPICall != nil {
in, out := &in.KubernetesAPICall, &out.KubernetesAPICall
*out = new(metav1.Duration)
*out = new(v1.Duration)
**out = **in
}
if in.EtcdAPICall != nil {
in, out := &in.EtcdAPICall, &out.EtcdAPICall
*out = new(metav1.Duration)
*out = new(v1.Duration)
**out = **in
}
if in.TLSBootstrap != nil {
in, out := &in.TLSBootstrap, &out.TLSBootstrap
*out = new(metav1.Duration)
*out = new(v1.Duration)
**out = **in
}
if in.Discovery != nil {
in, out := &in.Discovery, &out.Discovery
*out = new(metav1.Duration)
*out = new(v1.Duration)
**out = **in
}
if in.UpgradeManifests != nil {
in, out := &in.UpgradeManifests, &out.UpgradeManifests
*out = new(metav1.Duration)
*out = new(v1.Duration)
**out = **in
}
return

View File

@ -75,6 +75,9 @@ func ValidateClusterConfiguration(c *kubeadm.ClusterConfiguration) field.ErrorLi
allErrs = append(allErrs, ValidateEtcd(&c.Etcd, field.NewPath("etcd"))...)
allErrs = append(allErrs, ValidateEncryptionAlgorithm(c.EncryptionAlgorithm, field.NewPath("encryptionAlgorithm"))...)
allErrs = append(allErrs, componentconfigs.Validate(c)...)
for _, certError := range ValidateCertValidity(c) {
klog.Warningf("WARNING: %s", certError.Error())
}
return allErrs
}
@ -770,3 +773,19 @@ func ValidateUpgradeConfiguration(c *kubeadm.UpgradeConfiguration) field.ErrorLi
}
return allErrs
}
// ValidateCertValidity validates if the values for cert validity are too big
func ValidateCertValidity(cfg *kubeadm.ClusterConfiguration) []error {
var allErrs []error
if cfg.CertificateValidityPeriod != nil && cfg.CertificateValidityPeriod.Duration > constants.CertificateValidityPeriod {
allErrs = append(allErrs,
errors.Errorf("certificateValidityPeriod: the value %v is more than the recommended default for certificate expiration: %v",
cfg.CertificateValidityPeriod.Duration, constants.CertificateValidityPeriod))
}
if cfg.CACertificateValidityPeriod != nil && cfg.CACertificateValidityPeriod.Duration > constants.CACertificateValidityPeriod {
allErrs = append(allErrs,
errors.Errorf("caCertificateValidityPeriod: the value %v is more than the recommended default for CA certificate expiration: %v",
cfg.CACertificateValidityPeriod.Duration, constants.CACertificateValidityPeriod))
}
return allErrs
}

View File

@ -25,11 +25,13 @@ import (
"github.com/spf13/pflag"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
func TestValidateToken(t *testing.T) {
@ -1541,3 +1543,51 @@ func TestPullPolicy(t *testing.T) {
}
}
}
func TestValidateCertValidity(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.ClusterConfiguration
expectedErrors int
}{
{
name: "no errors from nil values",
cfg: &kubeadmapi.ClusterConfiguration{
CertificateValidityPeriod: nil,
CACertificateValidityPeriod: nil,
},
expectedErrors: 0,
},
{
name: "no errors from defaults",
cfg: &kubeadmapi.ClusterConfiguration{
CertificateValidityPeriod: &metav1.Duration{
Duration: constants.CertificateValidityPeriod,
},
CACertificateValidityPeriod: &metav1.Duration{
Duration: constants.CACertificateValidityPeriod,
},
},
expectedErrors: 0,
},
{
name: "two errors from long durations",
cfg: &kubeadmapi.ClusterConfiguration{
CertificateValidityPeriod: &metav1.Duration{
Duration: constants.CertificateValidityPeriod * 2,
},
CACertificateValidityPeriod: &metav1.Duration{
Duration: constants.CACertificateValidityPeriod * 2,
},
},
expectedErrors: 2,
},
}
for _, tc := range tests {
actual := ValidateCertValidity(tc.cfg)
if len(actual) != tc.expectedErrors {
t.Errorf("case %q:\n\t expected errors: %v\n\t got: %v\n\t errors: %v", tc.name, tc.expectedErrors, len(actual), actual)
}
}
}

View File

@ -133,6 +133,16 @@ func (in *ClusterConfiguration) DeepCopyInto(out *ClusterConfiguration) {
(*out)[key] = val
}
}
if in.CertificateValidityPeriod != nil {
in, out := &in.CertificateValidityPeriod, &out.CertificateValidityPeriod
*out = new(v1.Duration)
**out = **in
}
if in.CACertificateValidityPeriod != nil {
in, out := &in.CACertificateValidityPeriod, &out.CACertificateValidityPeriod
*out = new(v1.Duration)
**out = **in
}
return
}

View File

@ -516,7 +516,7 @@ func (p *certTextPrinter) PrintObj(obj runtime.Object, writer io.Writer) error {
s := fmt.Sprintf("%s\t%s\t%s\t%s\t%-8v",
cert.Name,
cert.ExpirationDate.Format("Jan 02, 2006 15:04 MST"),
duration.ShortHumanDuration(time.Duration(cert.ResidualTimeSeconds)*time.Second),
duration.HumanDuration(time.Duration(cert.ResidualTimeSeconds)*time.Second),
cert.CAName,
yesNo(cert.ExternallyManaged),
)
@ -535,7 +535,7 @@ func (p *certTextPrinter) PrintObj(obj runtime.Object, writer io.Writer) error {
s := fmt.Sprintf("%s\t%s\t%s\t%-8v",
ca.Name,
ca.ExpirationDate.Format("Jan 02, 2006 15:04 MST"),
duration.ShortHumanDuration(time.Duration(ca.ResidualTimeSeconds)*time.Second),
duration.HumanDuration(time.Duration(ca.ResidualTimeSeconds)*time.Second),
yesNo(ca.ExternallyManaged),
)
fmt.Fprintln(tabw, s)

View File

@ -22,6 +22,7 @@ import (
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
@ -29,6 +30,7 @@ import (
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
)
@ -89,20 +91,27 @@ func newCmdUserKubeConfig(out io.Writer) *cobra.Command {
return err
}
if validityPeriod > kubeadmconstants.CertificateValidity {
if validityPeriod > kubeadmconstants.CertificateValidityPeriod {
klog.Warningf("WARNING: the specified certificate validity period %v is longer than the default duration %v, this may increase security risks.",
validityPeriod, kubeadmconstants.CertificateValidity)
validityPeriod, kubeadmconstants.CertificateValidityPeriod)
}
internalCfg.ClusterConfiguration.CertificateValidityPeriod = &metav1.Duration{
Duration: validityPeriod,
}
notAfter := time.Now().Add(validityPeriod).UTC()
startTime := kubeadmutil.StartTimeUTC()
notAfter := startTime.Add(kubeadmconstants.CertificateValidityPeriod)
if internalCfg.ClusterConfiguration.CertificateValidityPeriod != nil {
notAfter = startTime.Add(validityPeriod)
}
// if the kubeconfig file for an additional user has to use a token, use it
if token != "" {
return kubeconfigphase.WriteKubeConfigWithToken(out, internalCfg, clientName, token, &notAfter)
return kubeconfigphase.WriteKubeConfigWithToken(out, internalCfg, clientName, token, notAfter)
}
// Otherwise, write a kubeconfig file with a generate client cert
return kubeconfigphase.WriteKubeConfigWithClientCert(out, internalCfg, clientName, organizations, &notAfter)
return kubeconfigphase.WriteKubeConfigWithClientCert(out, internalCfg, clientName, organizations, notAfter)
},
Args: cobra.NoArgs,
}
@ -113,7 +122,7 @@ func newCmdUserKubeConfig(out io.Writer) *cobra.Command {
cmd.Flags().StringVar(&token, options.TokenStr, token, "The token that should be used as the authentication mechanism for this kubeconfig, instead of client certificates")
cmd.Flags().StringVar(&clientName, "client-name", clientName, "The name of user. It will be used as the CN if client certificates are created")
cmd.Flags().StringSliceVar(&organizations, "org", organizations, "The organizations of the client certificate. It will be used as the O if client certificates are created")
cmd.Flags().DurationVar(&validityPeriod, "validity-period", kubeadmconstants.CertificateValidity, "The validity period of the client certificate. It is an offset from the current time.")
cmd.Flags().DurationVar(&validityPeriod, "validity-period", kubeadmconstants.CertificateValidityPeriod, "The validity period of the client certificate. It is an offset from the current time.")
cmd.MarkFlagRequired("client-name")
return cmd

View File

@ -22,6 +22,7 @@ import (
"os"
"path/filepath"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/clientcmd"
@ -29,6 +30,7 @@ import (
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
kubeconfigtestutil "k8s.io/kubernetes/cmd/kubeadm/test/kubeconfig"
@ -89,12 +91,13 @@ func TestKubeConfigSubCommandsThatWritesToOut(t *testing.T) {
}
var tests = []struct {
name string
command string
clusterName string
withClientCert bool
withToken bool
additionalFlags []string
name string
command string
clusterName string
withClientCert bool
withToken bool
additionalFlags []string
expectedValidityPeriod time.Duration
}{
{
name: "user subCommand withClientCert",
@ -120,6 +123,13 @@ func TestKubeConfigSubCommandsThatWritesToOut(t *testing.T) {
clusterName: "my-cluster-with-token",
additionalFlags: []string{"--token=123456"},
},
{
name: "user subCommand with validity period",
withClientCert: true,
command: "user",
additionalFlags: []string{"--validity-period=12h"},
expectedValidityPeriod: 12 * time.Hour,
},
}
for _, test := range tests {
@ -156,8 +166,13 @@ func TestKubeConfigSubCommandsThatWritesToOut(t *testing.T) {
kubeconfigtestutil.AssertKubeConfigCurrentCluster(t, config, "https://1.2.3.4:1234", caCert)
if test.withClientCert {
if test.expectedValidityPeriod == 0 {
test.expectedValidityPeriod = kubeadmconstants.CertificateValidityPeriod
}
// checks that kubeconfig files have expected client cert
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, "myUser")
startTime := kubeadmutil.StartTimeUTC()
notAfter := startTime.Add(test.expectedValidityPeriod)
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, notAfter, "myUser")
}
if test.withToken {

View File

@ -44,8 +44,10 @@ const (
// CertificateBackdate defines the offset applied to notBefore for CA certificates generated by kubeadm
CertificateBackdate = time.Minute * 5
// CertificateValidity defines the validity for all the signed certificates generated by kubeadm
CertificateValidity = time.Hour * 24 * 365
// CertificateValidityPeriod defines the validity period for all the signed certificates generated by kubeadm
CertificateValidityPeriod = time.Hour * 24 * 365
// CACertificateValidityPeriod defines the validity period for all the signed CA certificates generated by kubeadm
CACertificateValidityPeriod = time.Hour * 24 * 365 * 10
// DefaultCertificateDir defines default certificate directory
DefaultCertificateDir = "pki"

View File

@ -22,6 +22,7 @@ import (
"fmt"
"io"
"path/filepath"
"time"
"github.com/pkg/errors"
@ -30,6 +31,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
)
@ -50,6 +52,8 @@ type KubeadmCert struct {
// These functions will be run in series, passed both the InitConfiguration and a cert Config.
configMutators []configMutatorsFunc
config pkiutil.CertConfig
// Used for unit tests.
creationTime time.Time
}
// GetConfig returns the definition for the given cert given the provided InitConfiguration
@ -60,6 +64,30 @@ func (k *KubeadmCert) GetConfig(ic *kubeadmapi.InitConfiguration) (*pkiutil.Cert
}
}
// creationTime should be set only during unit tests, otherwise the kubeadm start time
// should be
if k.creationTime.IsZero() {
k.creationTime = kubeadmutil.StartTimeUTC()
}
// Backdate certificate to allow small time jumps.
k.config.NotBefore = k.creationTime.Add(-kubeadmconstants.CertificateBackdate)
// Use the validity periods defined in the ClusterConfiguration.
// If CAName is empty this is a CA cert.
if len(k.CAName) != 0 {
if ic.ClusterConfiguration.CertificateValidityPeriod != nil {
k.config.NotAfter = k.creationTime.
Add(ic.ClusterConfiguration.CertificateValidityPeriod.Duration)
}
} else {
if ic.ClusterConfiguration.CACertificateValidityPeriod != nil {
k.config.NotAfter = k.creationTime.
Add(ic.ClusterConfiguration.CACertificateValidityPeriod.Duration)
}
}
// Use the encryption algorithm from ClusterConfiguration.
k.config.EncryptionAlgorithm = ic.ClusterConfiguration.EncryptionAlgorithmType()
return &k.config, nil
}

View File

@ -23,7 +23,10 @@ import (
"os"
"path/filepath"
"testing"
"time"
"github.com/google/go-cmp/cmp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
certutil "k8s.io/client-go/util/cert"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
@ -301,3 +304,85 @@ func TestCreateKeyAndCSR(t *testing.T) {
})
}
}
func TestGetConfig(t *testing.T) {
var (
now = time.Now()
backdate = kubeadmconstants.CertificateBackdate
)
tests := []struct {
name string
cert *KubeadmCert
cfg *kubeadmapi.InitConfiguration
expectedConfig *pkiutil.CertConfig
}{
{
name: "encryption algorithm is set",
cert: &KubeadmCert{
creationTime: now,
},
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmECDSAP256,
},
},
expectedConfig: &pkiutil.CertConfig{
Config: certutil.Config{
NotBefore: now.Add(-backdate),
},
EncryptionAlgorithm: kubeadmapi.EncryptionAlgorithmECDSAP256,
},
},
{
name: "cert validity is set",
cert: &KubeadmCert{
CAName: "some-ca",
creationTime: now,
},
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
CertificateValidityPeriod: &metav1.Duration{
Duration: time.Hour * 1,
},
},
},
expectedConfig: &pkiutil.CertConfig{
Config: certutil.Config{
NotBefore: now.Add(-backdate),
},
NotAfter: now.Add(time.Hour * 1)},
},
{
name: "CA cert validity is set",
cert: &KubeadmCert{
CAName: "",
creationTime: now,
},
cfg: &kubeadmapi.InitConfiguration{
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
CACertificateValidityPeriod: &metav1.Duration{
Duration: time.Hour * 10,
},
},
},
expectedConfig: &pkiutil.CertConfig{
Config: certutil.Config{
NotBefore: now.Add(-backdate),
},
NotAfter: now.Add(time.Hour * 10),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual, err := tt.cert.GetConfig(tt.cfg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if diff := cmp.Diff(actual, tt.expectedConfig); diff != "" {
t.Fatalf("GetConfig() returned diff (-want,+got):\n%s", diff)
}
})
}
}

View File

@ -27,6 +27,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
)
@ -250,6 +251,17 @@ func (rm *Manager) RenewUsingLocalCA(name string) (bool, error) {
EncryptionAlgorithm: rm.cfg.EncryptionAlgorithmType(),
}
startTime := kubeadmutil.StartTimeUTC()
// Backdate certificate to allow small time jumps.
cfg.NotBefore = startTime.Add(-kubeadmconstants.CertificateBackdate)
// Use the validity periods defined in the ClusterConfiguration.
// Only use CertificateValidityPeriod as CA renewal is not supported.
if rm.cfg.CertificateValidityPeriod != nil {
cfg.NotAfter = startTime.Add(rm.cfg.CertificateValidityPeriod.Duration)
}
// reads the CA
caCert, caKey, err := certsphase.LoadCertificateAuthority(rm.cfg.CertificatesDir, handler.CABaseName)
if err != nil {

View File

@ -28,10 +28,13 @@ import (
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
certutil "k8s.io/client-go/util/cert"
netutils "k8s.io/utils/net"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
certtestutil "k8s.io/kubernetes/cmd/kubeadm/app/util/certs"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
@ -46,7 +49,7 @@ var (
testCertOrganization = []string{"sig-cluster-lifecycle"}
testCertCfg = makeTestCertConfig(testCertOrganization)
testCertCfg = makeTestCertConfig(testCertOrganization, time.Time{}, time.Time{})
)
type fakecertificateReadWriter struct {
@ -117,12 +120,23 @@ func TestRenewUsingLocalCA(t *testing.T) {
cfg := &kubeadmapi.ClusterConfiguration{
CertificatesDir: dir,
CertificateValidityPeriod: &metav1.Duration{
Duration: time.Hour * 10,
},
}
rm, err := NewManager(cfg, dir)
if err != nil {
t.Fatalf("Failed to create the certificate renewal manager: %v", err)
}
// Prepare test certs with a past validity.
startTime := kubeadmutil.StartTimeUTC()
fmt.Println("START TIME TEST", startTime)
notBefore := startTime.Add(-rm.cfg.CertificateValidityPeriod.Duration * 2)
notAfter := startTime.Add(-rm.cfg.CertificateValidityPeriod.Duration)
tests := []struct {
name string
certName string
@ -133,7 +147,7 @@ func TestRenewUsingLocalCA(t *testing.T) {
name: "Certificate renewal for a PKI certificate",
certName: "apiserver",
createCertFunc: func() *x509.Certificate {
return writeTestCertificate(t, dir, "apiserver", testCACert, testCAKey, testCertOrganization)
return writeTestCertificate(t, dir, "apiserver", testCACert, testCAKey, testCertOrganization, notBefore, notAfter)
},
expectedOrganization: testCertOrganization,
},
@ -141,7 +155,7 @@ func TestRenewUsingLocalCA(t *testing.T) {
name: "Certificate renewal for a certificate embedded in a kubeconfig file",
certName: "admin.conf",
createCertFunc: func() *x509.Certificate {
return writeTestKubeconfig(t, dir, "admin.conf", testCACert, testCAKey)
return writeTestKubeconfig(t, dir, "admin.conf", testCACert, testCAKey, notBefore, notAfter)
},
expectedOrganization: testCertOrganization,
},
@ -151,7 +165,9 @@ func TestRenewUsingLocalCA(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
cert := test.createCertFunc()
time.Sleep(1 * time.Second)
notBefore := startTime.Add(-kubeadmconstants.CertificateBackdate)
notAfter := startTime.Add(rm.cfg.CertificateValidityPeriod.Duration)
testCertCfg := makeTestCertConfig(testCertOrganization, notBefore, notAfter)
_, err := rm.RenewUsingLocalCA(test.certName)
if err != nil {
@ -177,6 +193,8 @@ func TestRenewUsingLocalCA(t *testing.T) {
certtestutil.AssertCertificateHasCommonName(t, newCert, testCertCfg.CommonName)
certtestutil.AssertCertificateHasDNSNames(t, newCert, testCertCfg.AltNames.DNSNames...)
certtestutil.AssertCertificateHasIPAddresses(t, newCert, testCertCfg.AltNames.IPs...)
certtestutil.AssertCertificateHasNotBefore(t, newCert, testCertCfg.NotBefore)
certtestutil.AssertCertificateHasNotAfter(t, newCert, testCertCfg.NotAfter)
})
}
}
@ -212,14 +230,14 @@ func TestCreateRenewCSR(t *testing.T) {
name: "Creation of a CSR request for renewal of a PKI certificate",
certName: "apiserver",
createCertFunc: func() *x509.Certificate {
return writeTestCertificate(t, dir, "apiserver", testCACert, testCAKey, testCertOrganization)
return writeTestCertificate(t, dir, "apiserver", testCACert, testCAKey, testCertOrganization, time.Time{}, time.Time{})
},
},
{
name: "Creation of a CSR request for renewal of a certificate embedded in a kubeconfig file",
certName: "admin.conf",
createCertFunc: func() *x509.Certificate {
return writeTestKubeconfig(t, dir, "admin.conf", testCACert, testCAKey)
return writeTestKubeconfig(t, dir, "admin.conf", testCACert, testCAKey, time.Time{}, time.Time{})
},
},
}
@ -294,7 +312,7 @@ func TestCertToConfig(t *testing.T) {
}
}
func makeTestCertConfig(organization []string) *pkiutil.CertConfig {
func makeTestCertConfig(organization []string, notBefore, notAfter time.Time) *pkiutil.CertConfig {
return &pkiutil.CertConfig{
Config: certutil.Config{
CommonName: "test-common-name",
@ -303,8 +321,10 @@ func makeTestCertConfig(organization []string) *pkiutil.CertConfig {
IPs: []net.IP{netutils.ParseIPSloppy("10.100.0.1")},
DNSNames: []string{"test-domain.space"},
},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
NotBefore: notBefore,
},
NotAfter: notAfter,
}
}

View File

@ -24,6 +24,7 @@ import (
"os"
"path/filepath"
"testing"
"time"
"k8s.io/client-go/tools/clientcmd"
certutil "k8s.io/client-go/util/cert"
@ -42,7 +43,7 @@ func TestPKICertificateReadWriter(t *testing.T) {
defer os.RemoveAll(dir)
// creates a certificate
cert := writeTestCertificate(t, dir, "test", testCACert, testCAKey, testCertOrganization)
cert := writeTestCertificate(t, dir, "test", testCACert, testCAKey, testCertOrganization, time.Time{}, time.Time{})
// Creates a pkiCertificateReadWriter
pkiReadWriter := newPKICertificateReadWriter(dir, "test")
@ -100,7 +101,7 @@ func TestKubeconfigReadWriter(t *testing.T) {
}
// creates a certificate and then embeds it into a kubeconfig file
cert := writeTestKubeconfig(t, dirKubernetes, "test", testCACert, testCAKey)
cert := writeTestKubeconfig(t, dirKubernetes, "test", testCACert, testCAKey, time.Time{}, time.Time{})
// Creates a KubeconfigReadWriter
kubeconfigReadWriter := newKubeconfigReadWriter(dirKubernetes, "test", dirPKI, caName)
@ -146,8 +147,8 @@ func TestKubeconfigReadWriter(t *testing.T) {
}
// writeTestCertificate is a utility for creating a test certificate
func writeTestCertificate(t *testing.T, dir, name string, caCert *x509.Certificate, caKey crypto.Signer, organization []string) *x509.Certificate {
cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, makeTestCertConfig(organization))
func writeTestCertificate(t *testing.T, dir, name string, caCert *x509.Certificate, caKey crypto.Signer, organization []string, notBefore, notAfter time.Time) *x509.Certificate {
cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, makeTestCertConfig(organization, notBefore, notAfter))
if err != nil {
t.Fatalf("couldn't generate certificate: %v", err)
}
@ -160,7 +161,7 @@ func writeTestCertificate(t *testing.T, dir, name string, caCert *x509.Certifica
}
// writeTestKubeconfig is a utility for creating a test kubeconfig with an embedded certificate
func writeTestKubeconfig(t *testing.T, dir, name string, caCert *x509.Certificate, caKey crypto.Signer) *x509.Certificate {
func writeTestKubeconfig(t *testing.T, dir, name string, caCert *x509.Certificate, caKey crypto.Signer, notBefore, notAfter time.Time) *x509.Certificate {
cfg := &pkiutil.CertConfig{
Config: certutil.Config{
@ -171,7 +172,9 @@ func writeTestKubeconfig(t *testing.T, dir, name string, caCert *x509.Certificat
IPs: []net.IP{netutils.ParseIPSloppy("10.100.0.1")},
DNSNames: []string{"test-domain.space"},
},
NotBefore: notBefore,
},
NotAfter: notAfter,
}
cert, key, err := pkiutil.NewCertAndKey(caCert, caKey, cfg)
if err != nil {

View File

@ -66,11 +66,12 @@ type tokenAuth struct {
// kubeConfigSpec struct holds info required to build a KubeConfig object
type kubeConfigSpec struct {
CACert *x509.Certificate
APIServer string
ClientName string
TokenAuth *tokenAuth `datapolicy:"token"`
ClientCertAuth *clientCertAuth `datapolicy:"security-key"`
CACert *x509.Certificate
APIServer string
ClientName string
ClientCertNotAfter time.Time
TokenAuth *tokenAuth `datapolicy:"token"`
ClientCertAuth *clientCertAuth `datapolicy:"security-key"`
}
// CreateJoinControlPlaneKubeConfigFiles will create and write to disk the kubeconfig files required by kubeadm
@ -131,7 +132,7 @@ func createKubeConfigFiles(outDir string, cfg *kubeadmapi.InitConfiguration, kub
}
// builds the KubeConfig object
config, err := buildKubeConfigFromSpec(spec, cfg.ClusterName, nil)
config, err := buildKubeConfigFromSpec(spec, cfg.ClusterName)
if err != nil {
return err
}
@ -170,7 +171,7 @@ func getKubeConfigSpecs(cfg *kubeadmapi.InitConfiguration) (map[string]*kubeConf
}
// buildKubeConfigFromSpec creates a kubeconfig object for the given kubeConfigSpec
func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string, notAfter *time.Time) (*clientcmdapi.Config, error) {
func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string) (*clientcmdapi.Config, error) {
// If this kubeconfig should use token
if spec.TokenAuth != nil {
@ -184,8 +185,8 @@ func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string, notAfter
), nil
}
// otherwise, create a client certs
clientCertConfig := newClientCertConfigFromKubeConfigSpec(spec, notAfter)
// otherwise, create a client cert
clientCertConfig := newClientCertConfigFromKubeConfigSpec(spec)
clientCert, clientKey, err := pkiutil.NewCertAndKey(spec.CACert, spec.ClientCertAuth.CAKey, &clientCertConfig)
if err != nil {
@ -207,14 +208,14 @@ func buildKubeConfigFromSpec(spec *kubeConfigSpec, clustername string, notAfter
), nil
}
func newClientCertConfigFromKubeConfigSpec(spec *kubeConfigSpec, notAfter *time.Time) pkiutil.CertConfig {
func newClientCertConfigFromKubeConfigSpec(spec *kubeConfigSpec) pkiutil.CertConfig {
return pkiutil.CertConfig{
Config: certutil.Config{
CommonName: spec.ClientName,
Organization: spec.ClientCertAuth.Organizations,
Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
},
NotAfter: notAfter,
NotAfter: spec.ClientCertNotAfter,
}
}
@ -303,7 +304,7 @@ func createKubeConfigFileIfNotExists(outDir, filename string, config *clientcmda
}
// WriteKubeConfigWithClientCert writes a kubeconfig file - with a client certificate as authentication info - to the given writer.
func WriteKubeConfigWithClientCert(out io.Writer, cfg *kubeadmapi.InitConfiguration, clientName string, organizations []string, notAfter *time.Time) error {
func WriteKubeConfigWithClientCert(out io.Writer, cfg *kubeadmapi.InitConfiguration, clientName string, organizations []string, notAfter time.Time) error {
// creates the KubeConfigSpecs, actualized for the current InitConfiguration
caCert, caKey, err := pkiutil.TryLoadCertAndKeyFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
@ -326,13 +327,14 @@ func WriteKubeConfigWithClientCert(out io.Writer, cfg *kubeadmapi.InitConfigurat
CAKey: caKey,
Organizations: organizations,
},
ClientCertNotAfter: notAfter,
}
return writeKubeConfigFromSpec(out, spec, cfg.ClusterName, notAfter)
return writeKubeConfigFromSpec(out, spec, cfg.ClusterName)
}
// WriteKubeConfigWithToken writes a kubeconfig file - with a token as client authentication info - to the given writer.
func WriteKubeConfigWithToken(out io.Writer, cfg *kubeadmapi.InitConfiguration, clientName, token string, notAfter *time.Time) error {
func WriteKubeConfigWithToken(out io.Writer, cfg *kubeadmapi.InitConfiguration, clientName, token string, notAfter time.Time) error {
// creates the KubeConfigSpecs, actualized for the current InitConfiguration
caCert, _, err := pkiutil.TryLoadCertAndKeyFromDisk(cfg.CertificatesDir, kubeadmconstants.CACertAndKeyBaseName)
@ -354,16 +356,17 @@ func WriteKubeConfigWithToken(out io.Writer, cfg *kubeadmapi.InitConfiguration,
TokenAuth: &tokenAuth{
Token: token,
},
ClientCertNotAfter: notAfter,
}
return writeKubeConfigFromSpec(out, spec, cfg.ClusterName, notAfter)
return writeKubeConfigFromSpec(out, spec, cfg.ClusterName)
}
// writeKubeConfigFromSpec creates a kubeconfig object from a kubeConfigSpec and writes it to the given writer.
func writeKubeConfigFromSpec(out io.Writer, spec *kubeConfigSpec, clustername string, notAfter *time.Time) error {
func writeKubeConfigFromSpec(out io.Writer, spec *kubeConfigSpec, clustername string) error {
// builds the KubeConfig object
config, err := buildKubeConfigFromSpec(spec, clustername, notAfter)
config, err := buildKubeConfigFromSpec(spec, clustername)
if err != nil {
return err
}
@ -439,6 +442,12 @@ func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kube
return nil, err
}
startTime := kubeadmutil.StartTimeUTC()
notAfter := startTime.Add(kubeadmconstants.CertificateValidityPeriod)
if cfg.ClusterConfiguration.CertificateValidityPeriod != nil {
notAfter = startTime.Add(cfg.ClusterConfiguration.CertificateValidityPeriod.Duration)
}
return map[string]*kubeConfigSpec{
kubeadmconstants.AdminKubeConfigFileName: {
APIServer: controlPlaneEndpoint,
@ -446,6 +455,7 @@ func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kube
ClientCertAuth: &clientCertAuth{
Organizations: []string{kubeadmconstants.ClusterAdminsGroupAndClusterRoleBinding},
},
ClientCertNotAfter: notAfter,
},
kubeadmconstants.SuperAdminKubeConfigFileName: {
APIServer: controlPlaneEndpoint,
@ -453,6 +463,7 @@ func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kube
ClientCertAuth: &clientCertAuth{
Organizations: []string{kubeadmconstants.SystemPrivilegedGroup},
},
ClientCertNotAfter: notAfter,
},
kubeadmconstants.KubeletKubeConfigFileName: {
APIServer: controlPlaneEndpoint,
@ -460,16 +471,19 @@ func getKubeConfigSpecsBase(cfg *kubeadmapi.InitConfiguration) (map[string]*kube
ClientCertAuth: &clientCertAuth{
Organizations: []string{kubeadmconstants.NodesGroup},
},
ClientCertNotAfter: notAfter,
},
kubeadmconstants.ControllerManagerKubeConfigFileName: {
APIServer: localAPIEndpoint,
ClientName: kubeadmconstants.ControllerManagerUser,
ClientCertAuth: &clientCertAuth{},
APIServer: localAPIEndpoint,
ClientName: kubeadmconstants.ControllerManagerUser,
ClientCertAuth: &clientCertAuth{},
ClientCertNotAfter: notAfter,
},
kubeadmconstants.SchedulerKubeConfigFileName: {
APIServer: localAPIEndpoint,
ClientName: kubeadmconstants.SchedulerUser,
ClientCertAuth: &clientCertAuth{},
APIServer: localAPIEndpoint,
ClientName: kubeadmconstants.SchedulerUser,
ClientCertAuth: &clientCertAuth{},
ClientCertNotAfter: notAfter,
},
}, nil
}
@ -497,7 +511,7 @@ func createKubeConfigAndCSR(kubeConfigDir string, kubeadmConfig *kubeadmapi.Init
return errors.Errorf("%s: csr: %s", errExist, kubeConfigPath)
}
clientCertConfig := newClientCertConfigFromKubeConfigSpec(spec, nil)
clientCertConfig := newClientCertConfigFromKubeConfigSpec(spec)
clientKey, err := pkiutil.NewPrivateKey(clientCertConfig.EncryptionAlgorithm)
if err != nil {

View File

@ -34,6 +34,7 @@ import (
rbac "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
clientset "k8s.io/client-go/kubernetes"
@ -218,12 +219,14 @@ func TestBuildKubeConfigFromSpecWithClientAuth(t *testing.T) {
// Creates a CA
caCert, caKey := certstestutil.SetupCertificateAuthority(t)
notAfter, _ := time.Parse(time.RFC3339, "2026-01-02T15:04:05Z")
// Executes buildKubeConfigFromSpec passing a KubeConfigSpec with a ClientAuth
config := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://1.2.3.4:1234", "myClientName", "test-cluster", "myOrg1", "myOrg2")
config := setupKubeConfigWithClientAuth(t, caCert, caKey, notAfter, "https://1.2.3.4:1234", "myClientName", "test-cluster", "myOrg1", "myOrg2")
// Asserts spec data are propagated to the kubeconfig
kubeconfigtestutil.AssertKubeConfigCurrentCluster(t, config, "https://1.2.3.4:1234", caCert)
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, "myClientName", "myOrg1", "myOrg2")
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, notAfter, "myClientName", "myOrg1", "myOrg2")
}
func TestBuildKubeConfigFromSpecWithTokenAuth(t *testing.T) {
@ -231,7 +234,7 @@ func TestBuildKubeConfigFromSpecWithTokenAuth(t *testing.T) {
caCert, _ := certstestutil.SetupCertificateAuthority(t)
// Executes buildKubeConfigFromSpec passing a KubeConfigSpec with a Token
config := setupdKubeConfigWithTokenAuth(t, caCert, "https://1.2.3.4:1234", "myClientName", "123456", "test-cluster")
config := setupKubeConfigWithTokenAuth(t, caCert, "https://1.2.3.4:1234", "myClientName", "123456", "test-cluster")
// Asserts spec data are propagated to the kubeconfig
kubeconfigtestutil.AssertKubeConfigCurrentCluster(t, config, "https://1.2.3.4:1234", caCert)
@ -244,11 +247,13 @@ func TestCreateKubeConfigFileIfNotExists(t *testing.T) {
caCert, caKey := certstestutil.SetupCertificateAuthority(t)
anotherCaCert, anotherCaKey := certstestutil.SetupCertificateAuthority(t)
notAfter, _ := time.Parse(time.RFC3339, "2026-01-02T15:04:05Z")
// build kubeconfigs (to be used to test kubeconfigs equality/not equality)
config := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://1.2.3.4:1234", "test-cluster", "myOrg1", "myOrg2")
configWithAnotherClusterCa := setupdKubeConfigWithClientAuth(t, anotherCaCert, anotherCaKey, "https://1.2.3.4:1234", "test-cluster", "myOrg1", "myOrg2")
configWithAnotherClusterAddress := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://3.4.5.6:3456", "myOrg1", "test-cluster", "myOrg2")
invalidConfig := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://1.2.3.4:1234", "test-cluster", "myOrg1", "myOrg2")
config := setupKubeConfigWithClientAuth(t, caCert, caKey, notAfter, "https://1.2.3.4:1234", "test-cluster", "myOrg1", "myOrg2")
configWithAnotherClusterCa := setupKubeConfigWithClientAuth(t, anotherCaCert, anotherCaKey, notAfter, "https://1.2.3.4:1234", "test-cluster", "myOrg1", "myOrg2")
configWithAnotherClusterAddress := setupKubeConfigWithClientAuth(t, caCert, caKey, notAfter, "https://3.4.5.6:3456", "myOrg1", "test-cluster", "myOrg2")
invalidConfig := setupKubeConfigWithClientAuth(t, caCert, caKey, notAfter, "https://1.2.3.4:1234", "test-cluster", "myOrg1", "myOrg2")
invalidConfig.CurrentContext = "invalid context"
var tests = []struct {
@ -384,6 +389,8 @@ func TestWriteKubeConfigFailsIfCADoesntExists(t *testing.T) {
},
}
notAfter, _ := time.Parse(time.RFC3339, "2026-01-02T15:04:05Z")
var tests = []struct {
name string
writeKubeConfigFunction func(out io.Writer) error
@ -391,13 +398,13 @@ func TestWriteKubeConfigFailsIfCADoesntExists(t *testing.T) {
{
name: "WriteKubeConfigWithClientCert",
writeKubeConfigFunction: func(out io.Writer) error {
return WriteKubeConfigWithClientCert(out, cfg, "myUser", []string{"myOrg"}, nil)
return WriteKubeConfigWithClientCert(out, cfg, "myUser", []string{"myOrg"}, notAfter)
},
},
{
name: "WriteKubeConfigWithToken",
writeKubeConfigFunction: func(out io.Writer) error {
return WriteKubeConfigWithToken(out, cfg, "myUser", "12345", nil)
return WriteKubeConfigWithToken(out, cfg, "myUser", "12345", notAfter)
},
},
}
@ -433,9 +440,14 @@ func TestWriteKubeConfig(t *testing.T) {
LocalAPIEndpoint: kubeadmapi.APIEndpoint{AdvertiseAddress: "1.2.3.4", BindPort: 1234},
ClusterConfiguration: kubeadmapi.ClusterConfiguration{
CertificatesDir: pkidir,
CertificateValidityPeriod: &metav1.Duration{
Duration: time.Hour * 10,
},
},
}
notAfter, _ := time.Parse(time.RFC3339, "2026-01-02T15:04:05Z")
var tests = []struct {
name string
writeKubeConfigFunction func(out io.Writer) error
@ -445,14 +457,14 @@ func TestWriteKubeConfig(t *testing.T) {
{
name: "WriteKubeConfigWithClientCert",
writeKubeConfigFunction: func(out io.Writer) error {
return WriteKubeConfigWithClientCert(out, cfg, "myUser", []string{"myOrg"}, nil)
return WriteKubeConfigWithClientCert(out, cfg, "myUser", []string{"myOrg"}, notAfter)
},
withClientCert: true,
},
{
name: "WriteKubeConfigWithToken",
writeKubeConfigFunction: func(out io.Writer) error {
return WriteKubeConfigWithToken(out, cfg, "myUser", "12345", nil)
return WriteKubeConfigWithToken(out, cfg, "myUser", "12345", notAfter)
},
withToken: true,
},
@ -480,7 +492,7 @@ func TestWriteKubeConfig(t *testing.T) {
if test.withClientCert {
// checks that kubeconfig files have expected client cert
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, "myUser", "myOrg")
kubeconfigtestutil.AssertKubeConfigCurrentAuthInfoWithClientCert(t, config, caCert, notAfter, "myUser", "myOrg")
}
if test.withToken {
@ -495,9 +507,11 @@ func TestValidateKubeConfig(t *testing.T) {
caCert, caKey := certstestutil.SetupCertificateAuthority(t)
anotherCaCert, anotherCaKey := certstestutil.SetupCertificateAuthority(t)
config := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://1.2.3.4:1234", "test-cluster", "myOrg1")
configWithAnotherClusterCa := setupdKubeConfigWithClientAuth(t, anotherCaCert, anotherCaKey, "https://1.2.3.4:1234", "test-cluster", "myOrg1")
configWithAnotherServerURL := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://4.3.2.1:4321", "test-cluster", "myOrg1")
notAfter, _ := time.Parse(time.RFC3339, "2026-01-02T15:04:05Z")
config := setupKubeConfigWithClientAuth(t, caCert, caKey, notAfter, "https://1.2.3.4:1234", "test-cluster", "myOrg1")
configWithAnotherClusterCa := setupKubeConfigWithClientAuth(t, anotherCaCert, anotherCaKey, notAfter, "https://1.2.3.4:1234", "test-cluster", "myOrg1")
configWithAnotherServerURL := setupKubeConfigWithClientAuth(t, caCert, caKey, notAfter, "https://4.3.2.1:4321", "test-cluster", "myOrg1")
configWithSameClusterCaByExternalFile := config.DeepCopy()
currentCtx, exists := configWithSameClusterCaByExternalFile.Contexts[configWithSameClusterCaByExternalFile.CurrentContext]
@ -610,15 +624,17 @@ func TestValidateKubeconfigsForExternalCA(t *testing.T) {
t.Fatalf("failure while deleting ca.key: %v", err)
}
notAfter, _ := time.Parse(time.RFC3339, "2026-01-02T15:04:05Z")
// create a valid config
config := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://1.2.3.4:1234", "test-cluster", "myOrg1")
config := setupKubeConfigWithClientAuth(t, caCert, caKey, notAfter, "https://1.2.3.4:1234", "test-cluster", "myOrg1")
// create a config with another CA
anotherCaCert, anotherCaKey := certstestutil.SetupCertificateAuthority(t)
configWithAnotherClusterCa := setupdKubeConfigWithClientAuth(t, anotherCaCert, anotherCaKey, "https://1.2.3.4:1234", "test-cluster", "myOrg1")
configWithAnotherClusterCa := setupKubeConfigWithClientAuth(t, anotherCaCert, anotherCaKey, notAfter, "https://1.2.3.4:1234", "test-cluster", "myOrg1")
// create a config with another server URL
configWithAnotherServerURL := setupdKubeConfigWithClientAuth(t, caCert, caKey, "https://4.3.2.1:4321", "test-cluster", "myOrg1")
configWithAnotherServerURL := setupKubeConfigWithClientAuth(t, caCert, caKey, notAfter, "https://4.3.2.1:4321", "test-cluster", "myOrg1")
tests := map[string]struct {
filesToWrite map[string]*clientcmdapi.Config
@ -697,19 +713,20 @@ func TestValidateKubeconfigsForExternalCA(t *testing.T) {
}
}
// setupdKubeConfigWithClientAuth is a test utility function that wraps buildKubeConfigFromSpec for building a KubeConfig object With ClientAuth
func setupdKubeConfigWithClientAuth(t *testing.T, caCert *x509.Certificate, caKey crypto.Signer, APIServer, clientName, clustername string, organizations ...string) *clientcmdapi.Config {
// setupKubeConfigWithClientAuth is a test utility function that wraps buildKubeConfigFromSpec for building a KubeConfig object With ClientAuth
func setupKubeConfigWithClientAuth(t *testing.T, caCert *x509.Certificate, caKey crypto.Signer, notAfter time.Time, apiServer, clientName, clustername string, organizations ...string) *clientcmdapi.Config {
spec := &kubeConfigSpec{
CACert: caCert,
APIServer: APIServer,
APIServer: apiServer,
ClientName: clientName,
ClientCertAuth: &clientCertAuth{
CAKey: caKey,
Organizations: organizations,
},
ClientCertNotAfter: notAfter,
}
config, err := buildKubeConfigFromSpec(spec, clustername, nil)
config, err := buildKubeConfigFromSpec(spec, clustername)
if err != nil {
t.Fatal("buildKubeConfigFromSpec failed!")
}
@ -717,18 +734,18 @@ func setupdKubeConfigWithClientAuth(t *testing.T, caCert *x509.Certificate, caKe
return config
}
// setupdKubeConfigWithClientAuth is a test utility function that wraps buildKubeConfigFromSpec for building a KubeConfig object With Token
func setupdKubeConfigWithTokenAuth(t *testing.T, caCert *x509.Certificate, APIServer, clientName, token, clustername string) *clientcmdapi.Config {
// setupKubeConfigWithClientAuth is a test utility function that wraps buildKubeConfigFromSpec for building a KubeConfig object With Token
func setupKubeConfigWithTokenAuth(t *testing.T, caCert *x509.Certificate, apiServer, clientName, token, clustername string) *clientcmdapi.Config {
spec := &kubeConfigSpec{
CACert: caCert,
APIServer: APIServer,
APIServer: apiServer,
ClientName: clientName,
TokenAuth: &tokenAuth{
Token: token,
},
}
config, err := buildKubeConfigFromSpec(spec, clustername, nil)
config, err := buildKubeConfigFromSpec(spec, clustername)
if err != nil {
t.Fatal("buildKubeConfigFromSpec failed!")
}

View File

@ -23,6 +23,7 @@ import (
"net"
"path/filepath"
"testing"
"time"
certutil "k8s.io/client-go/util/cert"
"k8s.io/client-go/util/keyutil"
@ -51,6 +52,26 @@ func AssertCertificateIsSignedByCa(t *testing.T, cert *x509.Certificate, signing
}
}
// AssertCertificateHasNotBefore is a utility function for kubeadm testing that asserts if a given certificate has
// the expected NotBefore. Truncate (round) expectedNotBefore to 1 second, since the certificate stores
// with seconds as the maximum precision.
func AssertCertificateHasNotBefore(t *testing.T, cert *x509.Certificate, expectedNotBefore time.Time) {
truncated := expectedNotBefore.Truncate(time.Second)
if !cert.NotBefore.Equal(truncated) {
t.Errorf("cert has NotBefore %v, expected %v", cert.NotBefore, truncated)
}
}
// AssertCertificateHasNotAfter is a utility function for kubeadm testing that asserts if a given certificate has
// the expected NotAfter. Truncate (round) expectedNotAfter to 1 second, since the certificate stores
// with seconds as the maximum precision.
func AssertCertificateHasNotAfter(t *testing.T, cert *x509.Certificate, expectedNotAfter time.Time) {
truncated := expectedNotAfter.Truncate(time.Second)
if !cert.NotAfter.Equal(truncated) {
t.Errorf("cert has NotAfter %v, expected %v", cert.NotAfter, truncated)
}
}
// AssertCertificateHasCommonName is a utility function for kubeadm testing that asserts if a given certificate has
// the expected SubjectCommonName
func AssertCertificateHasCommonName(t *testing.T, cert *x509.Certificate, commonName string) {

View File

@ -62,7 +62,7 @@ const (
// CertConfig is a wrapper around certutil.Config extending it with EncryptionAlgorithm.
type CertConfig struct {
certutil.Config
NotAfter *time.Time
NotAfter time.Time
EncryptionAlgorithm kubeadmapi.EncryptionAlgorithmType
}
@ -72,10 +72,7 @@ func NewCertificateAuthority(config *CertConfig) (*x509.Certificate, crypto.Sign
if err != nil {
return nil, nil, errors.Wrap(err, "unable to create private key while generating CA certificate")
}
// backdate CA certificate to allow small time jumps
config.Config.NotBefore = time.Now().Add(-kubeadmconstants.CertificateBackdate)
cert, err := certutil.NewSelfSignedCACert(config.Config, key)
cert, err := NewSelfSignedCACert(config, key)
if err != nil {
return nil, nil, errors.Wrap(err, "unable to create self-signed CA certificate")
}
@ -655,9 +652,14 @@ func NewSignedCert(cfg *CertConfig, key crypto.Signer, caCert *x509.Certificate,
RemoveDuplicateAltNames(&cfg.AltNames)
notAfter := time.Now().Add(kubeadmconstants.CertificateValidity).UTC()
if cfg.NotAfter != nil {
notAfter = *cfg.NotAfter
notBefore := caCert.NotBefore
if !cfg.NotBefore.IsZero() {
notBefore = cfg.NotBefore
}
notAfter := notBefore.Add(kubeadmconstants.CertificateValidityPeriod)
if !cfg.NotAfter.IsZero() {
notAfter = cfg.NotAfter
}
certTmpl := x509.Certificate{
@ -668,7 +670,7 @@ func NewSignedCert(cfg *CertConfig, key crypto.Signer, caCert *x509.Certificate,
DNSNames: cfg.AltNames.DNSNames,
IPAddresses: cfg.AltNames.IPs,
SerialNumber: serial,
NotBefore: caCert.NotBefore,
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: keyUsage,
ExtKeyUsage: cfg.Usages,
@ -682,6 +684,48 @@ func NewSignedCert(cfg *CertConfig, key crypto.Signer, caCert *x509.Certificate,
return x509.ParseCertificate(certDERBytes)
}
// NewSelfSignedCACert creates a new self-signed CA certificate
func NewSelfSignedCACert(cfg *CertConfig, key crypto.Signer) (*x509.Certificate, error) {
// returns a uniform random value in [0, max-1), then add 1 to serial to make it a uniform random value in [1, max).
serial, err := cryptorand.Int(cryptorand.Reader, new(big.Int).SetInt64(math.MaxInt64-1))
if err != nil {
return nil, err
}
serial = new(big.Int).Add(serial, big.NewInt(1))
keyUsage := x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign
notBefore := time.Now().UTC()
if !cfg.NotBefore.IsZero() {
notBefore = cfg.NotBefore
}
notAfter := notBefore.Add(kubeadmconstants.CACertificateValidityPeriod)
if !cfg.NotAfter.IsZero() {
notAfter = cfg.NotAfter
}
tmpl := x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
CommonName: cfg.CommonName,
Organization: cfg.Organization,
},
DNSNames: []string{cfg.CommonName},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: keyUsage,
BasicConstraintsValid: true,
IsCA: true,
}
certDERBytes, err := x509.CreateCertificate(cryptorand.Reader, &tmpl, &tmpl, key.Public(), key)
if err != nil {
return nil, err
}
return x509.ParseCertificate(certDERBytes)
}
// RemoveDuplicateAltNames removes duplicate items in altNames.
func RemoveDuplicateAltNames(altNames *certutil.AltNames) {
if altNames == nil {
@ -707,7 +751,7 @@ func RemoveDuplicateAltNames(altNames *certutil.AltNames) {
// (+/- offset)
func ValidateCertPeriod(cert *x509.Certificate, offset time.Duration) error {
period := fmt.Sprintf("NotBefore: %v, NotAfter: %v", cert.NotBefore, cert.NotAfter)
now := time.Now().Add(offset)
now := time.Now().Add(offset).UTC()
if now.Before(cert.NotBefore) {
return errors.Errorf("the certificate is not valid yet: %s", period)
}

View File

@ -0,0 +1,36 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package util contains kubeadm utilities.
package util
import (
"time"
)
// startTime is a variable that represents the start time of the kubeadm process.
// It can be used to consistently use the same start time instead of calling time.Now()
// in multiple locations and ending up with minor time deviations.
var startTime time.Time
func init() {
startTime = time.Now()
}
// StartTimeUTC returns startTime with its location set to UTC.
func StartTimeUTC() time.Time {
return startTime.UTC()
}

View File

@ -20,6 +20,7 @@ import (
"crypto/x509"
"encoding/pem"
"testing"
"time"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@ -57,7 +58,7 @@ func AssertKubeConfigCurrentCluster(t *testing.T, config *clientcmdapi.Config, e
// AssertKubeConfigCurrentAuthInfoWithClientCert is a utility function for kubeadm testing that asserts if the CurrentAuthInfo in
// the given KubeConfig object contains a clientCert that refers to a specific client name, is signed by the expected CA, includes the expected organizations
func AssertKubeConfigCurrentAuthInfoWithClientCert(t *testing.T, config *clientcmdapi.Config, signinCa *x509.Certificate, expectedClientName string, expectedOrganizations ...string) {
func AssertKubeConfigCurrentAuthInfoWithClientCert(t *testing.T, config *clientcmdapi.Config, signinCa *x509.Certificate, expectedNotAfter time.Time, expectedClientName string, expectedOrganizations ...string) {
currentContext := config.Contexts[config.CurrentContext]
currentAuthInfo := config.AuthInfos[currentContext.AuthInfo]
@ -77,6 +78,9 @@ func AssertKubeConfigCurrentAuthInfoWithClientCert(t *testing.T, config *clientc
// Asserts the clientCert is signed by the signinCa
certstestutil.AssertCertificateIsSignedByCa(t, currentClientCert, signinCa)
// Assert the clientCert has expected NotAfter
certstestutil.AssertCertificateHasNotAfter(t, currentClientCert, expectedNotAfter)
// Asserts the clientCert has ClientAuth ExtKeyUsage
certstestutil.AssertCertificateHasClientAuthUsage(t, currentClientCert)