feat: add ruleset api(#1844)

* 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(config): remove usergroups default

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

* fix(config): remove usergroups default

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

* sec(ghsa-2ww6-hf35-mfjm): intercept namespace subresource

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

* feat(api): add rulestatus api

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

* chore: conflicts

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

* chore: conflicts

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

* chore: conflicts

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

* chore: conflicts

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

* chore: conflicts

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

* chore: conflicts

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

* chore: conflicts

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

* chore: conflicts

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

* chore: conflicts

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

* chore: conflicts

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

* chore: conflicts

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

* feat(api): add rulestatus api

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

* feat(api): add rulestatus api

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

* feat(api): add rulestatus api

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

* feat(api): add rulestatus api

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

* feat(api): add rulestatus api

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

* feat(api): add rulestatus api

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

---------

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
This commit is contained in:
Oliver Bähler
2026-01-27 14:28:48 +01:00
committed by GitHub
parent b9a14a954d
commit a6b830b1af
284 changed files with 12699 additions and 2162 deletions
+201
View File
@@ -0,0 +1,201 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package cert
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"
"github.com/pkg/errors"
)
type CA interface {
GenerateCertificate(opts CertificateOptions) (certificatePem *bytes.Buffer, certificateKey *bytes.Buffer, err error)
CACertificatePem() (b *bytes.Buffer, err error)
CAPrivateKeyPem() (b *bytes.Buffer, err error)
ExpiresIn(now time.Time) (time.Duration, error)
ValidateCert(certificate *x509.Certificate) error
}
type CapsuleCA struct {
certificate *x509.Certificate
key *rsa.PrivateKey
}
func NewCertificateAuthorityFromBytes(certBytes, keyBytes []byte) (*CapsuleCA, error) {
cert, key, err := GetCertificateWithPrivateKeyFromBytes(certBytes, keyBytes)
if err != nil {
return nil, err
}
return &CapsuleCA{
certificate: cert,
key: key,
}, nil
}
func (c CapsuleCA) CACertificatePem() (b *bytes.Buffer, err error) {
var crtBytes []byte
crtBytes, err = x509.CreateCertificate(rand.Reader, c.certificate, c.certificate, &c.key.PublicKey, c.key)
if err != nil {
return b, err
}
b = new(bytes.Buffer)
err = pem.Encode(b, &pem.Block{
Type: "CERTIFICATE",
Bytes: crtBytes,
})
return b, err
}
func (c CapsuleCA) CAPrivateKeyPem() (b *bytes.Buffer, err error) {
b = new(bytes.Buffer)
return b, pem.Encode(b, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(c.key),
})
}
func ValidateCertificate(cert *x509.Certificate, key *rsa.PrivateKey, expirationThreshold time.Duration) error {
if !key.PublicKey.Equal(cert.PublicKey) {
return errors.New("certificate signed by wrong public key")
}
now := time.Now()
if now.Before(cert.NotBefore) {
return errors.New("certificate is not valid yet")
}
if now.After(cert.NotAfter.Add(-expirationThreshold)) {
return errors.New("certificate expired or going to expire soon")
}
return nil
}
func GenerateCertificateAuthority() (s *CapsuleCA, err error) {
s = &CapsuleCA{
certificate: &x509.Certificate{
SerialNumber: big.NewInt(2019),
Subject: pkix.Name{
Organization: []string{"Clastix"},
Country: []string{"UK"},
Province: []string{""},
Locality: []string{"London"},
StreetAddress: []string{"27, Old Gloucester Street"},
PostalCode: []string{"WC1N 3AX"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(10, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
},
}
s.key, err = rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, err
}
return s, err
}
func GetCertificateFromBytes(certBytes []byte) (*x509.Certificate, error) {
var b *pem.Block
b, _ = pem.Decode(certBytes)
return x509.ParseCertificate(b.Bytes)
}
func GetPrivateKeyFromBytes(keyBytes []byte) (*rsa.PrivateKey, error) {
var b *pem.Block
b, _ = pem.Decode(keyBytes)
return x509.ParsePKCS1PrivateKey(b.Bytes)
}
func GetCertificateWithPrivateKeyFromBytes(certBytes, keyBytes []byte) (*x509.Certificate, *rsa.PrivateKey, error) {
cert, err := GetCertificateFromBytes(certBytes)
if err != nil {
return nil, nil, err
}
key, err := GetPrivateKeyFromBytes(keyBytes)
if err != nil {
return nil, nil, err
}
return cert, key, nil
}
func (c *CapsuleCA) GenerateCertificate(opts CertificateOptions) (certificatePem *bytes.Buffer, certificateKey *bytes.Buffer, err error) {
var certPrivKey *rsa.PrivateKey
certPrivKey, err = rsa.GenerateKey(rand.Reader, 4096)
if err != nil {
return nil, nil, err
}
cert := &x509.Certificate{
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
Organization: []string{"Clastix"},
Country: []string{"UK"},
Province: []string{""},
Locality: []string{"London"},
StreetAddress: []string{"27, Old Gloucester Street"},
PostalCode: []string{"WC1N 3AX"},
},
DNSNames: opts.DNSNames(),
NotBefore: time.Now().AddDate(0, 0, -1),
NotAfter: opts.ExpirationDate(),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}
var certBytes []byte
certBytes, err = x509.CreateCertificate(rand.Reader, cert, c.certificate, &certPrivKey.PublicKey, c.key)
if err != nil {
return nil, nil, err
}
certificatePem = new(bytes.Buffer)
err = pem.Encode(certificatePem, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
if err != nil {
return certificatePem, certificateKey, err
}
certificateKey = new(bytes.Buffer)
err = pem.Encode(certificateKey, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(certPrivKey),
})
if err != nil {
return certificatePem, certificateKey, err
}
return certificatePem, certificateKey, err
}
+76
View File
@@ -0,0 +1,76 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package cert
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestNewCertificateAuthorityFromBytes(t *testing.T) {
var ca *CapsuleCA
var err error
ca, err = GenerateCertificateAuthority()
assert.Nil(t, err)
var crt *bytes.Buffer
crt, err = ca.CACertificatePem()
assert.Nil(t, err)
var key *bytes.Buffer
key, err = ca.CAPrivateKeyPem()
assert.Nil(t, err)
_, err = NewCertificateAuthorityFromBytes(crt.Bytes(), key.Bytes())
assert.Nil(t, err)
}
func TestCapsuleCa_GenerateCertificate(t *testing.T) {
type testCase struct {
dnsNames []string
}
for name, c := range map[string]testCase{
"foo.tld": {[]string{"foo.tld"}},
"SAN": {[]string{"capsule-webhook-service.capsule-system.svc", "capsule-webhook-service.capsule-system.default.cluster"}},
} {
t.Run(name, func(t *testing.T) {
var ca *CapsuleCA
var err error
e := time.Now().AddDate(1, 0, 0)
ca, err = GenerateCertificateAuthority()
assert.Nil(t, err)
var crt *bytes.Buffer
var key *bytes.Buffer
crt, key, err = ca.GenerateCertificate(NewCertOpts(e, c.dnsNames...))
assert.Nil(t, err)
var b *pem.Block
var c *x509.Certificate
b, _ = pem.Decode(crt.Bytes())
c, err = x509.ParseCertificate(b.Bytes)
assert.Nil(t, err)
assert.Equal(t, e.Unix(), c.NotAfter.Unix())
for _, i := range c.DNSNames {
assert.Contains(t, c.DNSNames, i)
}
_, err = tls.X509KeyPair(crt.Bytes(), key.Bytes())
assert.Nil(t, err)
})
}
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package cert
type CaNotYetValidError struct{}
func (CaNotYetValidError) Error() string {
return "The current CA is not yet valid"
}
type CaExpiredError struct{}
func (CaExpiredError) Error() string {
return "The current CA is expired"
}
+28
View File
@@ -0,0 +1,28 @@
// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package cert
import "time"
type CertificateOptions interface {
DNSNames() []string
ExpirationDate() time.Time
}
type certOpts struct {
dnsNames []string
expirationDate time.Time
}
func (c certOpts) DNSNames() []string {
return c.dnsNames
}
func (c certOpts) ExpirationDate() time.Time {
return c.expirationDate
}
func NewCertOpts(expirationDate time.Time, dnsNames ...string) CertificateOptions {
return &certOpts{dnsNames: dnsNames, expirationDate: expirationDate}
}