Skip to content

Commit

Permalink
feat: Introducing a Validating Admission Controller (argoproj#1021)
Browse files Browse the repository at this point in the history
* feat: Introducing a Validating Admission Controller

Signed-off-by: Derek Wang <[email protected]>
  • Loading branch information
whynowy authored Feb 5, 2021
1 parent 5452769 commit 50257db
Show file tree
Hide file tree
Showing 44 changed files with 1,859 additions and 20 deletions.
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,13 @@ RUN argo version || true
COPY dist/sensor /bin/sensor

ENTRYPOINT [ "/bin/sensor" ]

####################################################################################################
# events-webhook
####################################################################################################
FROM scratch as events-webhook
COPY --from=base /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=base /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY dist/events-webhook /bin/events-webhook
ENTRYPOINT [ "/bin/events-webhook" ]

28 changes: 26 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ endif

# Build the project images
.DELETE_ON_ERROR:
all: sensor sensor-controller eventbus-controller eventsource-controller eventsource
all: sensor sensor-controller eventbus-controller eventsource-controller eventsource events-webhook

all-images: sensor-image sensor-controller-image eventbus-controller-image eventsource-controller-image eventsource-image
all-images: sensor-image sensor-controller-image eventbus-controller-image eventsource-controller-image eventsource-image events-webhook-image

all-controller-images: sensor-controller-image eventbus-controller-image eventsource-controller-image

Expand Down Expand Up @@ -145,6 +145,26 @@ eventbus-controller-image: dist/eventbus-controller-linux-amd64
DOCKER_BUILDKIT=1 docker build -t $(IMAGE_PREFIX)eventbus-controller:$(IMAGE_TAG) --target eventbus-controller -f $(DOCKERFILE) .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)eventbus-controller:$(IMAGE_TAG) ; fi

# Webhook
.PHONY: events-webhook
events-webhook: dist/events-webhook-linux-amd64

dist/events-webhook: GOARGS = GOOS= GOARCH=
dist/events-webhook-linux-amd64: GOARGS = GOOS=linux GOARCH=amd64
dist/events-webhook-linux-arm64: GOARGS = GOOS=linux GOARCH=arm64
dist/events-webhook-linux-ppc64le: GOARGS = GOOS=linux GOARCH=ppc64le
dist/events-webhook-linux-s390x: GOARGS = GOOS=linux GOARCH=s390x

dist/events-webhook:
go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/events-webhook ./webhook/cmd/main.go

dist/events-webhook-%:
CGO_ENABLED=0 $(GOARGS) go build -v -ldflags '${LDFLAGS}' -o ${DIST_DIR}/events-webhook ./webhook/cmd/main.go

events-webhook-image: dist/events-webhook-linux-amd64
DOCKER_BUILDKIT=1 docker build -t $(IMAGE_PREFIX)events-webhook:$(IMAGE_TAG) --target events-webhook -f $(DOCKERFILE) .
@if [ "$(DOCKER_PUSH)" = "true" ] ; then docker push $(IMAGE_PREFIX)events-webhook:$(IMAGE_TAG) ; fi

test:
go test $(shell go list ./... | grep -v /vendor/ | grep -v /test/e2e/) -race -short -v

Expand All @@ -163,6 +183,7 @@ crds:
manifests: crds
kustomize build manifests/cluster-install > manifests/install.yaml
kustomize build manifests/namespace-install > manifests/namespace-install.yaml
kustomize build manifests/extensions/validating-webhook > manifests/install-validating-webhook.yaml

.PHONY: swagger
swagger:
Expand Down Expand Up @@ -219,3 +240,6 @@ quay-release: eventbus-controller-image sensor-controller-image sensor-image eve

docker tag $(IMAGE_PREFIX)eventsource:$(IMAGE_TAG) quay.io/$(IMAGE_PREFIX)eventsource:$(IMAGE_TAG)
docker push quay.io/$(IMAGE_PREFIX)eventsource:$(IMAGE_TAG)

docker tag $(IMAGE_PREFIX)events-webhook:$(IMAGE_TAG) quay.io/$(IMAGE_PREFIX)events-webhook:$(IMAGE_TAG)
docker push quay.io/$(IMAGE_PREFIX)events-webhook:$(IMAGE_TAG)
2 changes: 2 additions & 0 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ const (
EventBusAuthFileMountPath = "/etc/eventbus/auth"
// Default NATS Streaming messages max age
NATSStreamingMaxAge = "72h"
// Default EventBus name
DefaultEventBusName = "default"
)

// Sensor constants
Expand Down
117 changes: 117 additions & 0 deletions common/tls/tls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package tls

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"

"github.com/pkg/errors"
)

func certTemplate(org string, hosts []string, notAfter time.Time) (*x509.Certificate, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return nil, errors.Wrap(err, "failed to generate serial number")
}
return &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{org},
},
SignatureAlgorithm: x509.SHA256WithRSA,
NotBefore: time.Now(),
NotAfter: notAfter,
BasicConstraintsValid: true,
DNSNames: hosts,
}, nil
}

