Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: [experimental] create TLS certificate via code & save it in a PersistentVolume #421

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions charts/datree-admission-webhook/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
@@ -8,6 +8,13 @@ metadata:
annotations: {{ toYaml . | nindent 4 }}
{{- end }}
rules:
- apiGroups:
- "admissionregistration.k8s.io"
resources:
- "validatingwebhookconfigurations"
verbs:
- "get"
- "update"
- apiGroups:
- "user.openshift.io"
resources:
25 changes: 19 additions & 6 deletions charts/datree-admission-webhook/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{{- $emptyTokenMessage := "\n\t❌ The installation failed since the token that was provided is invalid.\n\t💡 To fix this issue:\n\t\t1. Get your token at: https://app.datree.io/settings/token-management\n\t\t2. Reinstall Datree and set your token using helm:\n\n\t\thelm install -n datree datree-webhook datree-webhook/datree-admission-webhook --create-namespace --set datree.token=<YOUR_TOKEN>\n" -}}
{{- $uuidv4RegexPattern := "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$" -}}
{{- $webhookCertsVolumeName := "webhook-certs-volume" -}}
{{- $webhookCertsVolumeClaimName := "webhook-certs-volume-claim" -}}
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -106,16 +108,15 @@ spec:
- containerPort: 5555
name: debug
volumeMounts:
- name: webhook-tls-certs
mountPath: /run/secrets/tls
readOnly: true
- mountPath: /etc/webhook-certs
name: {{ $webhookCertsVolumeName }}
- name: webhook-config
mountPath: /config
readOnly: true
volumes:
- name: webhook-tls-certs
secret:
secretName: webhook-server-tls
- name: {{ $webhookCertsVolumeName }}
persistentVolumeClaim:
claimName: {{ $webhookCertsVolumeClaimName }}
- name: webhook-config
projected:
sources:
@@ -125,3 +126,15 @@ spec:
- configMap:
name: webhook-scanning-filters
optional: true
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ $webhookCertsVolumeClaimName }}
namespace: {{ template "datree.namespace" . }}
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,23 +1,4 @@
{{- $ca := genCA "/CN=Admission Controller Webhook Demo CA" 1827 -}}
{{- $svcHost := printf "datree-webhook-server.%s.svc" ( include "datree.namespace" . ) -}}
{{- $altNames := list ( $svcHost ) -}}
{{- $cert := genSignedCert (printf "/CN=%s" $svcHost) nil $altNames 1827 $ca -}}
apiVersion: v1
kind: Secret
metadata:
name: webhook-server-tls
labels: {{ include "datree.labels" . | nindent 4 }}
namespace: {{ template "datree.namespace" . }}
annotations:
self-signed-cert: "true"
{{- with .Values.customAnnotations }}
{{ toYaml . }}
{{- end }}
type: kubernetes.io/tls
data:
tls.key: {{ $cert.Key | b64enc }}
tls.crt: {{ $cert.Cert | b64enc }}
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
@@ -45,11 +26,13 @@ webhooks:
name: datree-webhook-server
namespace: {{ template "datree.namespace" . }}
path: "/validate"
caBundle: {{ $ca.Cert | b64enc }}
caBundle: {{ "PLACEHOLDER" | b64enc }}
namespaceSelector:
matchExpressions:
- key: admission.datree/validate
operator: DoesNotExist
- key: admission.datree/validate
operator: Exists # the label can't exist AND not exist simultaneously, therefore the webhook will never be invoked until we delete this expression
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: ["*"]
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -7,8 +7,10 @@ require (
github.com/ghodss/yaml v1.0.0
github.com/google/go-cmp v0.5.9
github.com/lithammer/shortuuid v3.0.0+incompatible
github.com/openshift/api v0.0.0-20230705144233-e28cd4dd28a8
github.com/openshift/client-go v0.0.0-20230705133330-7f808ad59404
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1
go.uber.org/zap v1.10.0
k8s.io/api v0.27.2
@@ -33,7 +35,6 @@ require (
github.com/jinzhu/copier v0.3.5 // indirect
github.com/mikefarah/yq/v4 v4.27.3 // indirect
github.com/open-policy-agent/opa v0.49.2 // indirect
github.com/openshift/api v0.0.0-20230705144233-e28cd4dd28a8 // indirect
github.com/owenrumney/go-sarif/v2 v2.1.2 // indirect
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -226,6 +226,7 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H
github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJQoJA=
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
@@ -327,6 +328,7 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2 changes: 1 addition & 1 deletion internal/fixtures/values.dev.yaml
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@
# The name of the namespace all resources will be created in, if not specified in the release.
namespace: ""
# The number of Datree webhook-server replicas to deploy for the webhook.
replicaCount: 1
replicaCount: 2
# Additional labels to add to all resources.
customLabels: { }
# Additional annotations to add to all resources.
17 changes: 14 additions & 3 deletions internal/startup/startup.go
Original file line number Diff line number Diff line change
@@ -3,6 +3,8 @@ package startup
import (
"errors"
"fmt"
cert_manager "github.com/datreeio/admission-webhook-datree/pkg/cert-manager"
"github.com/datreeio/admission-webhook-datree/pkg/k8sClient2"
"github.com/datreeio/admission-webhook-datree/pkg/openshiftService"

"net/http"
@@ -87,9 +89,18 @@ func Start() {
if err != nil {
fmt.Printf("Failed init skip list: %s \n", err.Error())
}
certPath, keyPath, err := server.ValidateCertificate()

err = cert_manager.GenerateCertificatesIfTheyAreMissing()
if err != nil {
fmt.Printf("Failed to generate certificates: %s \n", err.Error())
}
k8sClient2Instance, err := k8sClient2.NewK8sClient()
if err != nil {
fmt.Printf("Failed to create k8s client: %s \n", err.Error())
}
err = k8sClient2Instance.ActivateValidatingWebhookConfiguration()
if err != nil {
panic(err)
fmt.Printf("Failed to activate validating webhook configuration: %s \n", err.Error())
}

validationController := controllers.NewValidationController(basicCliClient, state, errorReporter, k8sMetadataUtilInstance, &internalLogger, openshiftServiceInstance)
@@ -105,7 +116,7 @@ func Start() {
internalLogger.LogInfo(fmt.Sprintf("server starting in webhook-version: %s", config.WebhookVersion))

// start server
if err := http.ListenAndServeTLS(":"+port, certPath, keyPath, nil); err != nil {
if err := http.ListenAndServeTLS(":"+port, cert_manager.TlsCertPath, cert_manager.TlsKeyPath, nil); err != nil {
err = http.ListenAndServe(":"+port, nil)
if err != nil {
fmt.Println("Failed to start http server", err.Error())
155 changes: 155 additions & 0 deletions pkg/cert-manager/cert-manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package cert_manager

import (
"bytes"
cryptorand "crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"errors"
"fmt"
log "github.com/sirupsen/logrus"
"math/big"
"os"
"time"
)

const certsFolder = "/etc/webhook-certs"
const TlsCertPath = certsFolder + "/tls.crt"
const TlsKeyPath = certsFolder + "/tls.key"
const CaCertPath = certsFolder + "/ca.crt"

type AllCertificates struct {
Cert []byte
Key []byte
CaCert []byte
CaKey []byte
}

func GenerateCertificatesIfTheyAreMissing() error {
if !doCertificatesExist() {
generateCertificates()
}
return nil
}

func doCertificatesExist() bool {
doesFileExist := func(filePath string) bool {
if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) {
return false
}
return true
}

return doesFileExist(TlsCertPath) && doesFileExist(TlsKeyPath) && doesFileExist(CaCertPath)
}

func generateCertificates() {
var caPEM, serverCertPEM, serverPrivKeyPEM *bytes.Buffer
// CA config
ca := &x509.Certificate{
SerialNumber: big.NewInt(2020),
Subject: pkix.Name{
Organization: []string{"/CN=Datree Admission Controller Webhook CA"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(5, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}

// CA private key
caPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 4096)
if err != nil {
fmt.Println(err)
}

// Self signed CA certificate
caBytes, err := x509.CreateCertificate(cryptorand.Reader, ca, ca, &caPrivKey.PublicKey, caPrivKey)
if err != nil {
fmt.Println(err)
}

// PEM encode CA cert
caPEM = new(bytes.Buffer)
_ = pem.Encode(caPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: caBytes,
})

webhookDNS := "datree-webhook-server.datree.svc" // TODO use namespace from config

// server cert config
cert := &x509.Certificate{
DNSNames: []string{webhookDNS},
SerialNumber: big.NewInt(1658),
Subject: pkix.Name{
CommonName: fmt.Sprintf("/CN=%v", webhookDNS),
Organization: []string{"/CN=Datree Admission Controller Webhook CA"},
},
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(5, 0, 0),
SubjectKeyId: []byte{1, 2, 3, 4, 6},
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature,
}

// server private key
serverPrivKey, err := rsa.GenerateKey(cryptorand.Reader, 4096)
if err != nil {
fmt.Println(err)
}

// sign the server cert
serverCertBytes, err := x509.CreateCertificate(cryptorand.Reader, cert, ca, &serverPrivKey.PublicKey, caPrivKey)
if err != nil {
fmt.Println(err)
}

// PEM encode the server cert and key
serverCertPEM = new(bytes.Buffer)
_ = pem.Encode(serverCertPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: serverCertBytes,
})

serverPrivKeyPEM = new(bytes.Buffer)
_ = pem.Encode(serverPrivKeyPEM, &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(serverPrivKey),
})

err = writeFile(CaCertPath, caPEM)
if err != nil {
log.Panic(err)
}

err = writeFile(TlsCertPath, serverCertPEM)
if err != nil {
log.Panic(err)
}

err = writeFile(TlsKeyPath, serverPrivKeyPEM)
if err != nil {
log.Panic(err)
}

}

// writeFile writes data in the file at the given path
func writeFile(filepath string, sCert *bytes.Buffer) error {
f, err := os.Create(filepath)
if err != nil {
return err
}
defer f.Close()

_, err = f.Write(sCert.Bytes())
if err != nil {
return err
}
return nil
}
Loading
Oops, something went wrong.