Files
capsule/pkg/runtime/cert/ca_test.go
T
3d1158ed23 fix: correct tls reconciler and add tenantowners (#1946)
* fix(controller): decode old object for delete requests

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: modernize golang

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* fix: tls controller

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* feat: add tenantowner tenant status reference

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: tlsreconciler only patches cabundles

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* chore: refactor logger usage

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* fix: tlsreconciler only patches cabundles

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: tlsreconciler only patches cabundles

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

---------

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-03 16:02:17 +02:00

296 lines
7.7 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package cert_test
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"net"
"reflect"
"testing"
"time"
"github.com/projectcapsule/capsule/pkg/runtime/cert"
)
func TestNewCertificateAuthorityFromBytes(t *testing.T) {
t.Parallel()
ca, err := cert.GenerateCertificateAuthority()
if err != nil {
t.Fatalf("expected CA generation to succeed, got %v", err)
}
crt, err := ca.CACertificatePem()
if err != nil {
t.Fatalf("expected CA certificate PEM encoding to succeed, got %v", err)
}
key, err := ca.CAPrivateKeyPem()
if err != nil {
t.Fatalf("expected CA private key PEM encoding to succeed, got %v", err)
}
loadedCA, err := cert.NewCertificateAuthorityFromBytes(crt.Bytes(), key.Bytes())
if err != nil {
t.Fatalf("expected loading CA from PEM bytes to succeed, got %v", err)
}
loadedCrt, err := loadedCA.CACertificatePem()
if err != nil {
t.Fatalf("expected loaded CA certificate PEM encoding to succeed, got %v", err)
}
if !bytes.Equal(crt.Bytes(), loadedCrt.Bytes()) {
t.Fatal("expected loaded CA certificate PEM to match original CA certificate PEM")
}
}
func TestNewCertificateAuthorityFromBytesRejectsInvalidCertificatePEM(t *testing.T) {
t.Parallel()
ca, err := cert.GenerateCertificateAuthority()
if err != nil {
t.Fatalf("expected CA generation to succeed, got %v", err)
}
key, err := ca.CAPrivateKeyPem()
if err != nil {
t.Fatalf("expected CA private key PEM encoding to succeed, got %v", err)
}
if _, err := cert.NewCertificateAuthorityFromBytes([]byte("invalid certificate"), key.Bytes()); err == nil {
t.Fatal("expected invalid certificate PEM to be rejected")
}
}
func TestNewCertificateAuthorityFromBytesRejectsInvalidPrivateKeyPEM(t *testing.T) {
t.Parallel()
ca, err := cert.GenerateCertificateAuthority()
if err != nil {
t.Fatalf("expected CA generation to succeed, got %v", err)
}
crt, err := ca.CACertificatePem()
if err != nil {
t.Fatalf("expected CA certificate PEM encoding to succeed, got %v", err)
}
if _, err := cert.NewCertificateAuthorityFromBytes(crt.Bytes(), []byte("invalid private key")); err == nil {
t.Fatal("expected invalid private key PEM to be rejected")
}
}
func TestCapsuleCAGenerateCertificate(t *testing.T) {
t.Parallel()
tests := map[string]struct {
sans cert.CertificateSANs
}{
"dns name": {
sans: cert.CertificateSANs{
DNSNames: []string{"foo.tld"},
},
},
"multiple dns names": {
sans: cert.CertificateSANs{
DNSNames: []string{
"capsule-webhook-service.capsule-system.svc",
"capsule-webhook-service.capsule-system.svc.cluster.local",
},
},
},
"dns names are normalized": {
sans: cert.CertificateSANs{
DNSNames: []string{
" Capsule-Webhook-Service.Capsule-System.Svc ",
"capsule-webhook-service.capsule-system.svc",
},
},
},
"ip addresses": {
sans: cert.CertificateSANs{
IPAddrs: []net.IP{
net.ParseIP("10.96.0.10"),
net.ParseIP("fd00::1"),
},
},
},
"dns names and ip addresses": {
sans: cert.CertificateSANs{
DNSNames: []string{
"capsule-webhook-service.capsule-system.svc",
},
IPAddrs: []net.IP{
net.ParseIP("10.96.0.10"),
},
},
},
}
for name, tt := range tests {
tt := tt
t.Run(name, func(t *testing.T) {
t.Parallel()
expirationDate := time.Now().AddDate(1, 0, 0).UTC()
ca, err := cert.GenerateCertificateAuthority()
if err != nil {
t.Fatalf("expected CA generation to succeed, got %v", err)
}
crt, key, err := ca.GenerateCertificate(cert.NewCertOpts(expirationDate, tt.sans))
if err != nil {
t.Fatalf("expected serving certificate generation to succeed, got %v", err)
}
servingCert := parseCertificatePEM(t, crt.Bytes())
if servingCert.NotAfter.Unix() != expirationDate.Unix() {
t.Fatalf("expected certificate NotAfter %d, got %d", expirationDate.Unix(), servingCert.NotAfter.Unix())
}
expectedSANs := tt.sans.Normalize()
actualSANs := cert.CertificateSANs{
DNSNames: servingCert.DNSNames,
IPAddrs: servingCert.IPAddresses,
}.Normalize()
assertCertificateSANsEqual(t, expectedSANs, actualSANs)
if !tt.sans.MatchesCertificate(servingCert) {
t.Fatalf(
"expected generated certificate to match requested SANs, desiredDNSNames=%v desiredIPAddresses=%v actualDNSNames=%v actualIPAddresses=%v",
expectedSANs.DNSNames,
cert.IPsToStrings(expectedSANs.IPAddrs),
servingCert.DNSNames,
cert.IPsToStrings(servingCert.IPAddresses),
)
}
if _, err := tls.X509KeyPair(crt.Bytes(), key.Bytes()); err != nil {
t.Fatalf("expected generated certificate/key pair to be valid, got %v", err)
}
})
}
}
func TestCapsuleCAGenerateCertificateIsSignedByCA(t *testing.T) {
t.Parallel()
ca, err := cert.GenerateCertificateAuthority()
if err != nil {
t.Fatalf("expected CA generation to succeed, got %v", err)
}
expirationDate := time.Now().AddDate(1, 0, 0).UTC()
crt, _, err := ca.GenerateCertificate(cert.NewCertOpts(expirationDate, cert.CertificateSANs{
DNSNames: []string{"capsule-webhook-service.capsule-system.svc"},
}))
if err != nil {
t.Fatalf("expected serving certificate generation to succeed, got %v", err)
}
servingCert := parseCertificatePEM(t, crt.Bytes())
caCrt, err := ca.CACertificatePem()
if err != nil {
t.Fatalf("expected CA certificate PEM encoding to succeed, got %v", err)
}
caCert := parseCertificatePEM(t, caCrt.Bytes())
roots := x509.NewCertPool()
roots.AddCert(caCert)
verifyOptions := x509.VerifyOptions{
DNSName: "capsule-webhook-service.capsule-system.svc",
Roots: roots,
}
if _, err := servingCert.Verify(verifyOptions); err != nil {
t.Fatalf("expected serving certificate to verify against generated CA, got %v", err)
}
}
func TestCapsuleCAGenerateCertificateWithIPAddressSANVerifiesForIP(t *testing.T) {
t.Parallel()
ca, err := cert.GenerateCertificateAuthority()
if err != nil {
t.Fatalf("expected CA generation to succeed, got %v", err)
}
ip := net.ParseIP("10.96.0.10")
expirationDate := time.Now().AddDate(1, 0, 0).UTC()
crt, _, err := ca.GenerateCertificate(cert.NewCertOpts(expirationDate, cert.CertificateSANs{
IPAddrs: []net.IP{ip},
}))
if err != nil {
t.Fatalf("expected serving certificate generation to succeed, got %v", err)
}
servingCert := parseCertificatePEM(t, crt.Bytes())
caCrt, err := ca.CACertificatePem()
if err != nil {
t.Fatalf("expected CA certificate PEM encoding to succeed, got %v", err)
}
caCert := parseCertificatePEM(t, caCrt.Bytes())
roots := x509.NewCertPool()
roots.AddCert(caCert)
verifyOptions := x509.VerifyOptions{
DNSName: ip.String(),
Roots: roots,
}
if _, err := servingCert.Verify(verifyOptions); err != nil {
t.Fatalf("expected serving certificate to verify against generated CA for IP SAN, got %v", err)
}
}
func parseCertificatePEM(t *testing.T, certificatePEM []byte) *x509.Certificate {
t.Helper()
block, _ := pem.Decode(certificatePEM)
if block == nil {
t.Fatal("expected certificate PEM block, got nil")
}
certificate, err := x509.ParseCertificate(block.Bytes)
if err != nil {
t.Fatalf("expected certificate parsing to succeed, got %v", err)
}
return certificate
}
func assertCertificateSANsEqual(t *testing.T, expected, actual cert.CertificateSANs) {
t.Helper()
expected = expected.Normalize()
actual = actual.Normalize()
if !reflect.DeepEqual(expected.DNSNames, actual.DNSNames) {
t.Fatalf("expected DNS names %v, got %v", expected.DNSNames, actual.DNSNames)
}
expectedIPs := cert.IPsToStrings(expected.IPAddrs)
actualIPs := cert.IPsToStrings(actual.IPAddrs)
if !reflect.DeepEqual(expectedIPs, actualIPs) {
t.Fatalf("expected IP addresses %v, got %v", expectedIPs, actualIPs)
}
}