func createCACertTemplate(org string, hosts []string, notAfter time.Time) (*x509.Certificate, error) {
rootCert, err := certTemplate(org, hosts, notAfter)
if err != nil {
return nil, err
}
rootCert.IsCA = true
rootCert.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature
rootCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
return rootCert, nil
}

func createServerCertTemplate(org string, hosts []string, notAfter time.Time) (*x509.Certificate, error) {
serverCert, err := certTemplate(org, hosts, notAfter)
if err != nil {
return nil, err
}
serverCert.KeyUsage = x509.KeyUsageDigitalSignature
serverCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
return serverCert, err
}

// Sign the cert
func createCert(template, parent *x509.Certificate, pub, parentPriv interface{}) (
cert *x509.Certificate, certPEM []byte, err error) {
certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv)
if err != nil {
return
}
cert, err = x509.ParseCertificate(certDER)
if err != nil {
return
}
b := pem.Block{Type: "CERTIFICATE", Bytes: certDER}
certPEM = pem.EncodeToMemory(&b)
return
}

func createCA(org string, hosts []string, notAfter time.Time) (*rsa.PrivateKey, *x509.Certificate, []byte, error) {
rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to generate random key")
}

rootCertTmpl, err := createCACertTemplate(org, hosts, notAfter)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to generate CA cert")
}

rootCert, rootCertPEM, err := createCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to sign CA cert")
}
return rootKey, rootCert, rootCertPEM, nil
}

// CreateCerts creates and returns a CA certificate and certificate and
// key for the server
func CreateCerts(org string, hosts []string, notAfter time.Time) (serverKey, serverCert, caCert []byte, err error) {
// Create a CA certificate and private key
caKey, caCertificate, caCertificatePEM, err := createCA(org, hosts, notAfter)
if err != nil {
return nil, nil, nil, err
}

// Create the private key
servKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to generate random key")
}
servCertTemplate, err := createServerCertTemplate(org, hosts, notAfter)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to create server cert template")
}

// create a certificate wrapping the public key, sign it with the CA private key
_, servCertPEM, err := createCert(servCertTemplate, caCertificate, &servKey.PublicKey, caKey)
if err != nil {
return nil, nil, nil, errors.Wrap(err, "failed to sign server cert")
}
servKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(servKey),
})
return servKeyPEM, servCertPEM, caCertificatePEM, nil
}
48 changes: 48 additions & 0 deletions common/tls/tls_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package tls

import (
"crypto/x509"
"encoding/pem"
"testing"
"time"

"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)

func TestCreateCerts(t *testing.T) {
t.Run("test create certs", func(t *testing.T) {
sKey, serverCertPEM, caCertBytes, err := CreateCerts("test-org", []string{"test-host"}, time.Now().AddDate(1, 0, 0))
assert.NoError(t, err)
p, _ := pem.Decode(sKey)
assert.Equal(t, "RSA PRIVATE KEY", p.Type)
key, err := x509.ParsePKCS1PrivateKey(p.Bytes)
assert.NoError(t, err)
err = key.Validate()
assert.NoError(t, err)
sCert, err := validCertificate(serverCertPEM, t)
assert.NoError(t, err)
caParsedCert, err := validCertificate(caCertBytes, t)
assert.NoError(t, err)
assert.Equal(t, "test-host", caParsedCert.DNSNames[0])
err = sCert.CheckSignatureFrom(caParsedCert)
assert.NoError(t, err)
})
}

func validCertificate(cert []byte, t *testing.T) (*x509.Certificate, error) {
t.Helper()
const certificate = "CERTIFICATE"
caCert, _ := pem.Decode(cert)
if caCert.Type != certificate {
return nil, errors.Errorf("CERT type mismatch, got %s, want: %s", caCert.Type, certificate)
}
parsedCert, err := x509.ParseCertificate(caCert.Bytes)
if err != nil {
return nil, errors.Wrap(err, "failed to parse cert")
}
if parsedCert.SignatureAlgorithm != x509.SHA256WithRSA {
return nil, errors.Errorf("signature not match. Got: %s, want: %s", parsedCert.SignatureAlgorithm, x509.SHA256WithRSA)
}
return parsedCert, nil
}
5 changes: 5 additions & 0 deletions controllers/eventbus/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ func (r *reconciler) reconcile(ctx context.Context, eventBus *v1alpha1.EventBus)
controllerutil.AddFinalizer(eventBus, finalizerName)

eventBus.Status.InitConditions()
if err := ValidateEventBus(eventBus); err != nil {
log.Errorw("validation failed", zap.Error(err))
eventBus.Status.MarkDeployFailed("InvalidSpec", err.Error())
return err
}
return installer.Install(eventBus, r.client, r.natsStreamingImage, r.natsMetricsImage, log)
}

