mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-19 20:46:42 +00:00
* 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>
296 lines
7.7 KiB
Go
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)
|
|
}
|
|
}
|