mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-08-25 16:07:24 +00:00
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:
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
Reference in New Issue
Block a user