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

feat: use hooks as a validating webhook handlers #223

Merged
merged 19 commits into from
Jan 12, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
++
  • Loading branch information
diafour committed Dec 15, 2020
commit 2fb19d4b8c93dd540d7047563c837c0952f81448
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ spec:
fieldPath: metadata.namespace
- name: VALIDATING_WEBHOOK_SERVICE_NAME
value: {{ .Values.shellOperator.validatingWebhookServiceName | quote }}
- name: VALIDATING_WEBHOOK_CLIENT_CA
value: /validating-certs/client-ca.pem
volumeMounts:
- name: validating-certs
mountPath: /validating-certs/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ data:
{{ .Files.Get "validating-certs/server-key.pem" | b64enc }}
cluster-ca.pem: |
{{ .Files.Get "validating-certs/cluster-ca.pem" | b64enc }}
client-ca.pem: |
{{ .Files.Get "validating-certs/cluster-ca.pem" | b64enc }}
55 changes: 55 additions & 0 deletions pkg/app/validating-webhook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package app

import (
"gopkg.in/alecthomas/kingpin.v2"
)

type validatingWebhookSettings struct {
ServerCertPath string
ServerKeyPath string
ClusterCAPath string
ClusterCABundle []byte
ClientCAPaths []string
ServiceName string
ConfigurationName string
ListenPort string
ListenAddr string
}

var ValidatingWebhookSettings = &validatingWebhookSettings{
ServerCertPath: "/validating-certs/server.crt",
ServerKeyPath: "/validating-certs/server-key.pem",
ClusterCAPath: "/validating-certs/cluster-ca.pem",
ClientCAPaths: nil,
ServiceName: "shell-operator-validating-svc",
ConfigurationName: "shell-operator-hooks",
ListenAddr: "0.0.0.0",
ListenPort: "9680",
}

// DefineValidatingWebhookFlags defines flags for ValidatingWebhook server.
func DefineValidatingWebhookFlags(cmd *kingpin.CmdClause) {
cmd.Flag("validating-webhook-configuration-name", "A name of a ValidatingWebhookConfiguration resource. Can be set with $VALIDATING_WEBHOOK_CONFIGURATION_NAME.").
Envar("VALIDATING_WEBHOOK_CONFIGURATION_NAME").
Default(ValidatingWebhookSettings.ConfigurationName).
StringVar(&ValidatingWebhookSettings.ConfigurationName)
cmd.Flag("validating-webhook-service-name", "A name of a service used in ValidatingWebhookConfiguration. Can be set with $VALIDATING_WEBHOOK_SERVICE_NAME.").
Envar("VALIDATING_WEBHOOK_SERVICE_NAME").
Default(ValidatingWebhookSettings.ServiceName).
StringVar(&ValidatingWebhookSettings.ServiceName)
cmd.Flag("validating-webhook-server-cert", "A path to a server certificate for service used in ValidatingWebhookConfiguration. Can be set with $VALIDATING_WEBHOOK_SERVER_CERT.").
Envar("VALIDATING_WEBHOOK_SERVER_CERT").
Default(ValidatingWebhookSettings.ServerCertPath).
StringVar(&ValidatingWebhookSettings.ServerCertPath)
cmd.Flag("validating-webhook-server-key", "A path to a server private key for service used in ValidatingWebhookConfiguration. Can be set with $VALIDATING_WEBHOOK_SERVER_KEY.").
Envar("VALIDATING_WEBHOOK_SERVER_KEY").
Default(ValidatingWebhookSettings.ServerKeyPath).
StringVar(&ValidatingWebhookSettings.ServerKeyPath)
cmd.Flag("validating-webhook-cluster-ca", "A path to a cluster ca bundle for ValidatingWebhookConfiguration. Can be set with $VALIDATING_WEBHOOK_CLUSTER_CA.").
Envar("VALIDATING_WEBHOOK_CLUSTER_CA").
Default(ValidatingWebhookSettings.ClusterCAPath).
StringVar(&ValidatingWebhookSettings.ClusterCAPath)
cmd.Flag("validating-webhook-client-ca", "A path to a server certificate for ValidatingWebhookConfiguration. Can be set with $VALIDATING_WEBHOOK_CLIENT_CA.").
Envar("VALIDATING_WEBHOOK_CLIENT_CA").
StringsVar(&ValidatingWebhookSettings.ClientCAPaths)
}
87 changes: 87 additions & 0 deletions pkg/hook/config/schemas.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,93 @@ properties:
"$ref": "#/definitions/nameSelector"
labelSelector:
"$ref": "#/definitions/labelSelector"
kubernetesValidating:
title: ValidatingWebhookConfiguration handlers
type: array
additionalItems: false
minItems: 1
items:
type: object
additionalProperties: false
required:
- name
properties:
name:
type: string
group:
type: string
includeSnapshotsFrom:
type: array
additionalItems: false
minItems: 1
items:
type: string
failurePolicy:
type: string
enum:
- Ignore
- Fail
sideEffects:
type: string
enum:
- None
- NoneOrDryRun
timeoutSeconds:
type: integer
example: 10
labelSelector:
"$ref": "#/definitions/labelSelector"
namespace:
type: object
additionalProperties: false
required:
- nameSelector
properties:
nameSelector:
"$ref": "#/definitions/nameSelector"
rules:
type: array
additionalItems: false
minItems: 1
items:
type: object
additionalProperties: false
required:
- apiVersions
- apiGroups
- resources
- operations
properties:
apiVersions:
type: array
minItems: 1
items:
type: string
apiGroups:
type: array
minItems: 1
items:
type: string
resources:
type: array
minItems: 1
items:
type: string
operations:
type: array
minItems: 1
items:
type: string
enum:
- "CREATE"
- "UPDATE"
- "*"
scope:
type: string
enum:
- "Cluster"
- "Namespaced"
- "*"
`,
"v0": `
type: object
Expand Down
17 changes: 10 additions & 7 deletions pkg/hook/config/schemas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@ package config