Expand Down
5 changes: 4 additions & 1 deletion controllers/eventbus/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ var (
},
}

cID = "test-cluster-id"

exoticBus = &v1alpha1.EventBus{
TypeMeta: metav1.TypeMeta{
APIVersion: v1alpha1.SchemeGroupVersion.String(),
Expand All @@ -61,7 +63,8 @@ var (
Spec: v1alpha1.EventBusSpec{
NATS: &v1alpha1.NATSBus{
Exotic: &v1alpha1.NATSConfig{
URL: testURL,
ClusterID: &cID,
URL: testURL,
},
},
},
Expand Down
26 changes: 26 additions & 0 deletions controllers/eventbus/validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package eventbus

import (
"github.com/pkg/errors"

"github.com/argoproj/argo-events/pkg/apis/eventbus/v1alpha1"
)

// ValidateEventBus accepts an EventBus and performs validation against it
func ValidateEventBus(eb *v1alpha1.EventBus) error {
if eb.Spec.NATS != nil {
if eb.Spec.NATS.Native != nil && eb.Spec.NATS.Exotic != nil {
return errors.New("\"spec.nats.native\" and \"spec.nats.exotic\" can not be defined together")
}
if eb.Spec.NATS.Exotic != nil {
e := eb.Spec.NATS.Exotic
if e.ClusterID == nil {
return errors.New("\"spec.nats.exotic.clusterID\" is missing")
}
if e.URL == "" {
return errors.New("\"spec.nats.exotic.url\" is missing")
}
}
}
return nil
}
64 changes: 64 additions & 0 deletions controllers/eventbus/validate_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package eventbus

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/argoproj/argo-events/common"
"github.com/argoproj/argo-events/pkg/apis/eventbus/v1alpha1"
)

var (
testEventBus = &v1alpha1.EventBus{
ObjectMeta: metav1.ObjectMeta{
Namespace: "test-ns",
Name: common.DefaultEventBusName,
},
Spec: v1alpha1.EventBusSpec{
NATS: &v1alpha1.NATSBus{
Native: &v1alpha1.NativeStrategy{
Auth: &v1alpha1.AuthStrategyToken,
},
},
},
}
)

func TestValidate(t *testing.T) {
t.Run("test good eventbus", func(t *testing.T) {
err := ValidateEventBus(testEventBus)
assert.NoError(t, err)
})

t.Run("test native exotic conflicting eventbus", func(t *testing.T) {
eb := testEventBus.DeepCopy()
eb.Spec.NATS.Exotic = &v1alpha1.NATSConfig{}
err := ValidateEventBus(eb)
assert.Error(t, err)
assert.True(t, strings.Contains(err.Error(), "can not be defined together"))
})

t.Run("test exotic eventbus no clusterID", func(t *testing.T) {
eb := testEventBus.DeepCopy()
eb.Spec.NATS.Native = nil
eb.Spec.NATS.Exotic = &v1alpha1.NATSConfig{}
err := ValidateEventBus(eb)
assert.Error(t, err)
assert.True(t, strings.Contains(err.Error(), "\"spec.nats.exotic.clusterID\" is missing"))
})

t.Run("test exotic eventbus empty URL", func(t *testing.T) {
eb := testEventBus.DeepCopy()
eb.Spec.NATS.Native = nil
cID := "test-cluster-id"
eb.Spec.NATS.Exotic = &v1alpha1.NATSConfig{
ClusterID: &cID,
}
err := ValidateEventBus(eb)
assert.Error(t, err)
assert.True(t, strings.Contains(err.Error(), "\"spec.nats.exotic.url\" is missing"))
})
}
3 changes: 1 addition & 2 deletions controllers/eventsource/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,7 @@ func (r *reconciler) reconcile(ctx context.Context, eventSource *v1alpha1.EventS
controllerutil.AddFinalizer(eventSource, finalizerName)

eventSource.Status.InitConditions()
err := ValidateEventSource(eventSource)
if err != nil {
if err := ValidateEventSource(eventSource); err != nil {
log.Errorw("validation error", zap.Error(err))
return err
}
Expand Down
3 changes: 2 additions & 1 deletion controllers/eventsource/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

"github.com/argoproj/argo-events/common"
"github.com/argoproj/argo-events/common/logging"
eventbusv1alpha1 "github.com/argoproj/argo-events/pkg/apis/eventbus/v1alpha1"
"github.com/argoproj/argo-events/pkg/apis/eventsource/v1alpha1"
Expand Down Expand Up @@ -47,7 +48,7 @@ var (
},
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: "default",
Name: common.DefaultEventBusName,
},
Spec: eventbusv1alpha1.EventBusSpec{
NATS: &eventbusv1alpha1.NATSBus{
Expand Down
Loading

0 comments on commit 50257db

Please sign in to comment.