diff --git a/README.md b/README.md index 797e3f27..fd24b355 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ KubePlus takes an application Helm chart and wraps it under a Kubernetes API (CR ### Isolation -KubePlus takes an application Helm chart and wraps it in a Kubernetes API (CRD). This API is used to provision application instances on a cluster. KubePlus isolates each application instance in a separate namespace. It adds a safety perimeter around such namespaces using Kubernetes network policies and non-shared persistent volumes ensuring that each application instance is appropriately isolated from other instances. Additionally, it provides controls for application providers to deploy different tenant application instances on different worker nodes for node isolation. +KubePlus takes an application Helm chart and wraps it in a Kubernetes API (CRD). This API is used to provision application instances on a cluster. KubePlus isolates each application instance in a separate namespace. It adds a safety perimeter around such namespaces using Kubernetes network policies and non-shared persistent volumes ensuring that each application instance is appropriately isolated from other instances. + ### Security The KubePlus Operator does not need any admin-level permissions on a cluster for application providers. This allows application providers to offer their managed services on any K8s clusters including those owned by their customers. KubePlus comes with a small utility that allows you to create provider specific kubeconfig on a cluster in order to enable application deployments and management. Providers have an ability to create a consumer specific further limited kubeconfig to allow for self-service provisioning of application instances as well. @@ -203,4 +204,4 @@ Please join us in our meetings. Your participation is welcome. Subscribe to [KubePlus mailing list](https://groups.google.com/g/kubeplus). Join #kubeplus channel on [CNCF Slack](https://cloud-native.slack.com/archives/C06U6MP24PN). -If you don't have an account on the CNCF workspace, get your invitation [here](https://communityinviter.com/apps/cloud-native/cncf). You can join the `#kubeplus` channel once your invitation is active. \ No newline at end of file +If you don't have an account on the CNCF workspace, get your invitation [here](https://communityinviter.com/apps/cloud-native/cncf). You can join the `#kubeplus` channel once your invitation is active. diff --git a/deploy/kubeplus-chart/Chart.yaml b/deploy/kubeplus-chart/Chart.yaml index b84cd1dd..0d79dfeb 100644 --- a/deploy/kubeplus-chart/Chart.yaml +++ b/deploy/kubeplus-chart/Chart.yaml @@ -7,7 +7,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 4.0.0 +version: 4.0.1 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/deploy/kubeplus-chart/values.yaml b/deploy/kubeplus-chart/values.yaml index dff64626..b7773614 100644 --- a/deploy/kubeplus-chart/values.yaml +++ b/deploy/kubeplus-chart/values.yaml @@ -3,7 +3,7 @@ CHECK_KYVERNO_POLICIES: NO # Containers WEBHOOK_INIT_CONTAINER: gcr.io/cloudark-kubeplus/webhook-tls-getter:3.0.28 CRD_REGISTRATION_HELPER: gcr.io/cloudark-kubeplus/kubeconfiggenerator:3.0.29 -MUTATING_WEBHOOK: gcr.io/cloudark-kubeplus/pac-mutating-admission-webhook:3.0.17 +MUTATING_WEBHOOK: gcr.io/cloudark-kubeplus/pac-mutating-admission-webhook:3.0.18 PLATFORM_OPERATOR: gcr.io/cloudark-kubeplus/platform-operator:3.0.7 CONSUMERUI: gcr.io/cloudark-kubeplus/consumerui:0.0.7 HELMER: gcr.io/cloudark-kubeplus/helm-pod:3.0.22 diff --git a/mutating-webhook/go.mod b/mutating-webhook/go.mod index 951cacf6..70e29d88 100644 --- a/mutating-webhook/go.mod +++ b/mutating-webhook/go.mod @@ -25,6 +25,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/gnostic v0.5.7-v3refs // indirect github.com/google/gofuzz v1.1.0 // indirect + github.com/googleapis/gnostic v0.4.0 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect diff --git a/mutating-webhook/go.sum b/mutating-webhook/go.sum index 96931816..6e8265af 100644 --- a/mutating-webhook/go.sum +++ b/mutating-webhook/go.sum @@ -240,6 +240,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gnostic v0.4.0 h1:BXDUo8p/DaxC+4FJY/SSx3gvnx9C1VdHNgaUkiEL5mk= +github.com/googleapis/gnostic v0.4.0/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= diff --git a/mutating-webhook/main.go b/mutating-webhook/main.go index 92c4f490..d3d11eec 100644 --- a/mutating-webhook/main.go +++ b/mutating-webhook/main.go @@ -43,9 +43,40 @@ func main() { whsvr := &WebhookServer{ server: &http.Server{ Addr: fmt.Sprintf(":%v", parameters.port), - TLSConfig: &tls.Config{Certificates: []tls.Certificate{pair}}, - }, - // client: kubeClient, + TLSConfig: &tls.Config{MinVersion: tls.VersionTLS12, + MaxVersion: tls.VersionTLS12, + Certificates: []tls.Certificate{pair}, + CipherSuites: []uint16{ + // TLS 1.0 - 1.2 cipher suites. + tls.TLS_RSA_WITH_RC4_128_SHA, + tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_RSA_WITH_AES_128_CBC_SHA256, + tls.TLS_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA, + tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + // TLS 1.3 cipher suites. + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + tls.TLS_CHACHA20_POLY1305_SHA256, + }, + }, + }, } // define http server and server handler diff --git a/mutating-webhook/versions.txt b/mutating-webhook/versions.txt index 1f9aea12..3db33a3e 100644 --- a/mutating-webhook/versions.txt +++ b/mutating-webhook/versions.txt @@ -29,3 +29,4 @@ 3.0.15 3.0.16 3.0.17 +3.0.18 diff --git a/mutating-webhook/webhook.go b/mutating-webhook/webhook.go index 55d656df..7d4f9f7f 100644 --- a/mutating-webhook/webhook.go +++ b/mutating-webhook/webhook.go @@ -271,6 +271,8 @@ func (whsvr *WebhookServer) mutate(ar *v1.AdmissionReview, httpMethod string) *v return errResponse } + /* + // Deprecating support for Pod-level policies. if req.Kind.Kind == "Pod" { customAPI, rootKind, rootName, rootNamespace := checkServiceLevelPolicyApplicability(ar) var podResourcePatches []patchOperation @@ -303,6 +305,7 @@ func (whsvr *WebhookServer) mutate(ar *v1.AdmissionReview, httpMethod string) *v patchOperations = append(patchOperations, podPatch) } } + */ if req.Kind.Kind == "Namespace" { @@ -368,7 +371,7 @@ func handleDelete(ar *v1.AdmissionReview) *v1.AdmissionResponse { //fmt.Printf("Body:%v\n", body) apiv1 := req.Kind apiv2 := req.Resource - fmt.Printf("&&&&&\n") + //fmt.Printf("&&&&&\n") fmt.Printf("APIv1:%s, APIv2:%s\n", apiv1, apiv2) group := req.Resource.Group version := req.Resource.Version @@ -510,9 +513,9 @@ func checkAndApplyNSPolicies(ar *v1.AdmissionReview) []patchOperation { // come from arSaved. podNamespace := req.Namespace - fmt.Printf("Pod Namespace:%s\n", podNamespace) + //fmt.Printf("Pod Namespace:%s\n", podNamespace) releaseName := namespaceHelmAnnotationMap[podNamespace] - fmt.Printf("Release Name:%s\n", releaseName) + //fmt.Printf("Release Name:%s\n", releaseName) customAPI := "" serviceKind := "" @@ -610,10 +613,10 @@ func saveResourcePolicy(ar *v1.AdmissionReview) { */ var resourcePolicy platformworkflowv1alpha1.ResourcePolicy - err = json.Unmarshal(body, &resourcePolicy) - if err != nil { + json.Unmarshal(body, &resourcePolicy) + /*if err != nil { fmt.Println(err) - } + }*/ kind := resourcePolicy.Spec.Resource.Kind lowercaseKind := strings.ToLower(kind) @@ -681,12 +684,12 @@ func checkServiceLevelPolicyApplicability(ar *v1.AdmissionReview) (string, strin //fmt.Printf("All Annotations:%v\n", annotations1) } releaseName := annotations1["meta.helm.sh/release-name"] - fmt.Printf("Helm release name:%s\n", releaseName) + //fmt.Printf("Helm release name:%s\n", releaseName) capiGroup := "" capiVersion := "" rootKind, rootName, capiGroup, capiVersion = getAPIDetailsFromHelmReleaseAnnotation(releaseName) rootAPIVersion = capiGroup + "/" + capiVersion - fmt.Printf("RK:%s, RN:%s, RAPI:%s\n", rootKind, rootName, rootAPIVersion) + //fmt.Printf("RK:%s, RN:%s, RAPI:%s\n", rootKind, rootName, rootAPIVersion) } else { rootKind, rootName, rootAPIVersion = findRoot(namespace, ownerKindS, ownerNameS, ownerAPIVersionS) } @@ -754,8 +757,8 @@ func findRoot(namespace, kind, name, apiVersion string) (string, string, string) name, metav1.GetOptions{}) if err2 != nil { - fmt.Printf("Error 2:%v\n", err2) - fmt.Println(err2) + //fmt.Printf("Error 2:%v\n", err2) + //fmt.Println(err2) return rootKind, rootName, rootAPIVersion } @@ -822,8 +825,8 @@ func getAPIDetailsFromHelmReleaseAnnotation(releaseName string) (string, string, } func applyPolicies(ar *v1.AdmissionReview, customAPI, rootKind, rootName, rootNamespace string) []patchOperation { - req := ar.Request - body := req.Object.Raw + //req := ar.Request + //body := req.Object.Raw /* podName, _ := jsonparser.GetUnsafeString(req.Object.Raw, "metadata", "name") @@ -839,12 +842,11 @@ func applyPolicies(ar *v1.AdmissionReview, customAPI, rootKind, rootName, rootNa }*/ // TODO: Defaulting to the first container. Take input for additional containers + /* _, _, _, err1 := jsonparser.Get(body, "spec", "containers", "[0]", "resources") if err1 != nil { fmt.Printf("Error:%v\n", err1) - } /*else { - //fmt.Printf("Resources:%v\n", string(res)) - }*/ + } */ var operation string //fmt.Printf("DataType:%s\n", dataType) @@ -880,6 +882,7 @@ func applyPolicies(ar *v1.AdmissionReview, customAPI, rootKind, rootName, rootNa podResRequest["cpu"] = cpuRequest podResRequest["memory"] = memRequest + //TODO: Defaulting to the first container. Take input for additional containers patch1 := patchOperation{ Op: operation, Path: "/spec/containers/0/resources/requests", @@ -899,6 +902,7 @@ func applyPolicies(ar *v1.AdmissionReview, customAPI, rootKind, rootName, rootNa podResLimits["cpu"] = cpuLimit podResLimits["memory"] = memLimit + //TODO: Defaulting to the first container. Take input for additional containers patch2 := patchOperation{ Op: operation, Path: "/spec/containers/0/resources/limits", @@ -939,12 +943,21 @@ func getFieldValueFromInstance(fieldName, rootKind, rootName string) string { //rootkey := lowercaseRootKind + "/" + rootNamespace + "/" + rootName rootkey := lowercaseRootKind + "-" + rootName //fmt.Printf("Root Key:%s\n", rootkey) - arSaved := resourceNameObjMap[rootkey].(*v1.AdmissionReview) - reqObject := arSaved.Request - reqspec := reqObject.Object.Raw - - fieldValue, _, _, _ := jsonparser.Get(reqspec, "spec", field) - fieldValueS := string(fieldValue) + /*fmt.Printf("Printing resourceNameObjMap -- \n") + for key, value := range resourceNameObjMap { + fmt.Println(key, ":", value) + } + fmt.Printf("--\n")*/ + + val := resourceNameObjMap[rootkey] + fieldValueS := "" + if val != nil { + arSaved := resourceNameObjMap[rootkey].(*v1.AdmissionReview) + reqObject := arSaved.Request + reqspec := reqObject.Object.Raw + fieldValue, _, _, _ := jsonparser.Get(reqspec, "spec", field) + fieldValueS = string(fieldValue) + } /*if err2 != nil { fmt.Printf("Error:%v\n", err2) } else { @@ -972,18 +985,19 @@ func getObjectDetails(ar *v1.AdmissionReview) (string, string, string) { } func trackCustomAPIs(ar *v1.AdmissionReview) *v1.AdmissionResponse { + fmt.Printf("Inside trackCustomAPIs...") req := ar.Request body := req.Object.Raw var platformWorkflow platformworkflowv1alpha1.ResourceComposition - err := json.Unmarshal(body, &platformWorkflow) - if err != nil { + json.Unmarshal(body, &platformWorkflow) + /*if err != nil { fmt.Println(err) - } + }*/ platformWorkflowName, _ := jsonparser.GetUnsafeString(body, "metadata", "name") - namespace1, _, _, err := jsonparser.Get(body, "metadata", "namespace") + namespace1, _, _, _ := jsonparser.Get(body, "metadata", "namespace") namespace := string(namespace1) if namespace == "" { namespace = "default" @@ -1221,11 +1235,11 @@ func getPaCAnnotation(ar *v1.AdmissionReview) map[string]string { crdplural, _ := jsonparser.GetUnsafeString(body, "spec", "names", "plural") crdversion, _ := jsonparser.GetUnsafeString(body, "spec", "versions","[0]","name") crdgroup, _ := jsonparser.GetUnsafeString(body, "spec", "group") - fmt.Printf("CRDKind:%s, CRDPlural:%s, CRDVersion:%s\n", crdkind, crdplural, crdversion) + //fmt.Printf("CRDKind:%s, CRDPlural:%s, CRDVersion:%s\n", crdkind, crdplural, crdversion) customAPI := crdgroup + "/" + crdversion + "/" + crdkind apiVersion := crdgroup + "/" + crdversion platformWorkflowName, ok := customAPIPlatformWorkflowMap[customAPI] - fmt.Printf("PlatformWorkflowName:%s, ok:%v\n", platformWorkflowName, ok) + //fmt.Printf("PlatformWorkflowName:%s, ok:%v\n", platformWorkflowName, ok) chartKinds := "" if ok { @@ -1234,7 +1248,7 @@ func getPaCAnnotation(ar *v1.AdmissionReview) map[string]string { chartKindsB := DryRunChart(platformWorkflowName, namespace) chartKinds = string(chartKindsB)*/ chartKinds = chartKindMap[platformWorkflowName] - fmt.Printf("Chart Kinds:%v\n", chartKinds) + //fmt.Printf("Chart Kinds:%v\n", chartKinds) // If no kinds are found in the dry run then there is nothing to be done. if chartKinds == "" { @@ -1258,7 +1272,7 @@ func getPaCAnnotation(ar *v1.AdmissionReview) map[string]string { //fmt.Printf("Unique kinds:%v\n", uniqueKinds) chartKinds = strings.Join(uniqueKinds, ";") - fmt.Printf("Annotating %s\n", chartKinds) + //fmt.Printf("Annotating %s\n", chartKinds) allAnnotations, _, _, err := jsonparser.Get(req.Object.Raw, "metadata", "annotations") if err == nil { @@ -1278,7 +1292,7 @@ func getPaCAnnotation(ar *v1.AdmissionReview) map[string]string { namespace = GetNamespace() manpageConfigMapName := registerManPage(crdkind, apiVersion, platformWorkflowName, namespace) - fmt.Printf("### ManPage ConfigMap Name:%s ####\n", manpageConfigMapName) + //fmt.Printf("### ManPage ConfigMap Name:%s ####\n", manpageConfigMapName) manPageAnnotation := "resource/usage" manPageAnnotationValue := manpageConfigMapName @@ -1302,9 +1316,9 @@ func checkCRDNameValidity(ar *v1.AdmissionReview) string { fmt.Errorf("Error:%s\n", err) } - crname, err := jsonparser.GetUnsafeString(req.Object.Raw, "metadata", "name") - fmt.Printf("CR Name:%s\n", crname) - fmt.Printf("Kind:%s\n", kind) + //crname, err := jsonparser.GetUnsafeString(req.Object.Raw, "metadata", "name") + //fmt.Printf("CR Name:%s\n", crname) + //fmt.Printf("Kind:%s\n", kind) message1 := ""; if strings.Contains(kind, ".") {