diff --git a/common-hooks/tls-certificate/internal_tls.go b/common-hooks/tls-certificate/internal_tls.go index 2b82c203..15c35b0a 100644 --- a/common-hooks/tls-certificate/internal_tls.go +++ b/common-hooks/tls-certificate/internal_tls.go @@ -39,7 +39,8 @@ import ( ) const ( - caExpiryDurationStr = "87600h" // 10 years + caExpiryDuration = (24 * time.Hour) * 365 * 10 // 10 years + caOutdatedDuration = (24 * time.Hour) * 365 / 2 // 6 month, just enough to renew CA certificate certExpiryDuration = (24 * time.Hour) * 365 * 10 // 10 years certOutdatedDuration = (24 * time.Hour) * 365 / 2 // 6 month, just enough to renew certificate @@ -108,6 +109,21 @@ type GenSelfSignedTLSHookConf struct { // Canonical name (CN) of common CA certificate. // If not specified (empty), then (if no CA cert already generated) using CN property of this struct CommonCACanonicalName string + + // CAExpiryDuration is duration of CA certificate validity + // if not specified (zero value), then using default value + CAExpiryDuration time.Duration + // CAOutdatedDuration defines how long before expiration + // a CA certificate is considered outdated and should be renewed. + // If not specified (zero value), then the default value is used. + CAOutdatedDuration time.Duration + // CertExpiryDuration is duration of certificate validity + // if not specified (zero value), then using default value + CertExpiryDuration time.Duration + // CertOutdatedDuration defines how long before expiration + // a certificate is considered outdated and should be renewed. + // If not specified (zero value), then the default value is used. + CertOutdatedDuration time.Duration } func (gss GenSelfSignedTLSHookConf) Path() string { @@ -171,12 +187,14 @@ func GenSelfSignedTLSConfig(conf GenSelfSignedTLSHookConf) *pkg.HookConfig { } type SelfSignedCertValues struct { - CA *certificate.Authority - CN string - KeyAlgorithm string - KeySize int - SANs []string - Usages []string + CA *certificate.Authority + CN string + KeyAlgorithm string + KeySize int + SANs []string + Usages []string + CAExpireDuration time.Duration + CertExpireDuration time.Duration } func GenSelfSignedTLS(conf GenSelfSignedTLSHookConf) func(ctx context.Context, input *pkg.HookInput) error { @@ -217,6 +235,23 @@ func GenSelfSignedTLS(conf GenSelfSignedTLSHookConf) func(ctx context.Context, i panic(fmt.Errorf("bad KeySize/KeyAlgorithm combination: %w", err)) } + caExpiryDuration := caExpiryDuration + if conf.CAExpiryDuration != 0 { + caExpiryDuration = conf.CAExpiryDuration + } + caOutdatedDuration := caOutdatedDuration + if conf.CAOutdatedDuration != 0 { + caOutdatedDuration = conf.CAOutdatedDuration + } + certExpiryDuration := certExpiryDuration + if conf.CertExpiryDuration != 0 { + certExpiryDuration = conf.CertExpiryDuration + } + certOutdatedDuration := certOutdatedDuration + if conf.CertOutdatedDuration != 0 { + certOutdatedDuration = conf.CertOutdatedDuration + } + return func(_ context.Context, input *pkg.HookInput) error { if conf.BeforeHookCheck != nil { passed := conf.BeforeHookCheck(input) @@ -246,7 +281,7 @@ func GenSelfSignedTLS(conf GenSelfSignedTLSHookConf) func(ctx context.Context, i // 2.2) save new common ca in values // 2.3) mark certificates to regenerate if useCommonCA { - auth, err = getCommonCA(input, conf.CommonCAPath()) + auth, err = getCommonCA(input, conf.CommonCAPath(), caOutdatedDuration) if err != nil { commonCACanonicalName := conf.CommonCACanonicalName @@ -258,7 +293,7 @@ func GenSelfSignedTLS(conf GenSelfSignedTLSHookConf) func(ctx context.Context, i commonCACanonicalName, certificate.WithKeyAlgo(keyAlgorithm), certificate.WithKeySize(keySize), - certificate.WithCAExpiry(caExpiryDurationStr)) + certificate.WithCAExpiry(caExpiryDuration)) if err != nil { return fmt.Errorf("generate ca: %w", err) } @@ -285,7 +320,7 @@ func GenSelfSignedTLS(conf GenSelfSignedTLSHookConf) func(ctx context.Context, i // update certificate if less than 6 month left. We create certificate for 10 years, so it looks acceptable // and we don't need to create Crontab schedule - caOutdated, err := isOutdatedCA(cert.CA) + caOutdated, err := isOutdatedCA(cert.CA, caOutdatedDuration) if err != nil { input.Logger.Warn("is outdated ca", log.Err(err)) } @@ -297,7 +332,7 @@ func GenSelfSignedTLS(conf GenSelfSignedTLSHookConf) func(ctx context.Context, i caOutdated = true } - certOutdated, err := isIrrelevantCert(cert.Cert, sans) + certOutdated, err := isIrrelevantCert(cert.Cert, sans, certOutdatedDuration) if err != nil { input.Logger.Warn("is irrelevant cert", log.Err(err)) } @@ -309,12 +344,14 @@ func GenSelfSignedTLS(conf GenSelfSignedTLSHookConf) func(ctx context.Context, i if mustGenerate { cert, err = generateNewSelfSignedTLS(SelfSignedCertValues{ - CA: auth, - CN: cn, - KeyAlgorithm: keyAlgorithm, - KeySize: keySize, - SANs: sans, - Usages: usages, + CA: auth, + CN: cn, + KeyAlgorithm: keyAlgorithm, + KeySize: keySize, + SANs: sans, + Usages: usages, + CAExpireDuration: caExpiryDuration, + CertExpireDuration: certExpiryDuration, }) if err != nil { @@ -347,7 +384,7 @@ func convCertToValues(cert *certificate.Certificate) CertValues { var ErrCertificateIsNotFound = errors.New("certificate is not found") var ErrCAIsInvalidOrOutdated = errors.New("ca is invalid or outdated") -func getCommonCA(input *pkg.HookInput, valKey string) (*certificate.Authority, error) { +func getCommonCA(input *pkg.HookInput, valKey string, caOutdatedDuration time.Duration) (*certificate.Authority, error) { auth := new(certificate.Authority) ca, ok := input.Values.GetOk(valKey) @@ -360,7 +397,7 @@ func getCommonCA(input *pkg.HookInput, valKey string) (*certificate.Authority, e return nil, err } - outdated, err := isOutdatedCA(auth.Cert) + outdated, err := isOutdatedCA(auth.Cert, caOutdatedDuration) if err != nil { input.Logger.Error("is outdated ca", log.Err(err)) @@ -386,7 +423,7 @@ func generateNewSelfSignedTLS(input SelfSignedCertValues) (*certificate.Certific input.CN, certificate.WithKeyAlgo(input.KeyAlgorithm), certificate.WithKeySize(input.KeySize), - certificate.WithCAExpiry(caExpiryDurationStr)) + certificate.WithCAExpiry(input.CAExpireDuration)) if err != nil { return nil, fmt.Errorf("generate ca: %w", err) } @@ -398,7 +435,7 @@ func generateNewSelfSignedTLS(input SelfSignedCertValues) (*certificate.Certific certificate.WithSANs(input.SANs...), certificate.WithKeyAlgo(input.KeyAlgorithm), certificate.WithKeySize(input.KeySize), - certificate.WithSigningDefaultExpiry(certExpiryDuration), + certificate.WithSigningDefaultExpiry(input.CertExpireDuration), certificate.WithSigningDefaultUsage(input.Usages), ) if err != nil { @@ -409,7 +446,7 @@ func generateNewSelfSignedTLS(input SelfSignedCertValues) (*certificate.Certific } // check certificate duration and SANs list -func isIrrelevantCert(certData []byte, desiredSANSs []string) (bool, error) { +func isIrrelevantCert(certData []byte, desiredSANSs []string, certOutdatedDuration time.Duration) (bool, error) { cert, err := certificate.ParseCertificate(certData) if err != nil { return false, fmt.Errorf("parse certificate: %w", err) @@ -446,7 +483,7 @@ func isIrrelevantCert(certData []byte, desiredSANSs []string) (bool, error) { return false, nil } -func isOutdatedCA(ca []byte) (bool, error) { +func isOutdatedCA(ca []byte, caOutdatedDuration time.Duration) (bool, error) { // Issue a new certificate if there is no CA in the secret. // Without CA it is not possible to validate the certificate. if len(ca) == 0 { @@ -458,7 +495,7 @@ func isOutdatedCA(ca []byte) (bool, error) { return false, fmt.Errorf("parse certificate: %w", err) } - if time.Until(cert.NotAfter) < certOutdatedDuration { + if time.Until(cert.NotAfter) < caOutdatedDuration { return true, nil } diff --git a/common-hooks/tls-certificate/internal_tls_test.go b/common-hooks/tls-certificate/internal_tls_test.go index 9c1d18f3..a396f71e 100644 --- a/common-hooks/tls-certificate/internal_tls_test.go +++ b/common-hooks/tls-certificate/internal_tls_test.go @@ -35,6 +35,8 @@ import ( mock "github.com/deckhouse/module-sdk/testing/mock" ) +const tenYears = (24 * time.Hour) * 365 * 10 + func Test_JQFilterTLS(t *testing.T) { t.Run("apply tls", func(t *testing.T) { const rawSecret = ` @@ -99,9 +101,17 @@ func Test_GenSelfSignedTLS(t *testing.T) { assert.NotEmpty(t, values.Crt) assert.NotEmpty(t, values.Key) + ca, err := certificate.ParseCertificate([]byte(values.CA)) + assert.NoError(t, err) + + assert.Equal(t, ca.IsCA, true) + assert.Equal(t, ca.NotAfter.Sub(ca.NotBefore), time.Hour*24*365*5) + cert, err := certificate.ParseCertificate([]byte(values.Crt)) assert.NoError(t, err) + assert.Equal(t, ca.Subject, cert.Issuer) + assert.Equal(t, []string{ "example-webhook", "example-webhook.d8-example-module", @@ -109,6 +119,8 @@ func Test_GenSelfSignedTLS(t *testing.T) { "example-webhook.d8-example-module.svc.cluster.local", "example-webhook.d8-example-module.svc.127.0.0.1.sslip.io", }, cert.DNSNames) + + assert.Equal(t, cert.NotAfter.Sub(cert.NotBefore), time.Hour*24*365) }) var input = &pkg.HookInput{ @@ -133,6 +145,8 @@ func Test_GenSelfSignedTLS(t *testing.T) { "%PUBLIC_DOMAIN%://example-webhook.d8-example-module.svc", }), FullValuesPathPrefix: "d8-example-module.internal.webhookCert", + CAExpiryDuration: time.Hour * 24 * 365 * 5, + CertExpiryDuration: time.Hour * 24 * 365, } err := tlscertificate.GenSelfSignedTLS(config)(context.Background(), input) @@ -150,7 +164,7 @@ func Test_GenSelfSignedTLS(t *testing.T) { "cert-name", certificate.WithKeyAlgo("ecdsa"), certificate.WithKeySize(256), - certificate.WithCAExpiry("87600h")) + certificate.WithCAExpiry(tenYears)) assert.NoError(t, err) @@ -247,7 +261,7 @@ func Test_GenSelfSignedTLS(t *testing.T) { "cert-name", certificate.WithKeyAlgo("ecdsa"), certificate.WithKeySize(256), - certificate.WithCAExpiry("87600h")) + certificate.WithCAExpiry(tenYears)) assert.NoError(t, err) @@ -327,6 +341,7 @@ func Test_GenSelfSignedTLS(t *testing.T) { "%PUBLIC_DOMAIN%://example-webhook.d8-example-module.svc", }), FullValuesPathPrefix: "d8-example-module.internal.webhookCert", + CertOutdatedDuration: 2 * time.Hour, } err := tlscertificate.GenSelfSignedTLS(config)(context.Background(), input) @@ -344,7 +359,7 @@ func Test_GenSelfSignedTLS(t *testing.T) { "cert-name", certificate.WithKeyAlgo("ecdsa"), certificate.WithKeySize(256), - certificate.WithCAExpiry("1h")) + certificate.WithCAExpiry(time.Hour)) assert.NoError(t, err) @@ -424,6 +439,7 @@ func Test_GenSelfSignedTLS(t *testing.T) { "%PUBLIC_DOMAIN%://example-webhook.d8-example-module.svc", }), FullValuesPathPrefix: "d8-example-module.internal.webhookCert", + CAOutdatedDuration: 2 * time.Hour, } err := tlscertificate.GenSelfSignedTLS(config)(context.Background(), input) diff --git a/pkg/certificate/options.go b/pkg/certificate/options.go index 0abed748..c06d6f62 100644 --- a/pkg/certificate/options.go +++ b/pkg/certificate/options.go @@ -16,7 +16,11 @@ limitations under the License. package certificate -import "github.com/cloudflare/cfssl/csr" +import ( + "time" + + "github.com/cloudflare/cfssl/csr" +) type Option func(request *csr.CertificateRequest) @@ -32,9 +36,9 @@ func WithKeySize(size int) Option { } } -func WithCAExpiry(expiry string) Option { +func WithCAExpiry(expiry time.Duration) Option { return func(request *csr.CertificateRequest) { - request.CA.Expiry = expiry + request.CA.Expiry = expiry.String() } }