diff --git a/README.md b/README.md index 4abdc6b..2e0749a 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,7 @@ kubectl coco initdata dump | kubectl coco initdata validate Validation checks: - `version` is `0.1.0` and `algorithm` is one of `sha256`, `sha384`, `sha512` - Required keys `aa.toml` and `cdh.toml` are present (`policy.rego` is optional) -- Embedded certificates pass rustls rules: CA certs must have `keyCertSign`; leaf certs must have a SubjectAltName and `extendedKeyUsage=serverAuth` and must not be self-signed +- Embedded certificates must be CA certificates (`CA:TRUE`, `keyCertSign`); rejected: leaf/non-CA certs, expired or not-yet-valid certs, SHA-1 or MD5 signatures, unknown critical extensions, RSA keys shorter than 1024 bits - All `aa.toml` token config URLs are consistent with `cdh.toml` kbc URL (a warning is printed if any differ) ### Transform and Apply Manifests diff --git a/cmd/initdata/common.go b/cmd/initdata/common.go index 8f8383c..390adef 100644 --- a/cmd/initdata/common.go +++ b/cmd/initdata/common.go @@ -4,8 +4,10 @@ package initdata import ( "bytes" "compress/gzip" + "crypto/rsa" "crypto/x509" "encoding/base64" + "strconv" "encoding/pem" "fmt" "io" @@ -101,13 +103,21 @@ func checkExpiry(cert *x509.Certificate) error { return nil } -// validateCACert checks that cert is a valid CA certificate: IsCA must be true -// and KeyUsageCertSign must be set. The IsCA guard is intentional — this -// function may be called directly (e.g. from validateCACerts) without the -// IsCA pre-filter that validateCerts applies. +// isWeakSignatureAlg reports whether alg is SHA-1, MD5, or MD2 — all rejected +// by rustls as insufficiently secure. +func isWeakSignatureAlg(alg x509.SignatureAlgorithm) bool { + switch alg { + case x509.MD2WithRSA, x509.MD5WithRSA, x509.SHA1WithRSA, x509.DSAWithSHA1, x509.ECDSAWithSHA1: + return true + } + return false +} + +// validateCACert checks that cert is a valid CA certificate: IsCA must be true, +// KeyUsageCertSign must be set, and the cert must not use weak crypto. func validateCACert(cert *x509.Certificate) error { if !cert.IsCA { - return fmt.Errorf("certificate %q: IsCA is false", cert.Subject.CommonName) + return fmt.Errorf("certificate %q: not a CA certificate (CA:TRUE required for trust anchors)", cert.Subject.CommonName) } if err := checkExpiry(cert); err != nil { return err @@ -115,6 +125,24 @@ func validateCACert(cert *x509.Certificate) error { if cert.KeyUsage&x509.KeyUsageCertSign == 0 { return fmt.Errorf("certificate %q: missing KeyUsageCertSign", cert.Subject.CommonName) } + if isWeakSignatureAlg(cert.SignatureAlgorithm) { + return fmt.Errorf("certificate %q: weak signature algorithm %s", cert.Subject.CommonName, cert.SignatureAlgorithm) + } + if rsaKey, ok := cert.PublicKey.(*rsa.PublicKey); ok && rsaKey.N.BitLen() < 1024 { + return fmt.Errorf("certificate %q: RSA key is %d bits, minimum is 1024", cert.Subject.CommonName, rsaKey.N.BitLen()) + } + if len(cert.UnhandledCriticalExtensions) > 0 { + oidStrs := make([]string, len(cert.UnhandledCriticalExtensions)) + for i, oid := range cert.UnhandledCriticalExtensions { + parts := make([]string, len(oid)) + for j, n := range oid { + parts[j] = strconv.Itoa(n) + } + oidStrs[i] = strings.Join(parts, ".") + } + return fmt.Errorf("certificate %q: has unknown critical extensions: %s", + cert.Subject.CommonName, strings.Join(oidStrs, ", ")) + } return nil } @@ -134,64 +162,14 @@ func validateCACerts(certs []*x509.Certificate) error { return nil } -// validateLeafCert checks rustls rules for end-entity (non-CA) certificates: -// must not be self-signed, must carry a SubjectAltName, and must have -// extendedKeyUsage serverAuth. -func validateLeafCert(cert *x509.Certificate) error { - if err := checkExpiry(cert); err != nil { - return err - } - // rustls rejects self-signed end-entity certificates - if isSelfSigned(cert) { - return fmt.Errorf("certificate %q: self-signed certificate cannot be used as a leaf cert", cert.Subject.CommonName) - } - hasSAN := len(cert.DNSNames) > 0 || len(cert.IPAddresses) > 0 || - len(cert.URIs) > 0 || len(cert.EmailAddresses) > 0 - if !hasSAN { - return fmt.Errorf("certificate %q: missing SubjectAltName extension", cert.Subject.CommonName) - } - for _, eku := range cert.ExtKeyUsage { - if eku == x509.ExtKeyUsageServerAuth { - return nil - } - } - return fmt.Errorf("certificate %q: missing extendedKeyUsage serverAuth", cert.Subject.CommonName) -} - -func validateCerts(certs []*x509.Certificate) error { - var errs []string - for _, cert := range certs { - var err error - if cert.IsCA { - err = validateCACert(cert) - } else { - err = validateLeafCert(cert) - } - if err != nil { - errs = append(errs, err.Error()) - } - } - if len(errs) > 0 { - return fmt.Errorf("cert validation failed:\n %s", strings.Join(errs, "\n ")) - } - return nil -} - -// validateCertsBySource applies rustls rules to each certificate and includes -// the source field in error messages so the user can locate the problematic cert. -// All initdata cert fields accept any cert valid under rustls rules: -// CA certs require keyCertSign; leaf certs require SAN + serverAuth EKU and -// must not be self-signed. +// validateCertsBySource applies CA certificate rules to each embedded cert and +// includes the source field in error messages so the user can locate the +// problematic cert. All initdata cert fields are trust anchor positions — +// only CA certificates (CA:TRUE, keyCertSign) are accepted. func validateCertsBySource(entries []certEntry) error { var errs []string for _, e := range entries { - var err error - if e.cert.IsCA { - err = validateCACert(e.cert) - } else { - err = validateLeafCert(e.cert) - } - if err != nil { + if err := validateCACert(e.cert); err != nil { errs = append(errs, fmt.Sprintf("%s: %s", e.source, err.Error())) } } diff --git a/cmd/initdata/common_test.go b/cmd/initdata/common_test.go index 0921c7a..9295a63 100644 --- a/cmd/initdata/common_test.go +++ b/cmd/initdata/common_test.go @@ -7,6 +7,7 @@ import ( "crypto/rsa" "crypto/x509" "crypto/x509/pkix" + "encoding/asn1" "encoding/base64" "encoding/pem" "math/big" @@ -67,31 +68,6 @@ func makeTestLeafCert(t *testing.T, caCert *x509.Certificate, caKey *rsa.Private return cert, pemBytes } -// makeValidLeafCert creates a leaf cert that passes rustls rules (SAN + serverAuth EKU). -func makeValidLeafCert(t *testing.T, caCert *x509.Certificate, caKey *rsa.PrivateKey) *x509.Certificate { - t.Helper() - key, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatalf("generate key: %v", err) - } - tmpl := &x509.Certificate{ - SerialNumber: big.NewInt(3), - Subject: pkix.Name{CommonName: "Test Valid Leaf"}, - NotBefore: time.Now().Add(-time.Hour), - NotAfter: time.Now().Add(time.Hour), - IsCA: false, - BasicConstraintsValid: true, - DNSNames: []string{"test.example.com"}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - } - der, err := x509.CreateCertificate(rand.Reader, tmpl, caCert, &key.PublicKey, caKey) - if err != nil { - t.Fatalf("create cert: %v", err) - } - cert, _ := x509.ParseCertificate(der) - return cert -} - func writeTempPEM(t *testing.T, dir, name string, pemData []byte) string { t.Helper() path := filepath.Join(dir, name) @@ -188,8 +164,9 @@ func TestValidateCACert_ValidCA(t *testing.T) { func TestValidateCACert_LeafCert(t *testing.T) { caCert, caKey, _ := makeTestCACert(t) leaf, _ := makeTestLeafCert(t, caCert, caKey) - if err := validateCACert(leaf); err == nil { - t.Error("validateCACert() should reject leaf cert") + err := validateCACert(leaf) + if err == nil || !strings.Contains(err.Error(), "not a CA certificate") { + t.Errorf("validateCACert() should reject leaf cert with 'not a CA certificate', got: %v", err) } } @@ -265,108 +242,74 @@ func TestIsSelfSigned_SelfIssuedNotSelfSigned(t *testing.T) { } } -func TestValidateLeafCert_Expired(t *testing.T) { - caCert, caKey, _ := makeTestCACert(t) - key, _ := rsa.GenerateKey(rand.Reader, 2048) - tmpl := &x509.Certificate{ - SerialNumber: big.NewInt(10), Subject: pkix.Name{CommonName: "Expired Leaf"}, - NotBefore: time.Now().Add(-48 * time.Hour), NotAfter: time.Now().Add(-time.Hour), - IsCA: false, BasicConstraintsValid: true, - DNSNames: []string{"kbs.example.com"}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - } - der, _ := x509.CreateCertificate(rand.Reader, tmpl, caCert, &key.PublicKey, caKey) - leaf, _ := x509.ParseCertificate(der) - err := validateLeafCert(leaf) - if err == nil || !strings.Contains(err.Error(), "expired") { - t.Errorf("expected expired error, got: %v", err) - } -} - -func TestValidateLeafCert_Valid(t *testing.T) { - caCert, caKey, _ := makeTestCACert(t) - leaf := makeValidLeafCert(t, caCert, caKey) - if err := validateLeafCert(leaf); err != nil { - t.Errorf("validateLeafCert() unexpected error for valid leaf: %v", err) +func TestValidateCACert_SHA1Rejected(t *testing.T) { + cert, _, _ := makeTestCACert(t) + cert.SignatureAlgorithm = x509.SHA1WithRSA + err := validateCACert(cert) + if err == nil || !strings.Contains(err.Error(), "weak signature") { + t.Errorf("expected weak signature error for SHA-1, got: %v", err) } } -func TestValidateLeafCert_NoSAN(t *testing.T) { - caCert, caKey, _ := makeTestCACert(t) - leaf, _ := makeTestLeafCert(t, caCert, caKey) // no SAN, no EKU - err := validateLeafCert(leaf) - if err == nil || !strings.Contains(err.Error(), "SubjectAltName") { - t.Errorf("expected SubjectAltName error, got: %v", err) +func TestValidateCACert_MD5Rejected(t *testing.T) { + cert, _, _ := makeTestCACert(t) + cert.SignatureAlgorithm = x509.MD5WithRSA + err := validateCACert(cert) + if err == nil || !strings.Contains(err.Error(), "weak signature") { + t.Errorf("expected weak signature error for MD5, got: %v", err) } } -func TestValidateLeafCert_NoServerAuth(t *testing.T) { - caCert, caKey, _ := makeTestCACert(t) - key, _ := rsa.GenerateKey(rand.Reader, 2048) - tmpl := &x509.Certificate{ - SerialNumber: big.NewInt(4), - Subject: pkix.Name{CommonName: "No EKU Leaf"}, - NotBefore: time.Now().Add(-time.Hour), - NotAfter: time.Now().Add(time.Hour), - IsCA: false, - BasicConstraintsValid: true, - DNSNames: []string{"test.example.com"}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - } - der, _ := x509.CreateCertificate(rand.Reader, tmpl, caCert, &key.PublicKey, caKey) - leaf, _ := x509.ParseCertificate(der) - err := validateLeafCert(leaf) - if err == nil || !strings.Contains(err.Error(), "serverAuth") { - t.Errorf("expected serverAuth error, got: %v", err) +func TestValidateCACert_WeakRSAKeyRejected(t *testing.T) { + // Construct a cert struct directly with a 512-bit modulus so we don't need + // to generate a real small key (which newer Go versions may reject). + n := new(big.Int).SetBit(new(big.Int), 511, 1) // 512-bit number + cert, _, _ := makeTestCACert(t) + cert.PublicKey = &rsa.PublicKey{N: n, E: 65537} + err := validateCACert(cert) + if err == nil || !strings.Contains(err.Error(), "1024") { + t.Errorf("expected RSA key size error, got: %v", err) } } -func TestValidateLeafCert_SelfSigned(t *testing.T) { - key, _ := rsa.GenerateKey(rand.Reader, 2048) - tmpl := &x509.Certificate{ - SerialNumber: big.NewInt(5), - Subject: pkix.Name{CommonName: "Self-Signed Leaf"}, - NotBefore: time.Now().Add(-time.Hour), - NotAfter: time.Now().Add(time.Hour), - IsCA: false, - BasicConstraintsValid: true, - DNSNames: []string{"test.example.com"}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, +func TestValidateCACert_UnknownCriticalExtensionsRejected(t *testing.T) { + cert, _, _ := makeTestCACert(t) + cert.UnhandledCriticalExtensions = []asn1.ObjectIdentifier{{1, 2, 3, 4}} + err := validateCACert(cert) + if err == nil || !strings.Contains(err.Error(), "unknown critical extensions") { + t.Errorf("expected unknown critical extensions error, got: %v", err) } - // self-signed: parent == self - der, _ := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) - leaf, _ := x509.ParseCertificate(der) - err := validateLeafCert(leaf) - if err == nil || !strings.Contains(err.Error(), "self-signed") { - t.Errorf("expected self-signed error, got: %v", err) + if err != nil && !strings.Contains(err.Error(), "1.2.3.4") { + t.Errorf("error should include OID in dot notation, got: %v", err) } } -func TestValidateCerts_ValidLeafAccepted(t *testing.T) { +func TestValidateCACerts_LeafRejected(t *testing.T) { caCert, caKey, _ := makeTestCACert(t) - leaf := makeValidLeafCert(t, caCert, caKey) - if err := validateCerts([]*x509.Certificate{caCert, leaf}); err != nil { - t.Errorf("validateCerts() should accept CA + valid leaf: %v", err) + leaf, _ := makeTestLeafCert(t, caCert, caKey) + err := validateCACerts([]*x509.Certificate{caCert, leaf}) + if err == nil || !strings.Contains(err.Error(), "not a CA certificate") { + t.Errorf("validateCACerts() should reject non-CA cert, got: %v", err) } } -func TestValidateCerts_AllValid(t *testing.T) { +func TestValidateCACerts_AllValid(t *testing.T) { cert1, _, _ := makeTestCACert(t) cert2, _, _ := makeTestCACert(t) - if err := validateCerts([]*x509.Certificate{cert1, cert2}); err != nil { - t.Errorf("validateCerts() unexpected error: %v", err) + if err := validateCACerts([]*x509.Certificate{cert1, cert2}); err != nil { + t.Errorf("validateCACerts() unexpected error: %v", err) } } -func TestValidateCerts_OneInvalid(t *testing.T) { +func TestValidateCACerts_OneInvalid(t *testing.T) { caCert, caKey, _ := makeTestCACert(t) leaf, _ := makeTestLeafCert(t, caCert, caKey) - err := validateCerts([]*x509.Certificate{caCert, leaf}) + err := validateCACerts([]*x509.Certificate{caCert, leaf}) if err == nil { - t.Fatal("validateCerts() should fail with one leaf cert") + t.Fatal("validateCACerts() should fail with one non-CA cert") } - if !strings.Contains(err.Error(), "Test Leaf") { - t.Errorf("error should name the failing cert, got: %v", err) + if !strings.Contains(err.Error(), "not a CA certificate") { + t.Errorf("error should identify non-CA cert, got: %v", err) } } diff --git a/cmd/initdata/testdata/gen/main.go b/cmd/initdata/testdata/gen/main.go index ab6931e..8c58331 100644 --- a/cmd/initdata/testdata/gen/main.go +++ b/cmd/initdata/testdata/gen/main.go @@ -124,7 +124,7 @@ func cdhToml(certPEM string) map[string]interface{} { } func main() { - // CA cert — 100-year validity so the fixture never expires in practice. + // Primary CA cert — 100-year validity so the fixture never expires in practice. caKey := mustGenKey() caTmpl := &x509.Certificate{ SerialNumber: big.NewInt(1), @@ -137,7 +137,20 @@ func main() { } caPEM, caCert := mustCreateCert(caTmpl, caTmpl, &caKey.PublicKey, caKey) - // Leaf cert — 100-year validity, signed by the test CA. + // Second CA cert — used in valid-with-both.toml so both cert positions carry a CA. + ca2Key := mustGenKey() + ca2Tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(5), + Subject: pkix.Name{CommonName: "Test CA 2"}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().AddDate(100, 0, 0), + IsCA: true, + BasicConstraintsValid: true, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + } + ca2PEM, _ := mustCreateCert(ca2Tmpl, ca2Tmpl, &ca2Key.PublicKey, ca2Key) + + // Leaf cert — signed by the test CA. Used in invalid-leaf-cert.toml. leafKey := mustGenKey() leafTmpl := &x509.Certificate{ SerialNumber: big.NewInt(2), @@ -166,7 +179,7 @@ func main() { } expPEM, _ := mustCreateCert(expTmpl, expTmpl, &expKey.PublicKey, expKey) - // Leaf cert with no SAN — valid structure but fails rustls leaf rules. + // Non-CA leaf cert — rejected because IsCA is false. badLeafKey := mustGenKey() badLeafTmpl := &x509.Certificate{ SerialNumber: big.NewInt(4), @@ -175,7 +188,7 @@ func main() { NotAfter: time.Now().AddDate(100, 0, 0), IsCA: false, BasicConstraintsValid: true, - // deliberately no SAN, no ExtKeyUsage + // IsCA:false — rejected because it is not a CA cert } badLeafPEM, _ := mustCreateCert(badLeafTmpl, caCert, &badLeafKey.PublicKey, caKey) @@ -187,15 +200,17 @@ func main() { "policy.rego": defaultPolicy, })) - writeFixture("valid-with-leaf-cert.toml", buildFixture(map[string]string{ + // invalid-leaf-cert.toml: a leaf (non-CA) cert in cdh.toml — must be rejected. + writeFixture("invalid-leaf-cert.toml", buildFixture(map[string]string{ "aa.toml": innerTOML(aaToml("")), "cdh.toml": innerTOML(cdhToml(string(leafPEM))), "policy.rego": defaultPolicy, })) + // valid-with-both.toml: CA certs in both aa.toml and cdh.toml — must pass. writeFixture("valid-with-both.toml", buildFixture(map[string]string{ "aa.toml": innerTOML(aaToml(string(caPEM))), - "cdh.toml": innerTOML(cdhToml(string(leafPEM))), + "cdh.toml": innerTOML(cdhToml(string(ca2PEM))), "policy.rego": defaultPolicy, })) diff --git a/cmd/initdata/testdata/invalid-expired-cert.toml b/cmd/initdata/testdata/invalid-expired-cert.toml index 3a5724f..004a1fa 100644 --- a/cmd/initdata/testdata/invalid-expired-cert.toml +++ b/cmd/initdata/testdata/invalid-expired-cert.toml @@ -6,7 +6,7 @@ algorithm = "sha256" "aa.toml" = ''' [token_configs] [token_configs.kbs] -cert = "-----BEGIN CERTIFICATE-----\nMIIC5zCCAc+gAwIBAgIBAzANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwpFeHBp\ncmVkIENBMB4XDTIwMDEwMTAwMDAwMFoXDTIxMDEwMTAwMDAwMFowFTETMBEGA1UE\nAxMKRXhwaXJlZCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALx9\nBhsmf31zQy6mqKL6u+hkQW2cE9Xf7FrtKKBMDqrwGqqkaushslk3+cxxNtgt0PCv\n7/nopEAqg3l+WCNsMlQZol5dkz6j6SXNZeZx9hdENhE0Fse0gToEQF+DvxaoHc0j\n54eDurrbaS8fDRmXhmosKWQOXZFmCWw8QQTWDBkGiFwQ8R7k17w47uZW2cv7M7Hh\n80Fetc5lMdrewlXyVigtWzsGS05iRqYlIX+gv4qL5+BR9T5pKnlJc1gOddCLwy3t\nJn7rYNgF62547CPhCQAKFEcP5aRXDrRPEWw99+JXT4PiSwBO6zo7Kx5y79TuEHy4\nXqKNz5XCKOiO1zNF5c0CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n/wQFMAMBAf8wHQYDVR0OBBYEFFbARZHvhm8ka4ttjTbqd8eRNXmiMA0GCSqGSIb3\nDQEBCwUAA4IBAQAZ0olB6wZBRC3+5M5IYwsjUSDlcU2+jXM3Y8fTynC/sR0I6PcJ\nzc9xTBD0XpMn3Q1yxTCOoCpdXUQ1l6pm0SYtSYixCPoN5rE2LV6XALaVITMkfSfw\nTD4Mq41Jk3JibsX1HwMSPybc4sQ8SmzUnH6JerWIL5a/MoGxkJPMfoDB0KJT5zZh\nxoadSAMR5wsW1cY9fb9BlDj5cMyjwv1Ng+/3LXtvoJ5Shp9ywCLdrjon0cSlCadF\nn7Tbb4VIrSKoo4tNi+szxsu25ejsCyVsFUWIwy9gO/ERZNZ+oiXglSz5x9/+QpMN\nFp3XvmWGIDbySJOi2ojIrstrAdtKSwNkasEB\n-----END CERTIFICATE-----\n" +cert = "-----BEGIN CERTIFICATE-----\nMIIC5zCCAc+gAwIBAgIBAzANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwpFeHBp\ncmVkIENBMB4XDTIwMDEwMTAwMDAwMFoXDTIxMDEwMTAwMDAwMFowFTETMBEGA1UE\nAxMKRXhwaXJlZCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALiP\nSPZwNSNtWgMlqUn9RaK+enTkuKhE3pJaRjvCvNsCY10KXui/iknoT9cQSLYfIJoq\n0UmOX5WeiAwZwMzBliKGzpT5e7ObA6bnaaME56+I8NlLEThpHllSduvRNdHQHMsl\nszSsIrlbn86S7M2xstscqnvGP7xUjwlvDHMpS+YVt3Fnzlp5mfYtQvLngvuGETO7\njijvpY7o5eDP88/GXFJckNewzOyPr0FgBjiVpqbL1HEP1Q2EXN8VHEalJ+McQz4E\nLN0tTu0IAdAlsViMcw8/tRYGowh4PQbpx4O5oAR0wXP3iwE6rH02VFHn2xxnovVu\n+OyVWIl+OgJAtPmbGdsCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n/wQFMAMBAf8wHQYDVR0OBBYEFGTErqFrrB/6YBzg2PU4AcYRcN1xMA0GCSqGSIb3\nDQEBCwUAA4IBAQBI9dnCBgzj+WC1Am8xJLSyJbwcA0Y9No2gntugidxfTdWB4evD\n5GnCD0YVbsfGGSq1G7GHd/qI0v0DBajNNZbcWkxi4FRnQwp/r19A1NO94hf/oKbv\n/Od/kV0OxPcEryqji2u2sqbjZRv6xZ33Osp19SkQIKMnWvuwXAGh9zeyTujjJ5VO\nciB4m+eyPO7L0ipJ0UCuDAQk5BP9QSbO5SRJdlvx/01N1HxuDO8KuJL3piCxuxYN\nGXC7xKwZCEkMunzezn7ev+sVqXi2c7NZn37tlHpNa5Lv9weoCyu5m9bCk1F1Wms6\nTSu3JhPxZAGbS3GFNkjW0KBIREfoHI4e3rjg\n-----END CERTIFICATE-----\n" url = 'http://kbs.example.svc:8080' ''' diff --git a/cmd/initdata/testdata/invalid-leaf-cert.toml b/cmd/initdata/testdata/invalid-leaf-cert.toml new file mode 100644 index 0000000..88ea21d --- /dev/null +++ b/cmd/initdata/testdata/invalid-leaf-cert.toml @@ -0,0 +1,24 @@ +version = "0.1.0" +algorithm = "sha256" + +[data] + +"aa.toml" = ''' +[token_configs] +[token_configs.kbs] +url = 'http://kbs.example.svc:8080' +''' + +"cdh.toml" = ''' +[kbc] +kbs_cert = "-----BEGIN CERTIFICATE-----\nMIIDITCCAgmgAwIBAgIBAjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdUZXN0\nIENBMCAXDTI2MDUwNjA1MTIyMVoYDzIxMjYwNTA2MDYxMjIxWjAaMRgwFgYDVQQD\nEw9rYnMuZXhhbXBsZS5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\nAQDJKcDrIRnqoXqt4L1drsY31RvUt7N+r1gFY5W007scNN9A6/SuBxVMhsgMN6/y\nuCEGCo1aICUsZplohR9UYK0asgMhTFPKQbrlR5Qkc9K0+uVZNZrybmeFaDc/inTD\nEc4HA6DE9GL2bpY08c/hEH85v9uKAM5Ar+dC68qU6ndNej/qJs/ENPgfSQu6Fxul\n82i30eOMbxgSkBavrf7NLN0Bbb8EBnlKLpmcvKhtxkvTB9rp53xfC2/iOd9mV3bG\n0DXiWNJXrI5LkKdgi6OE+734OklDcghFBkxniDEBpK7GluQoBvHK88SlLorOAE3y\nOdZsQiT7K+FzjNxvuVDv7oEzAgMBAAGjeDB2MA4GA1UdDwEB/wQEAwIHgDATBgNV\nHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFBHS5EAr\nim5bi/skARW9UNUnK5x4MCAGA1UdEQQZMBeCD2ticy5leGFtcGxlLnN2Y4cECgAA\nATANBgkqhkiG9w0BAQsFAAOCAQEARHDdKT7Ac2ELeU2/lWVvvbCt2935Y1SbZgrf\nwhjLsn8ZvPq0jxCwLKHvliIio/bIBrfs4PbX6/YazTVllRfjA4R2HR1MFAVMG0V+\nGXKbAqN1FBFvj9pRhzf5PLSS0zj3sbomwR7aVplJLI6JTM3DCplInhE5k1XGCdQP\nZNf9h4WqZt/+NyyLqqDRORhiKa3QiazroNMe4bD9vs7V3VRsSmXl+HNhhwGnMHVt\nm8eWoUHtB8YIaxuEJV8CV6epktrGVuRfByr5cKHBDpCxFAFT0pIw5JQfOwu2oiOQ\nGlV93irse08QPJxMkpXgxMdetCCGmRJ8p+OqbhBQnzDVCM4piQ==\n-----END CERTIFICATE-----\n" +name = 'cc_kbc' +url = 'http://kbs.example.svc:8080' +''' + +"policy.rego" = ''' +package agent_policy + +default CreateContainerRequest := true +default ExecProcessRequest := false +''' diff --git a/cmd/initdata/testdata/invalid-leaf-no-san.toml b/cmd/initdata/testdata/invalid-leaf-no-san.toml index 3b76065..9a7ee19 100644 --- a/cmd/initdata/testdata/invalid-leaf-no-san.toml +++ b/cmd/initdata/testdata/invalid-leaf-no-san.toml @@ -6,7 +6,7 @@ algorithm = "sha256" "aa.toml" = ''' [token_configs] [token_configs.kbs] -cert = "-----BEGIN CERTIFICATE-----\nMIIC0zCCAbugAwIBAgIBBDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdUZXN0\nIENBMCAXDTI2MDUwMzA2MDY1NFoYDzIxMjYwNTAzMDcwNjU0WjATMREwDwYDVQQD\nEwhCYWQgTGVhZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMhBKw9F\nqngYNscDXYwq/nrOrjqdoLa0NIUbpWzOYBhHdLKU0Lf+xKjQs8uhtQ6Ad79IE4ft\nEe/4q6U22gSCyQy56Urhp9piOUyBrEpgaGIC5T+K97xyuU4xA6UwZRMT+DmnejzB\nVnis9jWGfP/VrKkTTmdV30M3VxqvulAvF8XChZ27mxpWrXnb9NFAwLRsHSYqtKx1\n/vVeyUZqL5Hrq9f9JjDOKJGpcXVwEicCcmaQVuqT43tiM3IQO4HAFQKPivMATXaA\n2StUWc7SRCt86v+85sJ8EtLjbw70iUXXXSg+Itk/kvM62cM+a9wXGNcdmaM2Optg\nlcNJnHfWEFdnqgECAwEAAaMxMC8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBSN\nEyKgLGkyQHiVwJ4w/+2nT8PvFzANBgkqhkiG9w0BAQsFAAOCAQEAhtH8NPz7AQLL\nuIB3oH3V6d6paLcwa2xTRP/d3B1LaDZm2XQS2MOg4v6ZJZw9dBxhcXIQfRIYLZct\n/RXGFZJKwdxp+dpeVxsDLUc1mtCTxzGHHtAHNmjIzhydVr9oGMqEj31LLwa72sSb\n2+ziC6B4tlcwy85HMJ1dW7mRJ5ta/ZOOgk26gsZn/EYeqhyJ84oLAJLjO2PzkxS/\nqKTwtf5GEpTNnjsoRUPIKIhiE7Ed2LSlykBNuVziYlS7Tu4sAT9zwTcUWMsLeUJ0\nlTk3cB6iBhQJBZ5QHk0Z7rWR2Yb0K5WYwU9aOQQ9fN9qRZaXn0iNgg0F3fHEm+Pf\nH5EaOEBxlg==\n-----END CERTIFICATE-----\n" +cert = "-----BEGIN CERTIFICATE-----\nMIIC0zCCAbugAwIBAgIBBDANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdUZXN0\nIENBMCAXDTI2MDUwNjA1MTIyMVoYDzIxMjYwNTA2MDYxMjIxWjATMREwDwYDVQQD\nEwhCYWQgTGVhZjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANYugDM5\nP9Cb1UJcbPRgfIcXrTtvnkwTkZ8qmzpvB4zk5R3W+HbQtVN2NF0DLysdXtuMmG4Q\ntWVnPxWpXUE9zKgDBsxAUYAh5soZh5e3wigPcDtDXeav5SdRIXzy3PwxM1VaAwcq\n44b9GvIk8je6thGTGaUhimMxhdNMdvMqlIZJqEmvaeHwWNv+lpAh/lmFUtuDVQfO\nGYfRzWivaPlQOiqrTGhlXpiWx5Sycdzaofqke5rNC/9T1flf3IWpR+bDTiPixm7Z\nh2R9iajK3QNW3Fc1cfkAt+ynK+wVRIgZMp3i1COhgidtC6uIJ7wo2y9OAdgPigcb\nUYpkgr77XnQf/Q0CAwEAAaMxMC8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBQR\n0uRAK4puW4v7JAEVvVDVJyuceDANBgkqhkiG9w0BAQsFAAOCAQEAtXLdQ7XBWXAw\nW+P5bt38oy1HyuSpAmIoKs6qxrYZ6fgppnvv1+sgBEzHbZ+uwt99g7Qy3cQf95HU\noPNztFqVGka6GvvYpBrUPxoMAfc+7C3/sG8jOa+fWBwgu34iFBD3TdaTs9ysLoTf\ni5hvlcOn3qQLbgIrVeboPCrdZ4obw3OmisJbuU45+C2eGnfux+tLxQpWTgWPgyK6\npvbuymIZMy8pQZhZlYTdR4n/n0phXOzjL+ayR+XuwJ0OBEMFhpWnyoZExJbqB+6g\nd+HkcElRiAcoE4T2fSNqyLIOfEtn7qCBGX4Fgvu0ZpxYGDBzz4y9bVAxk+ryd+CK\nsCt5+kIHTA==\n-----END CERTIFICATE-----\n" url = 'http://kbs.example.svc:8080' ''' diff --git a/cmd/initdata/testdata/valid-with-both.toml b/cmd/initdata/testdata/valid-with-both.toml index 7dc65d3..53c4347 100644 --- a/cmd/initdata/testdata/valid-with-both.toml +++ b/cmd/initdata/testdata/valid-with-both.toml @@ -6,13 +6,13 @@ algorithm = "sha256" "aa.toml" = ''' [token_configs] [token_configs.kbs] -cert = "-----BEGIN CERTIFICATE-----\nMIIC4zCCAcugAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdUZXN0\nIENBMCAXDTI2MDUwMzA2MDY1NFoYDzIxMjYwNTAzMDcwNjU0WjASMRAwDgYDVQQD\nEwdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3/QZ8Hd\nYg9SpWded/KP/ut9zQoQ/6C9YhFEopVx+Xyjvepln8sbF0ZusRUFMiVTDFsOmmA8\nG0QdjuoOjjHHC1fO/IQQ3W3iyW0aU8dJK8VxW+k2qs6TSBngd3UWOb9/eXJ9Vai1\nURHJcX9JYSsYg3UJpzXXep41MJlmAzlEpZtKHBGQ3DpMhL/7pjz8mgAuRrG6E0J5\n14UiEeLorq8+uAK8OW6fDlrmnrhEQxUnyROwjeQgz0mxGFAFz8pMV45DwaTV3pR6\nSn/b98m8JedixOTy5P9l3kjSkro/aMOptXT7ZfT3X+WfGfFtaZEUawPoM6l4Jfcg\nDIrEJUrq9W0ffwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw\nAwEB/zAdBgNVHQ4EFgQUjRMioCxpMkB4lcCeMP/tp0/D7xcwDQYJKoZIhvcNAQEL\nBQADggEBAIHAdz9EpqmrIzM72tCi6vkZHchgP3p963G51XMs+lvzdyKbt3WliaHG\nIgfCqF/g/7fz8Vb0deCOSnw5vjsG6KnTKUYmKvVrfhBY3tzhtlhhOFzu543V2pNG\njyxPoY/TI2CFFfejS7FdtQFupgI3h6IqiNmjvoVIUBvSVB1T8L787k0GNY3VEocu\n9eToNyXVt4/B7tD9u8aAzqEHZxJakBwenUa/sQZm82z8hZ/YJoOdwIOM7rGPrBuP\n8ai8qojKPYmR9Lzb36mx2ntitpdZKj7QoRBwDPQbC+weVwOgtx8gZEV2NHN/OPlI\nQBrVOkBKqzdgVQdY3YVDa+UE50hOIU4=\n-----END CERTIFICATE-----\n" +cert = "-----BEGIN CERTIFICATE-----\nMIIC4zCCAcugAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdUZXN0\nIENBMCAXDTI2MDUwNjA1MTIyMVoYDzIxMjYwNTA2MDYxMjIxWjASMRAwDgYDVQQD\nEwdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA50HBT/nV\n+7TTm0W3Brh6IimY72hBcNAM3yeEig6Znm09j6yVEA9+CAfoX4Dptue1Wk32L8+Q\n6lq5SwvAAR51UalsXvDvpyKLuTWmflFcrR9GlNJ/N9xUm7UclANXH2O5f959B3+X\nSBbV/Zry/dO8uQRF0YX7PEvhlCTwDs4mE5Mg1QawyqCH/G7mvXhRiZGIRq93OEGm\n51E6uduinY9xi3goB7to8uAdFvO5JrPMPNjidwbP91MJ1Et4Jhp3voWpUwZOpz2D\ngrY8xb/FXkW0IQhWrTvz2kr0RdeMIJG5bcPVp7nP/rPF/4IfxEt+8id3cr093zhc\nAdJaESSy60QHEQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw\nAwEB/zAdBgNVHQ4EFgQUEdLkQCuKbluL+yQBFb1Q1ScrnHgwDQYJKoZIhvcNAQEL\nBQADggEBAA26SliPr3TuVFcYUaviKX1sh80lr5QtohMw1Sk7s9gyOjn228NVbca/\nCKPKG9TFJgWL7FlsI5KCl9wq9dpnN2qSk0evrH9KFKBSXRvy4tk70QYfPbmrG+Mh\no2D3AaOBJ8shDaQcUBJzEeggtMT7f0IbY5tPf4Zu1QQsscmASldMTe3ASZV0+LaR\nGmqM50GVUJIIPVkJul7SbHxKaVVce+aPVGHvUe7gbdyFaZIQc6Q1YeQbpGZUQblo\nQ68Uyr2rQ21uu9QgXR2O/RARa35PcmcMABjW8w3K+VUFcxjw2/jSebgdDsk1gH7q\nePW0nxqwpURVCnaaai7eU9781itmFJs=\n-----END CERTIFICATE-----\n" url = 'http://kbs.example.svc:8080' ''' "cdh.toml" = ''' [kbc] -kbs_cert = "-----BEGIN CERTIFICATE-----\nMIIDITCCAgmgAwIBAgIBAjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdUZXN0\nIENBMCAXDTI2MDUwMzA2MDY1NFoYDzIxMjYwNTAzMDcwNjU0WjAaMRgwFgYDVQQD\nEw9rYnMuZXhhbXBsZS5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\nAQC7f4mTv9K24cSxE1kn7YrFR6YhrY1safp6BWvFs1ZyEmSAAlxbdy1HydYdSsNa\ncC64RlVUw9u5srDx/sfkNefstQMkk5HERlpOnjQHAVCDD89Frytpnja1vu3mEzfD\npX1C/8/xOs+G19CmQXLCNLWHeUYhgGwhKxilmCMGhYyi9tAyeCCttlRqTbuDLy2V\n1x3FeKyy3Lj3lZoLrVl4jgZw18OB5RfQJVGWxmaqEF65oKqNoi7KS5kyluj0VjRQ\n0VOH8JbtHlM045MxjE8Yt2K0fYk+IYuX3L/WRQRKUEbJf9pj+04Iozqy7OEDtkDK\nyrSdSL3r73VV90MPdsWHD7dJAgMBAAGjeDB2MA4GA1UdDwEB/wQEAwIHgDATBgNV\nHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFI0TIqAs\naTJAeJXAnjD/7adPw+8XMCAGA1UdEQQZMBeCD2ticy5leGFtcGxlLnN2Y4cECgAA\nATANBgkqhkiG9w0BAQsFAAOCAQEAnp7VKvkXS/EjRw1AIQJWXibNSl8BRTByEomi\ntLFC6ZtasN6lFPyo0rH5L7dULd8nfF9j1yvrafPkR3n1ddnT303aA3cVlBq5Dpvj\n2bBl2rqKF91MK+dG+Rmn2F6bxSGigsAwrIgvWm5sbl6f7jvjXe56hWQN4JNSUmly\neIhnLJbFo5hEUu5pbxK+o1+YMohShmMrhn8ye2txsLIKTCxCZ06C7x/qJu6AN88f\nzbyqxNwBRzDc1MPYCuBx8ONBSwBu5n5BJx2HU/R4Ig50hg/KbFdrcLExBmYF17BX\nqFSN4fQPrwdgKZBbYCPsw5S3BJc4bZjROkCr1PR30RkUAdxOjw==\n-----END CERTIFICATE-----\n" +kbs_cert = "-----BEGIN CERTIFICATE-----\nMIIC5zCCAc+gAwIBAgIBBTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwlUZXN0\nIENBIDIwIBcNMjYwNTA2MDUxMjIxWhgPMjEyNjA1MDYwNjEyMjFaMBQxEjAQBgNV\nBAMTCVRlc3QgQ0EgMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANU3\niuA4dceDsha72+SC6TeZqZIY5bF5ouUnIxhvFaykAfexj0VerY7NQdkplvYU0TME\nqR89fTr+7cTYRgnCHrLiB+S2d3hLKflPjKkJm7NzpPOzv2OwEcR2riWooCqxV3sF\n5KIahJNWBl7hBGoNwHaWI4JOuWZxQuFrAilhrwx7YNIEOqrcScOCU1I7AJM6TSQG\nZ7KKQL9JzHYT+yF6A4DmnsfW4bEKsS7kA6sIAJ/zLVKyX+rwweg8MvjlO+sRGSEH\nYfT4ssIuimams8YvonFDgKRRhaeTaMQvnVjZ68lFvbq7aMxxToWMasMTKXGfvF9X\nIr8uNzFz7ePt2BwPzFECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n/wQFMAMBAf8wHQYDVR0OBBYEFB2f/MQs1N4sQCMOHZmbcDhxWQmbMA0GCSqGSIb3\nDQEBCwUAA4IBAQBA7Cy0q6Sn8pbjwIJEMc7wsI6EQDCCpvUTQON3m+vCqSDS7avp\nvLKQ+0drAD5jJojJkyb8uBwrUp/0Hhsgutat1MuJ4jyW1XIrCVS2xgjVpm9PtNdA\nqGq7xpTJ5zvvZi+1NsjC4Ns7RKVP8snmVxTweuex5o3aM0hrA2DkxUkaZH4lYLE2\ng02l3/+H9Tex9WXTtJV+ubzSXGhDsQ8y10itbbNfjn6SAwu/mtR1BWJSq9uvOEiu\nh3T8yJ1amd4uDGulFwUYMHBONYdridFaRDg54wE7/vbhqCjOVCiiwz2vodpX0nJR\ntBmNneYf+ErVRBePeL7KT1ljRCls4RxN//WT\n-----END CERTIFICATE-----\n" name = 'cc_kbc' url = 'http://kbs.example.svc:8080' ''' diff --git a/cmd/initdata/testdata/valid-with-ca-cert.toml b/cmd/initdata/testdata/valid-with-ca-cert.toml index ac9a095..643a413 100644 --- a/cmd/initdata/testdata/valid-with-ca-cert.toml +++ b/cmd/initdata/testdata/valid-with-ca-cert.toml @@ -6,7 +6,7 @@ algorithm = "sha256" "aa.toml" = ''' [token_configs] [token_configs.kbs] -cert = "-----BEGIN CERTIFICATE-----\nMIIC4zCCAcugAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdUZXN0\nIENBMCAXDTI2MDUwMzA2MDY1NFoYDzIxMjYwNTAzMDcwNjU0WjASMRAwDgYDVQQD\nEwdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu3/QZ8Hd\nYg9SpWded/KP/ut9zQoQ/6C9YhFEopVx+Xyjvepln8sbF0ZusRUFMiVTDFsOmmA8\nG0QdjuoOjjHHC1fO/IQQ3W3iyW0aU8dJK8VxW+k2qs6TSBngd3UWOb9/eXJ9Vai1\nURHJcX9JYSsYg3UJpzXXep41MJlmAzlEpZtKHBGQ3DpMhL/7pjz8mgAuRrG6E0J5\n14UiEeLorq8+uAK8OW6fDlrmnrhEQxUnyROwjeQgz0mxGFAFz8pMV45DwaTV3pR6\nSn/b98m8JedixOTy5P9l3kjSkro/aMOptXT7ZfT3X+WfGfFtaZEUawPoM6l4Jfcg\nDIrEJUrq9W0ffwIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw\nAwEB/zAdBgNVHQ4EFgQUjRMioCxpMkB4lcCeMP/tp0/D7xcwDQYJKoZIhvcNAQEL\nBQADggEBAIHAdz9EpqmrIzM72tCi6vkZHchgP3p963G51XMs+lvzdyKbt3WliaHG\nIgfCqF/g/7fz8Vb0deCOSnw5vjsG6KnTKUYmKvVrfhBY3tzhtlhhOFzu543V2pNG\njyxPoY/TI2CFFfejS7FdtQFupgI3h6IqiNmjvoVIUBvSVB1T8L787k0GNY3VEocu\n9eToNyXVt4/B7tD9u8aAzqEHZxJakBwenUa/sQZm82z8hZ/YJoOdwIOM7rGPrBuP\n8ai8qojKPYmR9Lzb36mx2ntitpdZKj7QoRBwDPQbC+weVwOgtx8gZEV2NHN/OPlI\nQBrVOkBKqzdgVQdY3YVDa+UE50hOIU4=\n-----END CERTIFICATE-----\n" +cert = "-----BEGIN CERTIFICATE-----\nMIIC4zCCAcugAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdUZXN0\nIENBMCAXDTI2MDUwNjA1MTIyMVoYDzIxMjYwNTA2MDYxMjIxWjASMRAwDgYDVQQD\nEwdUZXN0IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA50HBT/nV\n+7TTm0W3Brh6IimY72hBcNAM3yeEig6Znm09j6yVEA9+CAfoX4Dptue1Wk32L8+Q\n6lq5SwvAAR51UalsXvDvpyKLuTWmflFcrR9GlNJ/N9xUm7UclANXH2O5f959B3+X\nSBbV/Zry/dO8uQRF0YX7PEvhlCTwDs4mE5Mg1QawyqCH/G7mvXhRiZGIRq93OEGm\n51E6uduinY9xi3goB7to8uAdFvO5JrPMPNjidwbP91MJ1Et4Jhp3voWpUwZOpz2D\ngrY8xb/FXkW0IQhWrTvz2kr0RdeMIJG5bcPVp7nP/rPF/4IfxEt+8id3cr093zhc\nAdJaESSy60QHEQIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw\nAwEB/zAdBgNVHQ4EFgQUEdLkQCuKbluL+yQBFb1Q1ScrnHgwDQYJKoZIhvcNAQEL\nBQADggEBAA26SliPr3TuVFcYUaviKX1sh80lr5QtohMw1Sk7s9gyOjn228NVbca/\nCKPKG9TFJgWL7FlsI5KCl9wq9dpnN2qSk0evrH9KFKBSXRvy4tk70QYfPbmrG+Mh\no2D3AaOBJ8shDaQcUBJzEeggtMT7f0IbY5tPf4Zu1QQsscmASldMTe3ASZV0+LaR\nGmqM50GVUJIIPVkJul7SbHxKaVVce+aPVGHvUe7gbdyFaZIQc6Q1YeQbpGZUQblo\nQ68Uyr2rQ21uu9QgXR2O/RARa35PcmcMABjW8w3K+VUFcxjw2/jSebgdDsk1gH7q\nePW0nxqwpURVCnaaai7eU9781itmFJs=\n-----END CERTIFICATE-----\n" url = 'http://kbs.example.svc:8080' ''' diff --git a/cmd/initdata/testdata/valid-with-leaf-cert.toml b/cmd/initdata/testdata/valid-with-leaf-cert.toml deleted file mode 100644 index 15b6bfb..0000000 --- a/cmd/initdata/testdata/valid-with-leaf-cert.toml +++ /dev/null @@ -1,24 +0,0 @@ -version = "0.1.0" -algorithm = "sha256" - -[data] - -"aa.toml" = ''' -[token_configs] -[token_configs.kbs] -url = 'http://kbs.example.svc:8080' -''' - -"cdh.toml" = ''' -[kbc] -kbs_cert = "-----BEGIN CERTIFICATE-----\nMIIDITCCAgmgAwIBAgIBAjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDEwdUZXN0\nIENBMCAXDTI2MDUwMzA2MDY1NFoYDzIxMjYwNTAzMDcwNjU0WjAaMRgwFgYDVQQD\nEw9rYnMuZXhhbXBsZS5zdmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB\nAQC7f4mTv9K24cSxE1kn7YrFR6YhrY1safp6BWvFs1ZyEmSAAlxbdy1HydYdSsNa\ncC64RlVUw9u5srDx/sfkNefstQMkk5HERlpOnjQHAVCDD89Frytpnja1vu3mEzfD\npX1C/8/xOs+G19CmQXLCNLWHeUYhgGwhKxilmCMGhYyi9tAyeCCttlRqTbuDLy2V\n1x3FeKyy3Lj3lZoLrVl4jgZw18OB5RfQJVGWxmaqEF65oKqNoi7KS5kyluj0VjRQ\n0VOH8JbtHlM045MxjE8Yt2K0fYk+IYuX3L/WRQRKUEbJf9pj+04Iozqy7OEDtkDK\nyrSdSL3r73VV90MPdsWHD7dJAgMBAAGjeDB2MA4GA1UdDwEB/wQEAwIHgDATBgNV\nHSUEDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFI0TIqAs\naTJAeJXAnjD/7adPw+8XMCAGA1UdEQQZMBeCD2ticy5leGFtcGxlLnN2Y4cECgAA\nATANBgkqhkiG9w0BAQsFAAOCAQEAnp7VKvkXS/EjRw1AIQJWXibNSl8BRTByEomi\ntLFC6ZtasN6lFPyo0rH5L7dULd8nfF9j1yvrafPkR3n1ddnT303aA3cVlBq5Dpvj\n2bBl2rqKF91MK+dG+Rmn2F6bxSGigsAwrIgvWm5sbl6f7jvjXe56hWQN4JNSUmly\neIhnLJbFo5hEUu5pbxK+o1+YMohShmMrhn8ye2txsLIKTCxCZ06C7x/qJu6AN88f\nzbyqxNwBRzDc1MPYCuBx8ONBSwBu5n5BJx2HU/R4Ig50hg/KbFdrcLExBmYF17BX\nqFSN4fQPrwdgKZBbYCPsw5S3BJc4bZjROkCr1PR30RkUAdxOjw==\n-----END CERTIFICATE-----\n" -name = 'cc_kbc' -url = 'http://kbs.example.svc:8080' -''' - -"policy.rego" = ''' -package agent_policy - -default CreateContainerRequest := true -default ExecProcessRequest := false -''' diff --git a/cmd/initdata/validate.go b/cmd/initdata/validate.go index 973fb8a..2553692 100644 --- a/cmd/initdata/validate.go +++ b/cmd/initdata/validate.go @@ -6,7 +6,9 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" + "errors" "fmt" + "io" "os" "sort" "strings" @@ -17,6 +19,11 @@ import ( "github.com/spf13/cobra" ) +// errValidationFailed is a sentinel returned when runValidate has already +// printed its own diagnostics and wants a non-zero exit without Cobra +// printing an additional "Error: ..." line. +var errValidationFailed = errors.New("validation failed") + var validateCmd = &cobra.Command{ Use: "validate", Short: "Validate initdata structure and embedded certificates", @@ -28,7 +35,12 @@ Checks: - TOML parses cleanly - version == "0.1.0" and algorithm is one of sha256, sha384, sha512 - aa.toml and cdh.toml are present (policy.rego is optional) - - Embedded certs pass rustls rules (CA: keyCertSign; leaf: SAN + serverAuth, not self-signed) + - Embedded certs are CA certificates (CA:TRUE, keyCertSign key usage) + +Rejected certs: leaf/non-CA certificates, expired certs, SHA-1 or MD5 +signatures, unknown critical extensions, RSA keys shorter than 1024 bits. + +Exit codes: 0 = passed, 1 = validation failed or input error. Examples: kubectl coco initdata validate --file ~/.kube/coco-initdata.toml @@ -42,15 +54,28 @@ func init() { validateCmd.Flags().StringVar(&validateFile, "file", "", "Path to plaintext initdata TOML file (reads encoded blob from stdin if not set)") } -func runValidate(_ *cobra.Command, _ []string) error { +// silenceAndReturn silences Cobra's own error/usage output for this command +// and returns the sentinel. Call only after writing diagnostics to stderr. +// cmd may be nil when runValidate is called directly in tests. +func silenceAndReturn(cmd *cobra.Command) error { + if cmd != nil { + cmd.SilenceErrors = true + cmd.SilenceUsage = true + } + return errValidationFailed +} + +func runValidate(cmd *cobra.Command, _ []string) error { tomlBytes, err := loadInitdataTOML(validateFile, os.Stdin) if err != nil { - return fmt.Errorf("failed to load initdata: %w", err) + fmt.Fprintf(os.Stderr, "Error: failed to load initdata: %v\n", err) + return silenceAndReturn(cmd) } var id pkginitdata.InitData if err := toml.Unmarshal(tomlBytes, &id); err != nil { - return fmt.Errorf("failed to parse TOML: %w", err) + fmt.Fprintf(os.Stderr, "Error: failed to parse TOML: %v\n", err) + return silenceAndReturn(cmd) } var failures []string @@ -75,14 +100,18 @@ func runValidate(_ *cobra.Command, _ []string) error { if err != nil { failures = append(failures, fmt.Sprintf("cert extraction failed: %v", err)) } else if len(entries) > 0 { - reportCerts(entries) + _ = reportCerts(os.Stderr, entries) if err := validateCertsBySource(entries); err != nil { failures = append(failures, err.Error()) } } if len(failures) > 0 { - return fmt.Errorf("validation failed:\n %s", strings.Join(failures, "\n ")) + fmt.Fprintln(os.Stderr, "Validation failed:") + for _, f := range failures { + fmt.Fprintf(os.Stderr, " %s\n", f) + } + return silenceAndReturn(cmd) } fmt.Println("Validation passed.") @@ -158,7 +187,21 @@ func checkKBSURLMismatch(data map[string]string) string { return "" } -func reportCerts(entries []certEntry) { +// diagWriter wraps an io.Writer and captures the first write error, +// short-circuiting subsequent writes so the caller can check once at the end. +type diagWriter struct { + w io.Writer + err error +} + +func (d *diagWriter) printf(format string, a ...any) { + if d.err == nil { + _, d.err = fmt.Fprintf(d.w, format, a...) + } +} + +func reportCerts(w io.Writer, entries []certEntry) error { + dw := &diagWriter{w: w} caCount, leafCount := 0, 0 for _, e := range entries { if e.cert.IsCA { @@ -167,7 +210,7 @@ func reportCerts(entries []certEntry) { leafCount++ } } - fmt.Printf("Certificates: %d total (%d CA, %d leaf)\n\n", len(entries), caCount, leafCount) + dw.printf("Certificates: %d total (%d CA, %d leaf)\n\n", len(entries), caCount, leafCount) for i, e := range entries { cert := e.cert @@ -189,25 +232,26 @@ func reportCerts(entries []certEntry) { fp := sha256.Sum256(cert.Raw) fingerprint := fmt.Sprintf("%X", fp[:6]) // first 6 bytes for brevity - fmt.Printf(" [%d] %s [%s]\n", i+1, certDisplayName(cert), typeLabel) - fmt.Printf(" %-12s %s\n", "Issuer:", issuerCN) - fmt.Printf(" %-12s %s → %s (%s)\n", "Valid:", cert.NotBefore.Format("2006-01-02"), cert.NotAfter.Format("2006-01-02"), certExpiryNote(cert)) - fmt.Printf(" %-12s %s\n", "Key:", certKeyDesc(cert.PublicKey)) + dw.printf(" [%d] %s [%s]\n", i+1, certDisplayName(cert), typeLabel) + dw.printf(" %-12s %s\n", "Issuer:", issuerCN) + dw.printf(" %-12s %s → %s (%s)\n", "Valid:", cert.NotBefore.Format("2006-01-02"), cert.NotAfter.Format("2006-01-02"), certExpiryNote(cert)) + dw.printf(" %-12s %s\n", "Key:", certKeyDesc(cert.PublicKey)) if usage := certFormatKeyUsage(cert.KeyUsage); usage != "" { - fmt.Printf(" %-12s %s\n", "Usage:", usage) + dw.printf(" %-12s %s\n", "Usage:", usage) } if !cert.IsCA { if san := certFormatSANs(cert); san != "" { - fmt.Printf(" %-12s %s\n", "SAN:", san) + dw.printf(" %-12s %s\n", "SAN:", san) } } if eku := certFormatEKU(cert.ExtKeyUsage); eku != "" { - fmt.Printf(" %-12s %s\n", "EKU:", eku) + dw.printf(" %-12s %s\n", "EKU:", eku) } - fmt.Printf(" %-12s %s\n", "Fingerprint:", fingerprint) - fmt.Printf(" %-12s %s\n", "Source:", e.source) - fmt.Println() + dw.printf(" %-12s %s\n", "Fingerprint:", fingerprint) + dw.printf(" %-12s %s\n", "Source:", e.source) + dw.printf("\n") } + return dw.err } // certDisplayName returns a human-readable identifier for cert. It prefers the diff --git a/cmd/initdata/validate_test.go b/cmd/initdata/validate_test.go index 8803f13..e97e1c5 100644 --- a/cmd/initdata/validate_test.go +++ b/cmd/initdata/validate_test.go @@ -10,6 +10,24 @@ import ( "testing" ) +// runValidateStderr runs runValidate and returns stderr output alongside the error. +// Use this for tests that check validation failure messages. +func runValidateStderr(t *testing.T) (string, error) { + t.Helper() + r, w, pipeErr := os.Pipe() + if pipeErr != nil { + t.Fatalf("os.Pipe: %v", pipeErr) + } + oldStderr := os.Stderr + os.Stderr = w + t.Cleanup(func() { os.Stderr = oldStderr }) + err := runValidate(nil, nil) + _ = w.Close() + out, _ := io.ReadAll(r) + _ = r.Close() + return string(out), err +} + func encodeBlobFromFile(t *testing.T, path string) string { t.Helper() raw, err := os.ReadFile(path) @@ -60,9 +78,9 @@ func TestRunValidate_WrongVersion(t *testing.T) { validateFile = "testdata/invalid-version.toml" defer func() { validateFile = "" }() - err := runValidate(nil, nil) - if err == nil || !strings.Contains(err.Error(), "version") { - t.Errorf("expected version error, got: %v", err) + stderr, err := runValidateStderr(t) + if err == nil || !strings.Contains(stderr, "version") { + t.Errorf("expected version error in stderr, got err=%v stderr=%q", err, stderr) } } @@ -70,9 +88,9 @@ func TestRunValidate_WrongAlgorithm(t *testing.T) { validateFile = "testdata/invalid-algorithm.toml" defer func() { validateFile = "" }() - err := runValidate(nil, nil) - if err == nil || !strings.Contains(err.Error(), "algorithm") { - t.Errorf("expected algorithm error, got: %v", err) + stderr, err := runValidateStderr(t) + if err == nil || !strings.Contains(stderr, "algorithm") { + t.Errorf("expected algorithm error in stderr, got err=%v stderr=%q", err, stderr) } } @@ -80,9 +98,9 @@ func TestRunValidate_MissingRequiredKey(t *testing.T) { validateFile = "testdata/invalid-missing-cdh.toml" defer func() { validateFile = "" }() - err := runValidate(nil, nil) - if err == nil || !strings.Contains(err.Error(), "cdh.toml") { - t.Errorf("expected missing cdh.toml error, got: %v", err) + stderr, err := runValidateStderr(t) + if err == nil || !strings.Contains(stderr, "cdh.toml") { + t.Errorf("expected missing cdh.toml in stderr, got err=%v stderr=%q", err, stderr) } } @@ -94,37 +112,41 @@ func TestRunValidate_WithCACert(t *testing.T) { } } -func TestRunValidate_WithLeafCert(t *testing.T) { - validateFile = "testdata/valid-with-leaf-cert.toml" +// TestRunValidate_LeafCertRejected uses a cert with valid SAN and serverAuth EKU +// — a cert that would have passed the old leaf-cert rules — to confirm that even +// a "well-formed" leaf cert is rejected when used as a trust anchor. +func TestRunValidate_LeafCertRejected(t *testing.T) { + validateFile = "testdata/invalid-leaf-cert.toml" defer func() { validateFile = "" }() - if err := runValidate(nil, nil); err != nil { - t.Errorf("runValidate() with leaf cert fixture: %v", err) + stderr, err := runValidateStderr(t) + if err == nil || !strings.Contains(stderr, "not a CA certificate") { + t.Errorf("runValidate() should reject leaf cert, got err=%v stderr=%q", err, stderr) } } -func TestRunValidate_WithBothCertTypes(t *testing.T) { +func TestRunValidate_WithBothCACerts(t *testing.T) { validateFile = "testdata/valid-with-both.toml" defer func() { validateFile = "" }() if err := runValidate(nil, nil); err != nil { - t.Errorf("runValidate() with CA+leaf fixture: %v", err) + t.Errorf("runValidate() with two CA cert fixture: %v", err) } } func TestRunValidate_InvalidEmbeddedCert(t *testing.T) { validateFile = "testdata/invalid-leaf-no-san.toml" defer func() { validateFile = "" }() - err := runValidate(nil, nil) - if err == nil || !strings.Contains(err.Error(), "cert validation failed") { - t.Errorf("expected cert validation error, got: %v", err) + stderr, err := runValidateStderr(t) + if err == nil || !strings.Contains(stderr, "not a CA certificate") { + t.Errorf("expected 'not a CA certificate' in stderr, got err=%v stderr=%q", err, stderr) } } func TestRunValidate_ExpiredCertFixture(t *testing.T) { validateFile = "testdata/invalid-expired-cert.toml" defer func() { validateFile = "" }() - err := runValidate(nil, nil) - if err == nil || !strings.Contains(err.Error(), "expired") { - t.Errorf("expected expired cert error, got: %v", err) + stderr, err := runValidateStderr(t) + if err == nil || !strings.Contains(stderr, "expired") { + t.Errorf("expected 'expired' in stderr, got err=%v stderr=%q", err, stderr) } } @@ -198,11 +220,11 @@ url = "http://kbs2.svc:8080" } oldStderr := os.Stderr os.Stderr = w + t.Cleanup(func() { os.Stderr = oldStderr }) runErr := runValidate(nil, nil) _ = w.Close() - os.Stderr = oldStderr stderrOut, _ := io.ReadAll(r) _ = r.Close() @@ -304,39 +326,27 @@ func TestCheckKBSURLMismatch_AllTokenConfigsCompared(t *testing.T) { } func TestReportCerts_Output(t *testing.T) { - caCert, caKey, _ := makeTestCACert(t) - leaf := makeValidLeafCert(t, caCert, caKey) + ca1, _, _ := makeTestCACert(t) + ca2, _, _ := makeTestCACert(t) entries := []certEntry{ - {cert: caCert, source: "aa.toml/token_configs.kbs"}, - {cert: leaf, source: "cdh.toml/kbc"}, + {cert: ca1, source: "aa.toml/token_configs.kbs"}, + {cert: ca2, source: "cdh.toml/kbc"}, } - r, w, err := os.Pipe() - if err != nil { - t.Fatalf("os.Pipe: %v", err) + var buf bytes.Buffer + if err := reportCerts(&buf, entries); err != nil { + t.Fatalf("reportCerts() unexpected error: %v", err) } - oldStdout := os.Stdout - os.Stdout = w - reportCerts(entries) - _ = w.Close() - os.Stdout = oldStdout - out, _ := io.ReadAll(r) - _ = r.Close() - - output := string(out) + output := buf.String() checks := []struct { label string want string }{ {"summary line", "2 total"}, - {"CA count", "1 CA"}, - {"leaf count", "1 leaf"}, + {"CA count", "2 CA"}, + {"leaf count", "0 leaf"}, {"CA subject", "Test CA"}, {"CA type label", "[CA"}, - {"leaf subject", "Test Valid Leaf"}, - {"leaf type label", "[leaf]"}, - {"SAN", "DNS:test.example.com"}, - {"EKU", "serverAuth"}, {"source aa", "aa.toml/token_configs.kbs"}, {"source cdh", "cdh.toml/kbc"}, {"key type", "RSA-"}, diff --git a/cmd/root.go b/cmd/root.go index 766c4aa..f16ae24 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,7 +25,8 @@ It provides commands to: - Create CoCo configuration - Transform regular K8s manifests to CoCo-enabled manifests - Deploy CoCo applications`, - Version: version, + Version: version, + SilenceUsage: true, } // Execute runs the root command