From 98407560677c92f4ad6f1d16259724f198d6ea6d Mon Sep 17 00:00:00 2001 From: Phillip Wittrock Date: Fri, 20 Nov 2020 16:09:51 -0800 Subject: [PATCH] updates --- config-gen/apis/v1alpha1/certs.go | 135 ++++++++++++++++++++ config-gen/apis/v1alpha1/controllergen.go | 4 +- config-gen/apis/v1alpha1/patchetemplates.go | 10 +- config-gen/apis/v1alpha1/run.go | 102 +-------------- config-gen/apis/v1alpha1/templates.go | 21 ++- config-gen/apis/v1alpha1/types.go | 105 ++++++++++----- config-gen/main.go | 2 +- 7 files changed, 231 insertions(+), 148 deletions(-) create mode 100644 config-gen/apis/v1alpha1/certs.go diff --git a/config-gen/apis/v1alpha1/certs.go b/config-gen/apis/v1alpha1/certs.go new file mode 100644 index 00000000000..a0a1785442d --- /dev/null +++ b/config-gen/apis/v1alpha1/certs.go @@ -0,0 +1,135 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "encoding/base64" + "fmt" + + "github.com/cloudflare/cfssl/cli/genkey" + "github.com/cloudflare/cfssl/config" + "github.com/cloudflare/cfssl/csr" + "github.com/cloudflare/cfssl/helpers" + "github.com/cloudflare/cfssl/selfsign" + "sigs.k8s.io/kustomize/kyaml/fn/framework" + "sigs.k8s.io/kustomize/kyaml/kio" + "sigs.k8s.io/kustomize/kyaml/yaml" +) + +var _ kio.Filter = &CertFilter{} + +// CertFilter generates and injects certificates into webhook +type CertFilter struct { + *APIConfiguration +} + +// Filter implements kio.Filter +func (c CertFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) { + if !c.Spec.Development.GenerateCert { + return input, nil + } + if err := c.generateCert(); err != nil { + return nil, err + } + + s := &framework.Selector{ + Kinds: []string{ + "CustomResourceDefinition", + "ValidatingWebhookConfiguration", + "MutatingWebhookConfiguration", + }, + Filter: func(n *yaml.RNode) bool { + // Allow-list conversion webhooks + m, _ := n.GetMeta() + if m.Kind != "CustomResourceDefinition" { + return true + } + return c.Spec.ConversionWebhooks[m.Name] + }, + } + matches, err := s.GetMatches(&framework.ResourceList{Items: input}) + if err != nil { + return nil, err + } + for i := range matches { + wh := matches[i].Field("webhooks") + if wh.IsNilOrEmpty() { + continue + } + err := wh.Value.VisitElements(func(node *yaml.RNode) error { + err := node.PipeE(yaml.LookupCreate(yaml.ScalarNode, "clientConfig", "caBundle"), + yaml.FieldSetter{StringValue: c.Status.CertCA}) + if err != nil { + return err + } + err = node.PipeE(yaml.LookupCreate(yaml.ScalarNode, "clientConfig", "service", "namespace"), + yaml.FieldSetter{StringValue: c.Spec.Namespace}) + if err != nil { + return err + } + + // setup conversion hook + m, _ := node.GetMeta() + if m.Kind == "CustomResourceDefinition" { + err = node.PipeE(yaml.LookupCreate(yaml.ScalarNode, "clientConfig", "service", "path"), + yaml.FieldSetter{StringValue: "/convert"}) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return nil, err + } + } + + return input, nil +} + +func (c CertFilter) generateCert() error { + var err error + var req = csr.New() + req.Hosts = []string{ + fmt.Sprintf("webhook-service.%s.svc", c.Spec.Namespace), + fmt.Sprintf("webhook-service.%s.svc.cluster.local", c.Spec.Namespace), + } + req.CN = "kb-dev-controller-manager" + + var key, csrPEM []byte + g := &csr.Generator{Validator: genkey.Validator} + csrPEM, key, err = g.ProcessRequest(req) + if err != nil { + return err + } + priv, err := helpers.ParsePrivateKeyPEM(key) + if err != nil { + return err + } + + profile := config.DefaultConfig() + profile.Expiry = c.Spec.Development.CertDuration + cert, err := selfsign.Sign(priv, csrPEM, profile) + if err != nil { + return err + } + + c.Status.CertCA = base64.StdEncoding.EncodeToString(cert) + c.Status.CertKey = base64.StdEncoding.EncodeToString(key) + return nil +} diff --git a/config-gen/apis/v1alpha1/controllergen.go b/config-gen/apis/v1alpha1/controllergen.go index a8d5a7c908b..575872d3f67 100644 --- a/config-gen/apis/v1alpha1/controllergen.go +++ b/config-gen/apis/v1alpha1/controllergen.go @@ -47,13 +47,13 @@ func (cgr ControllerGenFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error MaxDescLen: &desclen, }) gens := genall.Generators{&crdGen} - if !cgr.Spec.DisableCreateRBAC { + if !cgr.Spec.Enabled("rbac") { rbacGen := genall.Generator(rbac.Generator{ RoleName: cgr.Spec.Namespace + "-manager-role", }) gens = append(gens, &rbacGen) } - if cgr.Spec.EnableWebhooks { + if cgr.Spec.Enabled("webhooks") { webhookGen := genall.Generator(webhook.Generator{}) gens = append(gens, &webhookGen) } diff --git a/config-gen/apis/v1alpha1/patchetemplates.go b/config-gen/apis/v1alpha1/patchetemplates.go index 219b888278d..0d27dfa9252 100644 --- a/config-gen/apis/v1alpha1/patchetemplates.go +++ b/config-gen/apis/v1alpha1/patchetemplates.go @@ -45,7 +45,7 @@ func getPatchTemplates(c *APIConfiguration) []framework.PatchTemplate { "MutatingWebhookConfiguration", }, }, - Template: template.Must(template.New("conversion-webhook").Parse(`{{ if .Spec.EnableCertManager}} + Template: template.Must(template.New("conversion-webhook").Parse(`{{ if .Spec.Enabled "cert-manager" }} metadata: annotations: cert-manager.io/inject-ca-from: {{ .Spec.Namespace }}/{{ .Spec.Name }}-serving-cert @@ -61,7 +61,7 @@ metadata: return c.Spec.ConversionWebhooks[m.Name] }, }, - Template: template.Must(template.New("conversion-webhook").Parse(`{{ if .Spec.EnableWebhooks}} + Template: template.Must(template.New("conversion-webhook").Parse(`{{ if .Spec.Enabled "webhooks" }} spec: conversion: strategy: Webhook @@ -69,7 +69,7 @@ spec: # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) {{- if .Spec.Development.GenerateCert }} - caBundle: {{ .Spec.Development.CA }} + caBundle: {{ .Status.CertCA }} {{- end }} service: namespace: {{ .Spec.Namespace }} @@ -81,7 +81,7 @@ spec: // Auth proxy patch for the controller-manager { Selector: controllerManagerSelector, - Template: template.Must(template.New("controller-manager-auth-proxy-patch").Parse(`{{ if not .Spec.DisableAuthProxy}} + Template: template.Must(template.New("controller-manager-auth-proxy-patch").Parse(`{{ if not .Spec.DisableAuthProxy }} spec: template: spec: @@ -106,7 +106,7 @@ spec: // Webhooks patch for the controller-manager { Selector: controllerManagerSelector, - Template: template.Must(template.New("controller-manager-webhooks").Parse(`{{ if .Spec.EnableWebhooks }} + Template: template.Must(template.New("controller-manager-webhooks").Parse(`{{ if .Spec.Enabled "webhooks" }} spec: template: spec: diff --git a/config-gen/apis/v1alpha1/run.go b/config-gen/apis/v1alpha1/run.go index 0368a663f7e..9ad8e453413 100644 --- a/config-gen/apis/v1alpha1/run.go +++ b/config-gen/apis/v1alpha1/run.go @@ -17,27 +17,17 @@ limitations under the License. package v1alpha1 import ( - "encoding/base64" - "fmt" "time" - "github.com/cloudflare/cfssl/cli/genkey" - "github.com/cloudflare/cfssl/config" - "github.com/cloudflare/cfssl/csr" - "github.com/cloudflare/cfssl/helpers" - "github.com/cloudflare/cfssl/selfsign" "github.com/spf13/cobra" "sigs.k8s.io/kustomize/kyaml/errors" "sigs.k8s.io/kustomize/kyaml/fn/framework" - "sigs.k8s.io/kustomize/kyaml/yaml" ) // NewCommand returns a new cobra command func NewCommand() cobra.Command { fc := &APIConfiguration{} - // read Project.yaml into struct - return framework.TemplateCommand{ MergeResources: true, API: fc, @@ -51,7 +41,7 @@ func NewCommand() cobra.Command { // Validate the input if fc.Spec.Name == "" { if fc.Name == "" { - return errors.Errorf("must specify name") + return errors.Errorf("must specify PROJECT projectName field") } fc.Spec.Name = fc.Name } @@ -62,7 +52,7 @@ func NewCommand() cobra.Command { fc.Spec.Namespace = fc.Spec.Name + "-system" } if fc.Spec.Image == "" { - return errors.Errorf("must specify image") + return errors.Errorf("must specify PROJECT image field") } if fc.Spec.Development.CertDuration == 0 { d := time.Hour * 1 @@ -75,97 +65,19 @@ func NewCommand() cobra.Command { if err != nil { return err } - return doCerts(fc, rl) + return nil }, Templates: configTemplates, PatchTemplates: getPatchTemplates(fc), PostProcess: func(rl *framework.ResourceList) error { // Sort the resources var err error - rl.Items, err = SortFilter{APIConfiguration: fc}.Filter(rl.Items) - return err - }, - }.GetCommand() -} - -func doCerts(fc *APIConfiguration, rl *framework.ResourceList) error { - if !fc.Spec.Development.GenerateCert { - return nil - } - if err := generateCert(fc); err != nil { - return err - } - - s := &framework.Selector{ - Kinds: []string{ - "CustomResourceDefinition", - "ValidatingWebhookConfiguration", - "MutatingWebhookConfiguration", - }, - } - matches, err := s.GetMatches(rl) - if err != nil { - return err - } - for i := range matches { - wh := matches[i].Field("webhooks") - if wh.IsNilOrEmpty() { - continue - } - err := wh.Value.VisitElements(func(node *yaml.RNode) error { - err := node.PipeE(yaml.LookupCreate(yaml.ScalarNode, "clientConfig", "caBundle"), - yaml.FieldSetter{StringValue: fc.Spec.Development.CA}) - if err != nil { - return err - } - err = node.PipeE(yaml.LookupCreate(yaml.ScalarNode, "clientConfig", "service", "namespace"), - yaml.FieldSetter{StringValue: fc.Spec.Namespace}) + rl.Items, err = CertFilter{APIConfiguration: fc}.Filter(rl.Items) if err != nil { return err } - - return nil - }) - if err != nil { + rl.Items, err = SortFilter{APIConfiguration: fc}.Filter(rl.Items) return err - } - } - - return nil -} - -func generateCert(api *APIConfiguration) error { - var err error - var req = csr.New() - req.Hosts = []string{ - fmt.Sprintf("webhook-service.%s.svc", api.Spec.Namespace), - fmt.Sprintf("webhook-service.%s.svc.cluster.local", api.Spec.Namespace), - } - req.CN = "kb-dev-controller-manager" - // req.KeyRequest.A = "rsa" - // req.KeyRequest.S = 2048 - - var key, csrPEM []byte - g := &csr.Generator{Validator: genkey.Validator} - csrPEM, key, err = g.ProcessRequest(req) - if err != nil { - return err - } - priv, err := helpers.ParsePrivateKeyPEM(key) - if err != nil { - return err - } - - profile := config.DefaultConfig() - profile.Expiry = api.Spec.Development.CertDuration - - cert, err := selfsign.Sign(priv, csrPEM, profile) - if err != nil { - return err - } - - api.Spec.Development.CA = base64.StdEncoding.EncodeToString(cert) - api.Spec.Development.Key = base64.StdEncoding.EncodeToString(key) - - return nil + }, + }.GetCommand() } diff --git a/config-gen/apis/v1alpha1/templates.go b/config-gen/apis/v1alpha1/templates.go index b2273e9989d..fdd56d7b05b 100644 --- a/config-gen/apis/v1alpha1/templates.go +++ b/config-gen/apis/v1alpha1/templates.go @@ -21,7 +21,7 @@ import "text/template" var ( configTemplates = []*template.Template{ // Template for the controller-manager namespace - template.Must(template.New("namespace").Parse(`{{ if not .Spec.DisableCreateNamespace }} + template.Must(template.New("namespace").Parse(`{{ if .Spec.Enabled "namespace" }} apiVersion: v1 kind: Namespace metadata: @@ -32,7 +32,7 @@ metadata: {{- end }}`)), // Template for the controller-manager - template.Must(template.New("controller-manager").Parse(`{{ if not .Spec.DisableCreateManager }} + template.Must(template.New("controller-manager").Parse(`{{ if .Spec.Enabled "controller-manager" }} apiVersion: apps/v1 kind: Deployment metadata: @@ -66,7 +66,7 @@ spec: memory: 20Mi terminationGracePeriodSeconds: 10 --- -{{- if .Spec.EnableWebhooks }} +{{- if .Spec.Enabled "webhooks" }} apiVersion: v1 kind: Service metadata: @@ -102,7 +102,7 @@ spec: // Template for controller-manager RBAC role binding. // Note: the ClusterRole is generated by controller-gen from the code - template.Must(template.New("rbac-manager").Parse(`{{- if not .Spec.DisableCreateRBAC }} + template.Must(template.New("rbac-manager").Parse(`{{- if .Spec.Enabled "rbac" }} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -119,7 +119,7 @@ subjects: {{ end }}`)), // Template for controller-manager RBAC leader election - template.Must(template.New("rbac-leader-election").Parse(`{{- if not .Spec.DisableCreateRBAC }} + template.Must(template.New("rbac-leader-election").Parse(`{{- if .Spec.Enabled "rbac" }} apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: @@ -177,14 +177,13 @@ metadata: name: webhook-server-cert namespace: {{ .Spec.Namespace }} data: - tls.key: {{ .Spec.Development.Key }} - tls.crt: {{ .Spec.Development.CA }} + tls.key: {{ .Status.CertKey }} + tls.crt: {{ .Status.CertCA }} --- {{ end }}`)), // Template for controller-manager RBAC metrics auth proxy - template.Must(template.New("rbac-metrics-auth-proxy").Parse( - `{{- if not .Spec.DisableCreateRBAC }}{{ if not .Spec.DisableAuthProxy}} + template.Must(template.New("rbac-metrics-auth-proxy").Parse(`{{- if .Spec.Enabled "rbac" }}{{ if not .Spec.DisableAuthProxy}} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: @@ -215,7 +214,7 @@ subjects: {{ end }}{{ end }}`)), // Template for controller-manager prometheus service monitor - template.Must(template.New("prometheus").Parse(`{{- if .Spec.EnablePrometheus }} + template.Must(template.New("prometheus").Parse(`{{- if .Spec.Enabled "prometheus" }} apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: @@ -234,7 +233,7 @@ spec: {{ end }}`)), // Template for the certification manager injection - template.Must(template.New("cert-manager").Parse(`{{- if .Spec.EnableCertManager }} + template.Must(template.New("cert-manager").Parse(`{{- if .Spec.Enabled "cert-manager" }} # The following manifests contain a self-signed issuer CR and a certificate CR. # More document can be found at https://docs.cert-manager.io # WARNING: Targets CertManager 0.11 check https://docs.cert-manager.io/en/latest/tasks/upgrading/index.html for diff --git a/config-gen/apis/v1alpha1/types.go b/config-gen/apis/v1alpha1/types.go index 94df068dcff..3c51e2f8d09 100644 --- a/config-gen/apis/v1alpha1/types.go +++ b/config-gen/apis/v1alpha1/types.go @@ -26,62 +26,99 @@ import ( type APIConfiguration struct { metav1.TypeMeta `json:",inline" yaml:",omitempty"` metav1.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty"` - Spec APIConfigurationSpec `json:"spec,omitempty" yaml:"spec,omitempty"` - Project *APIConfigurationSpec `json:"config,omitempty" yaml:"config,omitempty"` + + // Spec is the configuration spec defining what configuration should be produced. + Spec APIConfigurationSpec `json:"spec,omitempty" yaml:"spec,omitempty"` + + // Status is the configuration status defined at runtime. + Status APIConfigurationStatus `json:"status,omitempty" yaml:"status,omitempty"` + + // Project is for the field name used by the PROJECT file (config), instead of spec. + // Use Spec unless in the PROJECT file. + Project *APIConfigurationSpec `json:"config,omitempty" yaml:"config,omitempty"` } -// APIConfigurationSpec defines the desired configuration to be generated -type APIConfigurationSpec struct { - // Directory is the kubebuilder directory containing the code - Directory string `json:"directory" yaml:"directory"` +var ( + // NamespaceComponent will create the namespace for the controller-manager + NamespaceComponent = "namespace" - // Name is the name of the config - Name string `json:"name" yaml:"name"` + // ControllerManagerComponent will create the controller-manager Deployment + ControllerManagerComponent = "controller-manager" - // Namespace is the name of the config - Namespace string `json:"namesapce" yaml:"namesapce"` + // WebhooksComponent will create the webhook configurations + WebhooksComponent = "webhooks" - // Image is the container image to use in the controller-manager - Image string `json:"image" yaml:"image"` + // CRDsComponent will create the CRDs + CRDsComponent = "crds" - // DisableCreateNamespace prevents the namespace from being generated - DisableCreateNamespace bool `json:"disableCreateNamespace,omitempty" yaml:"disableCreateNamespace,omitempty"` + // RBACComponent will create RBAC rules + RBACComponent = "rbac" - // DisableCreateManager if set to true will disable generating the controller-manager - DisableCreateManager bool `json:"disableCreateManager,omitempty" yaml:"disableCreateManager,omitempty"` + // CertManagerComponent will create the Issuer and Certificate resources + // for CertManager to inject the certificates + CertManagerComponent = "cert-manager" - // DisableCreateRBAC if set to true will disable generating the rbac - DisableCreateRBAC bool `json:"disableCreateRBAC,omitempty" yaml:"disableCreateRBAC,omitempty"` + // PrometheusComponent will create the ServiceMonitor resource + PrometheusComponent = "prometheus" +) - // DisableAuthProxy if set to true will disable the auth proxy - DisableAuthProxy bool `json:"disableAuthProxy,omitempty" yaml:"disableAuthProxy,omitempty"` +// DefaultComponents is the set of components that are created by default +var DefaultComponents = map[string]bool{ + NamespaceComponent: true, + ControllerManagerComponent: true, + RBACComponent: true, + CRDsComponent: true, +} + +// Enabled returns true if the component is enabled +func (a *APIConfigurationSpec) Enabled(component string) bool { + if v, found := a.Components[component]; found { + return v + } + return DefaultComponents[component] +} + +// APIConfigurationSpec defines the desired configuration to be generated +type APIConfigurationSpec struct { + // Directory is the kubebuilder directory containing the code + Directory string `json:"projectDirectory" yaml:"projectDirectory"` - // EnableWebhooks configures webhooks for the controller-manager - EnableWebhooks bool `json:"enableWebhooks,omitempty" yaml:"enableWebhooks,omitempty"` + // Name is the name of project and used to generate the component and role names + // Defaults to metadata.name + Name string `json:"projectName" yaml:"projectName"` - // EnableCertManager uses the jetstack certmanager to inject certificates - // for webhooks. - EnableCertManager bool `json:"enableCertManager,omitempty" yaml:"enableCertManager,omitempty"` + // Namespace is the namespace to run the project in -- defaults to projectName-system + Namespace string `json:"namespace" yaml:"namespace"` - // InstallCertManager installs the cert manager dependency - InstallCertManager bool `json:"installCertManager,omitempty" yaml:"installCertManager,omitempty"` + // Image is the container image to run in the controller-manager + Image string `json:"image" yaml:"image"` - // EnablePrometheus creates a service monitor - EnablePrometheus bool `json:"enablePrometheus,omitempty" yaml:"enablePrometheus,omitempty"` + // Components is a map of components to enable or disable + Components map[string]bool `json:"components" yaml:"components"` - Development DevelopmentOptions `json:"developmentOptions,omitempty" yaml:"developmentOptions,omitempty"` + // DisableAuthProxy if set to true will disable the auth proxy + DisableAuthProxy bool `json:"disableAuthProxy,omitempty" yaml:"disableAuthProxy,omitempty"` // ConversionWebhooks is a map of kinds to enable conversion webhooks for ConversionWebhooks map[string]bool `json:"conversionWebhooks,omitempty" yaml:"conversionWebhooks,omitempty"` + + // Development contains development options + Development DevelopmentOptions `json:"developmentOptions,omitempty" yaml:"developmentOptions,omitempty"` +} + +// APIConfigurationStatus is runtime status for the api configuration +type APIConfigurationStatus struct { + CertCA string + + CertKey string } // DevelopmentOptions defines options for development installation type DevelopmentOptions struct { + // GenerateCert will cause a self signed certificate to be generated and injected + // into the Webhook caBundles. GenerateCert bool `json:"generateCert,omitempty" yaml:"generateCert,omitempty"` + // CertDuration sets the duration for the cert CertDuration time.Duration `json:"certDuration,omitempty" yaml:"certDuration,omitempty"` - - CA string - - Key string } diff --git a/config-gen/main.go b/config-gen/main.go index d855d7ec251..5bc3f9c8e46 100644 --- a/config-gen/main.go +++ b/config-gen/main.go @@ -29,7 +29,7 @@ func main() { // Default to using the project file if no file is specified and // not being run as a function if len(os.Args) <= 1 && os.Getenv("CONFIG_FUNCTION") != "true" { - if c, _ := cli.Read(); c.APIConfigurationSpec != nil { + if c, _ := cli.Read(); c.Config != nil { os.Args = append(os.Args, cli.ReadPath) } }