import (
"testing"

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

func Test_GetSchema(t *testing.T) {
schemas := []string{"v0", "v1"}

for _, schema := range schemas {
s := GetSchema(schema)
assert.NotNil(t, s)
if s == nil {
t.Fatalf("schema '%s' should not be nil", schema)
}
}
}

func Test_LoadSchema_From_Schemas(t *testing.T) {
for _, schema := range Schemas {
s, err := LoadSchema(schema)
if assert.NoError(t, err) {
assert.NotNil(t, s)
for schemaVer := range Schemas {
s, err := LoadSchema(schemaVer)
if err != nil {
t.Fatalf("schema '%s' should load: %v", schemaVer, err)
}
if s == nil {
t.Fatalf("schema '%s' should not be nil: %v", schemaVer, err)
}
}
}
49 changes: 49 additions & 0 deletions pkg/hook/config/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,52 @@ func Test_Validate_V1_With_Error(t *testing.T) {
})
}
}

func Test_Validate_V1_KubernetesValidating(t *testing.T) {
g := NewWithT(t)

var vu *VersionedUntyped
var err error

var tests = []struct {
name string
configText string
fn func()
}{
{
"kubernetesValidating",
`
configVersion: v1
kubernetesValidating:
- name: "myCrdValidator"
group: main
includeSnapshotsFrom:
- myCrdBinding
failurePolicy: Ignore
labelSelector:
matchExpressions:
- key: azaza
operator: Exists
rules:
- apiVersions: ["v1"]
apiGroups: [""]
resources: ["pods"]
operations: ["*"]
timeoutSeconds: 123
`,
func() {
g.Expect(err).ShouldNot(HaveOccurred())
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
vu = prepareConfigObj(g, tt.configText)
t.Logf("version: %s", vu.Version)
s := GetSchema(vu.Version)
err = ValidateConfig(vu.Obj, s, "root")
//t.Logf("expected multierror was: %v", err)
tt.fn()
})
}
}
7 changes: 4 additions & 3 deletions pkg/hook/types/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (
type BindingType string

const (
Schedule BindingType = "schedule"
OnStartup BindingType = "onStartup"
OnKubernetesEvent BindingType = "kubernetes"
Schedule BindingType = "schedule"
OnStartup BindingType = "onStartup"
OnKubernetesEvent BindingType = "kubernetes"
KubernetesValidating BindingType = "kubernetesValidating"
)

// Types for effective binding configs
Expand Down
34 changes: 28 additions & 6 deletions pkg/validating-webhook/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package validating_webhook

import (
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"net"
"net/http"
"time"
Expand All @@ -20,6 +22,7 @@ type WebhookServer struct {
// StartWebhookServer starts https server
// to listen for AdmissionReview requests from cluster
func (s *WebhookServer) Start() error {
// Load server certificate
keyPair, err := tls.LoadX509KeyPair(
app.ValidatingWebhookSettings.ServerCertPath,
app.ValidatingWebhookSettings.ServerKeyPath,
Expand All @@ -28,19 +31,38 @@ func (s *WebhookServer) Start() error {
return fmt.Errorf("load TLS certs: %v", err)
}

//host := fmt.Sprintf("%s.%s",
// app.ValidatingWebhookSettings.ServiceName,
// app.Namespace,
//)
host := app.ValidatingWebhookSettings.ServiceName
// Construct hostname
host := fmt.Sprintf("%s.%s",
app.ValidatingWebhookSettings.ServiceName,
app.Namespace,
)

tlsConf := &tls.Config{
Certificates: []tls.Certificate{keyPair},
ServerName: host,
}

listenAddr := app.ValidatingWebhookSettings.ListenAddr + ":" + app.ValidatingWebhookSettings.ListenPort
// Load client CA if defined
if len(app.ValidatingWebhookSettings.ClientCAPaths) > 0 {
roots := x509.NewCertPool()

for _, caPath := range app.ValidatingWebhookSettings.ClientCAPaths {
caBytes, err := ioutil.ReadFile(caPath)
if err != nil {
return fmt.Errorf("load client CA '%s': %v", caPath, err)
}

ok := roots.AppendCertsFromPEM(caBytes)
if !ok {
return fmt.Errorf("parse client CA '%s': %v", caPath, err)
}
}

tlsConf.ClientAuth = tls.RequireAndVerifyClientCert
tlsConf.ClientCAs = roots
}

listenAddr := app.ValidatingWebhookSettings.ListenAddr + ":" + app.ValidatingWebhookSettings.ListenPort
// Check if port is available
listener, err := net.Listen("tcp", listenAddr)
if err != nil {
Expand Down
Loading