diff --git a/.gitignore b/.gitignore index 87aff2d9..8f76de9e 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,5 @@ controllers/junit.xml controllers/test-report.html bundle hack/scripts/docgen/node_modules -.devspace \ No newline at end of file +.devspace +certs \ No newline at end of file diff --git a/Makefile b/Makefile index 6b4304e3..23f56134 100644 --- a/Makefile +++ b/Makefile @@ -22,7 +22,7 @@ test: generate fmt vet manifests rm -f ./controllers/test-report.html ./controllers/junit.xml CNVRG_OPERATOR_MAX_CONCURRENT_RECONCILES=1 go test ./controllers/ -v -timeout 40m -`copctl-docker`: +copctl-docker: docker buildx build --platform=linux/amd64 --build-arg git_auth=$(GIT_AUTH) --push -t cnvrg/copctl:latest . test-report: diff --git a/charts/copctl-admission/templates/hook.yml b/charts/copctl-admission/templates/hook.yml deleted file mode 100644 index 1c780fff..00000000 --- a/charts/copctl-admission/templates/hook.yml +++ /dev/null @@ -1,21 +0,0 @@ -apiVersion: admissionregistration.k8s.io/v1 -kind: MutatingWebhookConfiguration -metadata: - name: "cnvrg-operator-admission.cnvrg-shim.svc" -webhooks: - - name: "cnvrg-operator-admission.cnvrg-shim.svc" - rules: - - apiGroups: [ "mlops.cnvrg.io" ] - apiVersions: [ "v1" ] - operations: [ "CREATE" ] - resources: [ "cnvrgapps" ] - scope: "Namespaced" - clientConfig: - service: - namespace: "cnvrg-shim" - name: "cnvrg-operator-admission" - path: "/aicloud/domain-discovery" - caBundle: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUU2RENDQXRDZ0F3SUJBZ0lRWFJVdGhBQ3B1T3JKTzVNQUZtYXZLREFOQmdrcWhraUc5dzBCQVFzRkFEQU4KTVFzd0NRWURWUVFERXdKallUQWdGdzB5TXpFeU1UZ3dOekUwTVRaYUdBOHlNRFV6TVRJeE9EQTNNalF4TmxvdwpEVEVMTUFrR0ExVUVBeE1DWTJFd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUUMyClB6T1RVY1UzRXN2cnFqejlmZHpVangyYmRZakZMd3pyRlgxTktIcFNyVzdyM2twbGd3SklzZWYwQ3pqUWIwNGoKcHJLQllvWVdRQUVKT0ZRd28vL1Ixa25FVmt1LzZCMUdUWmNXem1UTVVaOUlUdTVKUGtDRWpOQzIvdk5pSkNkVwpwOEwyMDhBZHRaeDlCazAwYkRmSWVYWHdZV2J2Qko1dTZrRkFKbmdaZXNnRWdUMFVDcXd6MmVVSXFCdXRTdWphCjVPVDNIZWZKTjdQZ0dueWdENlFFMmVxUnBDWGs0eE1qTEVwNktXTW10NENuai9ERTRzRU5SZjZwWWVEZ1MyMXkKcG5WYU9YdFplTXYxZnhEdWN3SzkrMTJsdWlRRitqRkk0VTFiQWpSQ1gwZmpsUU9nQWQ3Y3BZM2lycHFFeWFrbgpyNi9vUVlpTllYSERLQzdMRk9hTlNhaStoWW5YZmtCa1VhYXpIT0hVSXJMY1BlVmQ0L0dqLyswdlJYMGxrczJVCjEreDNxUGdpZDBEa1RwWnBHY0RoYmJ5eGp5RHF2akh5QTBuRmsrcGN4MzZwYVEreVIvd2IrVUtlVjRPQzdjVk4KTGh3UnZ3TXlTWTY3eHN5U2tDV3IzTFVNbTV2cTM3TzlLb25ZMGNwTkRqMmRWMzRwc2VMRjJwQ1JCUllpQnhtZgp4UC9oYjVBZ2RMSWVLajVQb0EvNHZ3UnB5YVd5bDRBN0JWTytlNjBvaVhScis5QmpBWm14ZFBScDNtSlpyWmh4CkphcVNUeC9kSkxDTWdLTFI2bkR4UFVRUmR2cWZ2UG81VjZtUWlFZDRWcklXdU1hS2J3anZTN3AzZ0xKNFhVREwKTHpyUmlFYXlzV3EyZ1RMdVB3MnAvNHliWkRIVTFEZ3I1aWR0ck4xNXNRSURBUUFCbzBJd1FEQU9CZ05WSFE4QgpBZjhFQkFNQ0FxUXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVhbzRYQWp6ZVdvS0RGaU02CjRpVlgvYkdsM0ZVd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFCdUVhdC91MGljVmo0aGhCbGN3dnhqRTVxSUwKNERUNXQzZVM3ZzV2Z3haNllkZEFjNEl1bFVESzdyb3RWM1QrdnNNK0QzY3NuanJlWjNtMVZuYlRMU1Z2YWhteQpVNTR3MGd0eTM3Y3ZIbG12OVJiNUlENkNERlMvUkFqcmQ0a2NEditBOTNqQ0NxWUcyTGNzKzlDSWt4R2tzU0d1CmFIUGRYZ3lwN0Z4SXZmTXFxS1pJemwvazJXNzRnbEdJdnRER2pJMnVzdVZZNzFmUFhOaUZaZys3UndIZ2pUeGEKOHlDQS91R2ZISy9OZUhKL2hRUW5FNzhIYVlTY3FVWEE1cmcrbVF0aG5EeW1EdFNsbGpKWDVlL294NHlnSU00UAppQ2FENjVEOWVlNDFuck5PZW1McmVIZXVxY0JIa2RIc05rRlYzd3NSclkwMUt1Skp6TWx4UFZFNjN2bk41MGdaCjBwUG1RYkV3S3FLakhJVE0zTkpKdG4zZTZHQW9EdytLdmRMSlFUK1RhMkpjT294WFBCTUJIdGlVOGFpT3EwNnoKZ0JBK2doYjY1aEg2YnUxRGpsSTJtSCtmbytWODArY2hBUXZQUzV5SCtlNzdtenYwWGgwMTNlZEh0bFJQNXZ0cgovSkVCaDJZbjFIUGY5alVVS01mK1NUZjA0a1VPd0xSY1ZzT0FROXhnSVJmZ2syMnFLVTV4ckZhWmZHYVRXTGZrCnhRUDErTXd0R1N3bCtkTmNnUGVUQXhacXMxS0toS0JaY0xKYllrZ1VMZk5TMTVERW9OQS9qSml6bHlYeGdwTEMKUGQxdEdFQW9FbXk1bzE3SUszM25iM004TEpUWnEvNWd2Vk5HSExFaU1pZzRmR3RFQkcwUDhuOU40Ym9XWTRCdgpJVDRENG82N0MrR3FlcmNVCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KSSBoYXZlIG5vIG5hbWUhQGNudnJnLWF1dGgtcHJveHktNzRiNTRjOWZmOS04MnRrbDovb3B0L2FwcC1yb290JCBYWHdZV2J2Qko1dTZrRkFKbmdaZXNnRWdUMFVDcXd6MmVVSXFCdXRTdWphCjVPVDNIZWZKTjdQZ0dueWdENlFFMmVxUnBDWGs0eE1qTEVwNktXTW10NENuai9ERTRzRU5SZjZwWWVEZ1MyMXkKcG5WYU9YdFplTXYxZnhEdWN3SzkrMTJsdWlRRitqRkk0VTFiQWpSQ1gwZmpsUU9nQWQ3Y3BZM2lycHFFeWFrbgpyNi9vUVlpTllYSERLQzdMRk9hTlNhaStoWW5YZmtCa1VhYXpIT0hVSXJMY1BlVmQ0L0dqLyswdlJYMGxrczJVCjEreDNxUGdpZDBEa1RwWnBHY0RoYmJ5eGp5RHF2akh5QTBuRmsrcGN4MzZwYVEreVIvd2IrVUtlVjRPQzdjVk4KTGh3UnZ3TXlTWTY3eHN5U2tDV3IzTFVNbTV2cTM3TzlLb25ZMGNwTkRqMmRWMzRwc2VMRjJwQ1JCUllpQnhtZgp4UC9oYjVBZ2RMSWVLajVQb0EvNHZ3UnB5YVd5bDRBN0JWTytlNjBvaVhScis5QmpBWm14ZFBScDNtSlpyWmh4CkphcVNUeC9kSkxDTWdLTFI2bkR4UFVRUmR2cWZ2UG81VjZtUWlFZDRWcklXdU1hS2J3anZTN3AzZ0xKNFhVREwKTHpyUmlFYXlzV3EyZ1RMdVB3MnAvNHliWkRIVTFEZ3I1aWR0ck4xNXNRSURBUUFCbzBJd1FEQU9CZ05WSFE4QgpBZjhFQkFNQ0FxUXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVhbzRYQWp6ZVdvS0RGaU02CjRpVlgvYkdsM0ZVd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFCdUVhdC91MGljVmo0aGhCbGN3dnhqRTVxSUwKNERUNXQzZVM3ZzV2Z3haNllkZEFjNEl1bFVESzdyb3RWM1QrdnNNK0QzY3NuanJlWjNtMVZuYlRMU1Z2YWhteQpVNTR3MGd0eTM3Y3ZIbG12OVJiNUlENkNERlMvUkFqcmQ0a2NEditBOTNqQ0NxWUcyTGNzKzlDSWt4R2tzU0d1CmFIUGRYZ3lwN0Z4SXZmTXFxS1pJemwvazJXNzRnbEdJdnRER2pJMnVzdVZZNzFmUFhOaUZaZys3UndIZ2pUeGEKOHlDQS91R2ZISy9OZUhKL2hRUW5FNzhIYVlTY3FVWEE1cmcrbVF0aG5EeW1EdFNsbGpKWDVlL294NHlnSU00UAppQ2FENjVEOWVlNDFuck5PZW1McmVIZXVxY0JIa2RIc05rRlYzd3NSclkwMUt1Skp6TWx4UFZFNjN2bk41MGdaCjBwUG1RYkV3S3FLakhJVE0zTkpKdG4zZTZHQW9EdytLdmRMSlFUK1RhMkpjT294WFBCTUJIdGlVOGFpT3EwNnoKZ0JBK2doYjY1aEg2YnUxRGpsSTJtSCtmbytWODArY2hBUXZQUzV5SCtlNzdtenYwWGgwMTNlZEh0bFJQNXZ0cgovSkVCaDJZbjFIUGY5alVVS01mK1NUZjA0a1VPd0xSY1ZzT0FROXhnSVJmZ2syMnFLVTV4ckZhWmZHYVRXTGZrCnhRUDErTXd0R1N3bCtkTmNnUGVUQXhacXMxS0toS0JaY0xKYllrZ1VMZk5TMTVERW9OQS9qSml6bHlYeGdwTEMKUGQxdEdFQW9FbXk1bzE3SUszM25iM004TEpUWnEvNWd2Vk5HSExFaU1pZzRmR3RFQkcwUDhuOU40Ym9XWTRCdgpJVDRENG82N0MrR3FlcmNVCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=" - admissionReviewVersions: [ "v1" ] - sideEffects: None - timeoutSeconds: 5 \ No newline at end of file diff --git a/cmd/copctl/cmd/create/utils.go b/cmd/copctl/cmd/create/utils.go index 3bd99241..5e907cdb 100644 --- a/cmd/copctl/cmd/create/utils.go +++ b/cmd/copctl/cmd/create/utils.go @@ -6,7 +6,6 @@ import ( "go.uber.org/zap" "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/client/config" - "strings" ) func clientset() *kubernetes.Clientset { @@ -29,11 +28,3 @@ func privateKey() *rsa.PrivateKey { } return privKey } - -func commonNameToNsAndSvc(commonName string) (ns string, svc string) { - endpoint := strings.Split(commonName, ".") - if len(endpoint) < 3 { - zap.S().Error("wrong common name, expected format: ..svc ") - } - return endpoint[1], endpoint[0] -} diff --git a/cmd/copctl/cmd/create/webhook.go b/cmd/copctl/cmd/create/webhook.go index 63089210..b4b4a711 100644 --- a/cmd/copctl/cmd/create/webhook.go +++ b/cmd/copctl/cmd/create/webhook.go @@ -17,6 +17,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "math/big" "os" + "strings" "time" ) @@ -40,30 +41,64 @@ var webhookCmd = &cobra.Command{ Aliases: []string{"w"}, Short: "Generate certs for Admission Webhook", Run: func(cmd *cobra.Command, args []string) { - commonName := viper.GetString("common-name") - certsDir := viper.GetString("certs-dir") - // re-create the cert folder - if viper.GetBool("override") { - clean(certsDir, commonName) - } - // get key for CA - cakey := privateKey() - // create CA certificate - ca, caPEM := createCA(cakey) - // create certificate and key for server - crt, key := serverCrtAndKey(commonName, ca, cakey) - // create mutation webhook configuration - ns, svc := commonNameToNsAndSvc(commonName) - createMutatingWebhookCfg( - admission.NewAICloudDomainHandler(). - HookCfg(ns, svc, caPEM.Bytes()), - ) - // dump certificate to disc - dumpToDisk(caPEM, crt, key, certsDir) + NewWebhook( + viper.GetString("common-name"), + viper.GetString("certs-dir"), + viper.GetBool("override"), + ).run() }, } -func createCA(key *rsa.PrivateKey) (ca *x509.Certificate, caPEM *bytes.Buffer) { +type Webhook struct { + CommonName string + CertsDir string + Override bool +} + +func NewWebhook(commonName, certsDir string, override bool) *Webhook { + return &Webhook{ + CommonName: commonName, + CertsDir: certsDir, + Override: override, + } +} + +func (h *Webhook) run() { + // re-create the cert folder + if h.Override { + h.clean() + } + // get key for CA + cakey := privateKey() + // create CA certificate + ca, caPEM := h.createCA(cakey) + // create certificate and key for server + crt, key := h.serverCrtAndKey(ca, cakey) + // create mutation webhook configuration + ns, svc := h.commonNameToNsAndSvc() + h.createMutatingWebhookCfg( + admission.NewAICloudDomainHandler(). + HookCfg(ns, svc, caPEM.Bytes()), + ) + // dump certificate to disc + h.dumpToDisk(caPEM, crt, key) +} + +func (h *Webhook) commonNameToNsAndSvc() (ns string, svc string) { + endpoint := strings.Split(h.CommonName, ".") + if len(endpoint) < 3 { + zap.S().Error("wrong common name, expected format: ..svc ") + } + return endpoint[1], endpoint[0] +} + +func (h *Webhook) clean() { + if err := os.RemoveAll(h.CertsDir); err != nil { + zap.S().Error(err) + } +} + +func (h *Webhook) createCA(key *rsa.PrivateKey) (ca *x509.Certificate, caPEM *bytes.Buffer) { ca = &x509.Certificate{ SerialNumber: big.NewInt(1658), Subject: pkix.Name{ @@ -92,14 +127,14 @@ func createCA(key *rsa.PrivateKey) (ca *x509.Certificate, caPEM *bytes.Buffer) { return ca, caPEM } -func serverCrtAndKey(commonName string, ca *x509.Certificate, cakey *rsa.PrivateKey) (serverCrtPEM *bytes.Buffer, serverKeyPEM *bytes.Buffer) { +func (h *Webhook) serverCrtAndKey(ca *x509.Certificate, cakey *rsa.PrivateKey) (serverCrtPEM *bytes.Buffer, serverKeyPEM *bytes.Buffer) { serverCrt := &x509.Certificate{ SerialNumber: big.NewInt(1658), Subject: pkix.Name{ - CommonName: commonName, + CommonName: h.CommonName, }, - DNSNames: []string{commonName}, + DNSNames: []string{h.CommonName}, NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, @@ -131,34 +166,26 @@ func serverCrtAndKey(commonName string, ca *x509.Certificate, cakey *rsa.Private return } -func dumpToDisk(caCrt, serverCrt, serverKey *bytes.Buffer, certsDir string) { +func (h *Webhook) dumpToDisk(caCrt, serverCrt, serverKey *bytes.Buffer) { // create dir for cert and key - if err := os.MkdirAll(certsDir, 0755); err != nil { + if err := os.MkdirAll(h.CertsDir, 0755); err != nil { zap.S().Fatal(err) } // dump key to file - if err := os.WriteFile(certsDir+"/ca.crt", caCrt.Bytes(), 0644); err != nil { + if err := os.WriteFile(h.CertsDir+"/ca.crt", caCrt.Bytes(), 0644); err != nil { zap.S().Fatal(err) } - if err := os.WriteFile(certsDir+"/server.crt", serverCrt.Bytes(), 0644); err != nil { + if err := os.WriteFile(h.CertsDir+"/server.crt", serverCrt.Bytes(), 0644); err != nil { zap.S().Fatal(err) } - if err := os.WriteFile(certsDir+"/server.key", serverKey.Bytes(), 0644); err != nil { + if err := os.WriteFile(h.CertsDir+"/server.key", serverKey.Bytes(), 0644); err != nil { zap.S().Fatal(err) } } -func createAICloudDomainWebhookCfg(ns, svc string, caBundle []byte) { - zap.S().Infof("creating ai cloud domain mutation webhook") - createMutatingWebhookCfg( - admission.NewAICloudDomainHandler(). - HookCfg(ns, svc, caBundle), - ) -} - -func createMutatingWebhookCfg(hookCfg *admissionv1.MutatingWebhookConfiguration) { +func (h *Webhook) createMutatingWebhookCfg(hookCfg *admissionv1.MutatingWebhookConfiguration) { zap.S().Infof("creating webhook: %s", hookCfg.Name) err := clientset().