Skip to content
Merged
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
12 changes: 8 additions & 4 deletions provisioning/baremetal_crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ type TlsCertificate struct {
}

const (
tlsExpiration = 365 * 2 * 24 * time.Hour // 2 years
tlsRefresh = 180 * 24 * time.Hour // 180 days
tlsExpiration = 365 * 24 * time.Hour // 1 year
tlsRefresh = 30 * 24 * time.Hour // 30 days before expiration
)

func generateRandomPassword() (string, error) {
Expand Down Expand Up @@ -86,14 +86,18 @@ func generateTlsCertificate(hosts sets.Set[string]) (TlsCertificate, error) {
}

func isTlsCertificateExpired(certificate []byte) (bool, error) {
return isTlsCertificateExpiredAt(certificate, time.Now())
}

func isTlsCertificateExpiredAt(certificate []byte, now time.Time) (bool, error) {
certs, err := cert.ParseCertsPEM(certificate)
if err != nil {
return false, err
}

refreshAfter := time.Now().Add(tlsRefresh)
refreshAfter := now.Add(tlsRefresh)
for _, cert := range certs {
if cert.NotAfter.Before(refreshAfter) {
if !cert.NotAfter.After(refreshAfter) {
return true, nil
}
}
Expand Down
89 changes: 89 additions & 0 deletions provisioning/baremetal_crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,35 @@ package provisioning
import (
"net"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/util/cert"

"github.com/openshift/library-go/pkg/crypto"
)

// generateTestCertWithLifetime creates a test certificate with a specific lifetime
// for testing expiration and rotation boundary conditions.
func generateTestCertWithLifetime(lifetime time.Duration) ([]byte, error) {
caConfig, err := crypto.MakeSelfSignedCAConfig("test-ca", lifetime)
if err != nil {
return nil, err
}
ca := crypto.CA{
Config: caConfig,
SerialGenerator: &crypto.RandomSerialGenerator{},
}
config, err := ca.MakeServerCert(sets.New("localhost"), lifetime)
if err != nil {
return nil, err
}
certBytes, _, err := config.GetPEMBytes()
return certBytes, err
}

// containsIP checks if the given IP list contains an IP that matches the target,
// handling the difference between 4-byte and 16-byte IPv4 representations.
func containsIP(ips []net.IP, target string) bool {
Expand Down Expand Up @@ -340,3 +362,70 @@ func TestTlsCertificateSANsMatchIPv6Canonical(t *testing.T) {
require.NoError(t, err)
assert.True(t, match, "SANs should match with IPv6 addresses in canonical form")
}

func TestCertificateValidityPeriod(t *testing.T) {
tlsCert, err := generateTlsCertificate(sets.New("localhost"))
require.NoError(t, err)

certs, err := cert.ParseCertsPEM(tlsCert.certificate)
require.NoError(t, err)
require.NotEmpty(t, certs)

serverCert := certs[0]
validity := serverCert.NotAfter.Sub(serverCert.NotBefore)

assert.InDelta(t, tlsExpiration.Seconds(), validity.Seconds(), 60,
"certificate validity should be approximately 1 year (365 days)")
}

func TestCertificateRotationBoundary(t *testing.T) {
certPEM, err := generateTestCertWithLifetime(tlsExpiration)
require.NoError(t, err)

certs, err := cert.ParseCertsPEM(certPEM)
require.NoError(t, err)
require.NotEmpty(t, certs)

notAfter := certs[0].NotAfter

cases := []struct {
name string
now time.Time
expectRotation bool
}{
{
name: "rotation-triggered-at-29-days-remaining",
now: notAfter.Add(-(tlsRefresh - 24*time.Hour)),
expectRotation: true,
},
{
name: "rotation-triggered-at-exactly-30-days-remaining",
now: notAfter.Add(-tlsRefresh),
expectRotation: true,
},
{
name: "no-rotation-at-31-days-remaining",
now: notAfter.Add(-(tlsRefresh + 24*time.Hour)),
expectRotation: false,
},
{
name: "no-rotation-at-180-days-remaining",
now: notAfter.Add(-180 * 24 * time.Hour),
expectRotation: false,
},
{
name: "no-rotation-freshly-generated",
now: notAfter.Add(-tlsExpiration),
expectRotation: false,
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
needsRotation, err := isTlsCertificateExpiredAt(certPEM, tc.now)
require.NoError(t, err)
assert.Equal(t, tc.expectRotation, needsRotation,
"at %v before expiry: expected rotation=%v", notAfter.Sub(tc.now), tc.expectRotation)
})
}
}