1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 07:57:07 +00:00

Merge pull request #320 from nirmata/304_feature

#319, #317 & #318 fixes
This commit is contained in:
Shivkumar Dudhani 2019-08-27 23:53:58 -07:00 committed by GitHub
commit d14e07cedc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 632 additions and 374 deletions

View file

@ -37,7 +37,8 @@ func main() {
printVersionInfo()
// profile cpu and memory consuption
prof = enableProfiling(cpu, memory)
// cleanUp Channel
cleanUp := make(chan struct{})
// CLIENT CONFIG
clientConfig, err := createClientConfig(kubeconfig)
if err != nil {
@ -136,7 +137,7 @@ func main() {
// -- annotations on resources with update details on mutation JSON patches
// -- generate policy violation resource
// -- generate events on policy and resource
server, err := webhooks.NewWebhookServer(pclient, client, tlsPair, pInformer.Kyverno().V1alpha1().Policies(), pInformer.Kyverno().V1alpha1().PolicyViolations(), egen, webhookRegistrationClient, pc.GetPolicyStatusAggregator(), filterK8Resources)
server, err := webhooks.NewWebhookServer(pclient, client, tlsPair, pInformer.Kyverno().V1alpha1().Policies(), pInformer.Kyverno().V1alpha1().PolicyViolations(), egen, webhookRegistrationClient, pc.GetPolicyStatusAggregator(), filterK8Resources, cleanUp)
if err != nil {
glog.Fatalf("Unable to create webhook server: %v\n", err)
}
@ -157,6 +158,9 @@ func main() {
<-stopCh
disableProfiling(prof)
server.Stop()
// resource cleanup
// remove webhook configurations
<-cleanUp
}
func init() {

View file

@ -15,9 +15,13 @@ const (
ValidatingWebhookConfigurationDebug = "kyverno-validating-webhook-cfg-debug"
ValidatingWebhookName = "nirmata.kyverno.validating-webhook"
PolicyValidatingWebhookConfigurationName = "kyverno-policy-validating-webhook-cfg"
PolicyValidatingWebhookConfigurationDebug = "kyverno-policy-validating-webhook-cfg-debug"
PolicyValidatingWebhookName = "nirmata.kyverno.policy-validating-webhook"
PolicyValidatingWebhookConfigurationName = "kyverno-policy-validating-webhook-cfg"
PolicyValidatingWebhookConfigurationDebugName = "kyverno-policy-validating-webhook-cfg-debug"
PolicyValidatingWebhookName = "nirmata.kyverno.policy-validating-webhook"
PolicyMutatingWebhookConfigurationName = "kyverno-policy-mutating-webhook-cfg"
PolicyMutatingWebhookConfigurationDebugName = "kyverno-policy-mutating-webhook-cfg-debug"
PolicyMutatingWebhookName = "nirmata.kyverno.policy-mutating-webhook"
// Due to kubernetes issue, we must use next literal constants instead of deployment TypeMeta fields
// Issue: https://github.com/kubernetes/kubernetes/pull/63972
@ -31,6 +35,7 @@ var (
MutatingWebhookServicePath = "/mutate"
ValidatingWebhookServicePath = "/validate"
PolicyValidatingWebhookServicePath = "/policyvalidate"
PolicyMutatingWebhookServicePath = "/policymutate"
KubePolicyAppLabels = map[string]string{
"app": "kyverno",
}

View file

@ -465,17 +465,17 @@ func (pc *PolicyController) handleWebhookRegistration(delete bool, policy *kyver
// check empty policy first, then rule type in terms of O(time)
if policies == nil {
glog.V(3).Infoln("No policy found in the cluster, deregistering webhook")
pc.webhookRegistrationClient.DeregisterMutatingWebhook()
pc.webhookRegistrationClient.RemoveResourceMutatingWebhookConfiguration()
} else if !HasMutateOrValidatePolicies(policies) {
glog.V(3).Infoln("No muatate/validate policy found in the cluster, deregistering webhook")
pc.webhookRegistrationClient.DeregisterMutatingWebhook()
pc.webhookRegistrationClient.RemoveResourceMutatingWebhookConfiguration()
}
return nil
}
if webhookList == nil && HasMutateOrValidate(*policy) {
glog.V(3).Infoln("Found policy without mutatingwebhook, registering webhook")
pc.webhookRegistrationClient.RegisterMutatingWebhook()
pc.webhookRegistrationClient.CreateResourceMutatingWebhookConfiguration()
}
return nil

120
pkg/webhookconfig/common.go Normal file
View file

@ -0,0 +1,120 @@
package webhookconfig
import (
"io/ioutil"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/config"
admregapi "k8s.io/api/admissionregistration/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
rest "k8s.io/client-go/rest"
)
func (wrc *WebhookRegistrationClient) readCaData() []byte {
var caData []byte
// Check if ca is defined in the secret tls-ca
// assume the key and signed cert have been defined in secret tls.kyverno
if caData = wrc.client.ReadRootCASecret(); len(caData) != 0 {
glog.V(4).Infof("read CA from secret")
return caData
}
glog.V(4).Infof("failed to read CA from secret, reading from kubeconfig")
// load the CA from kubeconfig
if caData = extractCA(wrc.clientConfig); len(caData) != 0 {
glog.V(4).Infof("read CA from kubeconfig")
return caData
}
glog.V(4).Infof("failed to read CA from kubeconfig")
return nil
}
// ExtractCA used for extraction CA from config
func extractCA(config *rest.Config) (result []byte) {
fileName := config.TLSClientConfig.CAFile
if fileName != "" {
result, err := ioutil.ReadFile(fileName)
if err != nil {
return nil
}
return result
}
return config.TLSClientConfig.CAData
}
func (wrc *WebhookRegistrationClient) constructOwner() v1.OwnerReference {
kubePolicyDeployment, err := wrc.client.GetKubePolicyDeployment()
if err != nil {
glog.Errorf("Error when constructing OwnerReference, err: %v\n", err)
return v1.OwnerReference{}
}
return v1.OwnerReference{
APIVersion: config.DeploymentAPIVersion,
Kind: config.DeploymentKind,
Name: kubePolicyDeployment.ObjectMeta.Name,
UID: kubePolicyDeployment.ObjectMeta.UID,
}
}
func generateDebugWebhook(name, url string, caData []byte, validate bool, timeoutSeconds int32, resource, apiGroups, apiVersions string, operationTypes []admregapi.OperationType) admregapi.Webhook {
return admregapi.Webhook{
Name: name,
ClientConfig: admregapi.WebhookClientConfig{
URL: &url,
CABundle: caData,
},
Rules: []admregapi.RuleWithOperations{
admregapi.RuleWithOperations{
Operations: operationTypes,
Rule: admregapi.Rule{
APIGroups: []string{
apiGroups,
},
APIVersions: []string{
apiVersions,
},
Resources: []string{
resource,
},
},
},
},
TimeoutSeconds: &timeoutSeconds,
}
}
func generateWebhook(name, servicePath string, caData []byte, validation bool, timeoutSeconds int32, resource, apiGroups, apiVersions string, operationTypes []admregapi.OperationType) admregapi.Webhook {
return admregapi.Webhook{
Name: name,
ClientConfig: admregapi.WebhookClientConfig{
Service: &admregapi.ServiceReference{
Namespace: config.KubePolicyNamespace,
Name: config.WebhookServiceName,
Path: &servicePath,
},
CABundle: caData,
},
Rules: []admregapi.RuleWithOperations{
admregapi.RuleWithOperations{
Operations: operationTypes,
Rule: admregapi.Rule{
APIGroups: []string{
apiGroups,
},
APIVersions: []string{
apiVersions,
},
Resources: []string{
resource,
},
},
},
},
TimeoutSeconds: &timeoutSeconds,
}
}

110
pkg/webhookconfig/policy.go Normal file
View file

@ -0,0 +1,110 @@
package webhookconfig
import (
"fmt"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/config"
admregapi "k8s.io/api/admissionregistration/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (wrc *WebhookRegistrationClient) contructPolicyValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.PolicyValidatingWebhookConfigurationName,
Labels: config.KubePolicyAppLabels,
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
Webhooks: []admregapi.Webhook{
generateWebhook(
config.PolicyValidatingWebhookName,
config.PolicyValidatingWebhookServicePath,
caData,
true,
wrc.timeoutSeconds,
"policies/*",
"kyverno.io",
"v1alpha1",
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
),
},
}
}
func (wrc *WebhookRegistrationClient) contructDebugPolicyValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.PolicyValidatingWebhookServicePath)
glog.V(4).Infof("Debug PolicyValidatingWebhookConfig is registered with url %s\n", url)
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.PolicyValidatingWebhookConfigurationDebugName,
Labels: config.KubePolicyAppLabels,
},
Webhooks: []admregapi.Webhook{
generateDebugWebhook(
config.PolicyValidatingWebhookName,
url,
caData,
true,
wrc.timeoutSeconds,
"policies/*",
"kyverno.io",
"v1alpha1",
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
),
},
}
}
func (wrc *WebhookRegistrationClient) contructPolicyMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.PolicyMutatingWebhookConfigurationName,
Labels: config.KubePolicyAppLabels,
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
Webhooks: []admregapi.Webhook{
generateWebhook(
config.PolicyMutatingWebhookName,
config.PolicyMutatingWebhookServicePath,
caData,
true,
wrc.timeoutSeconds,
"policies/*",
"kyverno.io",
"v1alpha1",
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
),
},
}
}
func (wrc *WebhookRegistrationClient) contructDebugPolicyMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.PolicyMutatingWebhookServicePath)
glog.V(4).Infof("Debug PolicyMutatingWebhookConfig is registered with url %s\n", url)
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.PolicyMutatingWebhookConfigurationDebugName,
Labels: config.KubePolicyAppLabels,
},
Webhooks: []admregapi.Webhook{
generateDebugWebhook(
config.PolicyMutatingWebhookName,
url,
caData,
true,
wrc.timeoutSeconds,
"policies/*",
"kyverno.io",
"v1alpha1",
[]admregapi.OperationType{admregapi.Create, admregapi.Update},
),
},
}
}

View file

@ -2,9 +2,7 @@ package webhookconfig
import (
"errors"
"fmt"
"io/ioutil"
"strings"
"time"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/config"
@ -56,19 +54,54 @@ func (wrc *WebhookRegistrationClient) Register() error {
}
// For the case if cluster already has this configs
wrc.DeregisterAll()
// remove previously create webhookconfigurations if any
// webhook configurations are created dynamically based on the policy resources
wrc.removeWebhookConfigurations()
// register policy validating webhook during inital start
return wrc.RegisterPolicyValidatingWebhook()
}
func (wrc *WebhookRegistrationClient) RegisterMutatingWebhook() error {
mutatingWebhookConfig, err := wrc.constructMutatingWebhookConfig(wrc.clientConfig)
if err != nil {
// Static Webhook configuration on Policy CRD
// create Policy CRD validating webhook configuration resource
// used for validating Policy CR
if err := wrc.createPolicyValidatingWebhookConfiguration(); err != nil {
return err
}
// create Policy CRD validating webhook configuration resource
// used for defauling values in Policy CR
if err := wrc.createPolicyMutatingWebhookConfiguration(); err != nil {
return err
}
return nil
}
if _, err = wrc.registrationClient.MutatingWebhookConfigurations().Create(mutatingWebhookConfig); err != nil {
// RemovePolicyWebhookConfigurations removes webhook configurations for reosurces and policy
// called during webhook server shutdown
func (wrc *WebhookRegistrationClient) RemovePolicyWebhookConfigurations(cleanUp chan<- struct{}) {
//TODO: dupliate, but a placeholder to perform more error handlind during cleanup
wrc.removeWebhookConfigurations()
// close channel to notify cleanup is complete
close(cleanUp)
}
func (wrc *WebhookRegistrationClient) CreateResourceMutatingWebhookConfiguration() error {
var caData []byte
var config *admregapi.MutatingWebhookConfiguration
// read CA data from
// 1) secret(config)
// 2) kubeconfig
if caData = wrc.readCaData(); caData == nil {
return errors.New("Unable to extract CA data from configuration")
}
// if serverIP is specified we assume its debug mode
if wrc.serverIP != "" {
// debug mode
// clientConfig - URL
config = wrc.contructDebugMutatingWebhookConfig(caData)
} else {
// clientConfig - service
config = wrc.constructMutatingWebhookConfig(caData)
}
if _, err := wrc.registrationClient.MutatingWebhookConfigurations().Create(config); err != nil {
return err
}
@ -76,13 +109,26 @@ func (wrc *WebhookRegistrationClient) RegisterMutatingWebhook() error {
return nil
}
func (wrc *WebhookRegistrationClient) RegisterValidatingWebhook() error {
validationWebhookConfig, err := wrc.constructValidatingWebhookConfig(wrc.clientConfig)
if err != nil {
return err
}
func (wrc *WebhookRegistrationClient) CreateResourceValidatingWebhookConfiguration() error {
var caData []byte
var config *admregapi.ValidatingWebhookConfiguration
if _, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(validationWebhookConfig); err != nil {
// read CA data from
// 1) secret(config)
// 2) kubeconfig
if caData = wrc.readCaData(); caData == nil {
return errors.New("Unable to extract CA data from configuration")
}
// if serverIP is specified we assume its debug mode
if wrc.serverIP != "" {
// debug mode
// clientConfig - URL
config = wrc.contructDebugValidatingWebhookConfig(caData)
} else {
// clientConfig - service
config = wrc.constructValidatingWebhookConfig(caData)
}
if _, err := wrc.registrationClient.ValidatingWebhookConfigurations().Create(config); err != nil {
return err
}
@ -90,56 +136,124 @@ func (wrc *WebhookRegistrationClient) RegisterValidatingWebhook() error {
return nil
}
func (wrc *WebhookRegistrationClient) RegisterPolicyValidatingWebhook() error {
policyValidationWebhookConfig, err := wrc.contructPolicyValidatingWebhookConfig()
if err != nil {
//registerPolicyValidatingWebhookConfiguration create a Validating webhook configuration for Policy CRD
func (wrc *WebhookRegistrationClient) createPolicyValidatingWebhookConfiguration() error {
var caData []byte
var config *admregapi.ValidatingWebhookConfiguration
// read CA data from
// 1) secret(config)
// 2) kubeconfig
if caData = wrc.readCaData(); caData == nil {
return errors.New("Unable to extract CA data from configuration")
}
// if serverIP is specified we assume its debug mode
if wrc.serverIP != "" {
// debug mode
// clientConfig - URL
config = wrc.contructDebugPolicyValidatingWebhookConfig(caData)
} else {
// clientConfig - service
config = wrc.contructPolicyValidatingWebhookConfig(caData)
}
// create validating webhook configuration resource
if _, err := wrc.registrationClient.ValidatingWebhookConfigurations().Create(config); err != nil {
return err
}
if _, err = wrc.registrationClient.ValidatingWebhookConfigurations().Create(policyValidationWebhookConfig); err != nil {
glog.V(4).Infof("created Validating Webhook Configuration %s ", config.Name)
return nil
}
func (wrc *WebhookRegistrationClient) createPolicyMutatingWebhookConfiguration() error {
var caData []byte
var config *admregapi.MutatingWebhookConfiguration
// read CA data from
// 1) secret(config)
// 2) kubeconfig
if caData = wrc.readCaData(); caData == nil {
return errors.New("Unable to extract CA data from configuration")
}
// if serverIP is specified we assume its debug mode
if wrc.serverIP != "" {
// debug mode
// clientConfig - URL
config = wrc.contructDebugPolicyMutatingWebhookConfig(caData)
} else {
// clientConfig - service
config = wrc.contructPolicyMutatingWebhookConfig(caData)
}
// create mutating webhook configuration resource
if _, err := wrc.registrationClient.MutatingWebhookConfigurations().Create(config); err != nil {
return err
}
glog.V(3).Infoln("Policy validating webhook registered")
glog.V(4).Infof("created Mutating Webhook Configuration %s ", config.Name)
return nil
}
// DeregisterAll deletes webhook configs from cluster
// This function does not fail on error:
// Register will fail if the config exists, so there is no need to fail on error
func (wrc *WebhookRegistrationClient) DeregisterAll() {
wrc.DeregisterMutatingWebhook()
wrc.deregisterValidatingWebhook()
func (wrc *WebhookRegistrationClient) removeWebhookConfigurations() {
startTime := time.Now()
glog.V(4).Infof("Started cleaning up webhookconfigurations")
defer func() {
glog.V(4).Infof("Finished cleaning up webhookcongfigurations (%v)", time.Since(startTime))
}()
// mutating and validating webhook configuration for Kubernetes resources
wrc.RemoveResourceMutatingWebhookConfiguration()
wrc.removeResourceValidatingWebhookConfiguration()
// mutating and validating webhook configurtion for Policy CRD resource
wrc.removePolicyWebhookConfigurations()
}
// removePolicyWebhookConfigurations removes mutating and validating webhook configurations, if already presnt
// webhookConfigurations are re-created later
func (wrc *WebhookRegistrationClient) removePolicyWebhookConfigurations() {
// Validating webhook configuration
var validatingConfig string
if wrc.serverIP != "" {
err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.PolicyValidatingWebhookConfigurationDebug, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
}
validatingConfig = config.PolicyValidatingWebhookConfigurationDebugName
} else {
validatingConfig = config.PolicyValidatingWebhookConfigurationName
}
err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.PolicyValidatingWebhookConfigurationName, &v1.DeleteOptions{})
glog.V(4).Infof("removing webhook configuration %s", validatingConfig)
err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(validatingConfig, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
}
// Mutating webhook configuration
var mutatingConfig string
if wrc.serverIP != "" {
mutatingConfig = config.PolicyMutatingWebhookConfigurationDebugName
} else {
mutatingConfig = config.PolicyMutatingWebhookConfigurationName
}
glog.V(4).Infof("removing webhook configuration %s", mutatingConfig)
if err := wrc.registrationClient.MutatingWebhookConfigurations().Delete(mutatingConfig, &v1.DeleteOptions{}); err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
}
}
func (wrc *WebhookRegistrationClient) deregister() {
wrc.DeregisterMutatingWebhook()
wrc.deregisterValidatingWebhook()
}
func (wrc *WebhookRegistrationClient) DeregisterMutatingWebhook() {
//RemoveResourceMutatingWebhookConfiguration removes mutating webhook configuration for all resources
func (wrc *WebhookRegistrationClient) RemoveResourceMutatingWebhookConfiguration() {
var configName string
if wrc.serverIP != "" {
err := wrc.registrationClient.MutatingWebhookConfigurations().Delete(config.MutatingWebhookConfigurationDebug, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
} else {
wrc.MutationRegistered.UnSet()
}
return
configName = config.MutatingWebhookConfigurationDebug
} else {
configName = config.MutatingWebhookConfigurationName
}
err := wrc.registrationClient.MutatingWebhookConfigurations().Delete(config.MutatingWebhookConfigurationName, &v1.DeleteOptions{})
// delete webhook configuration
err := wrc.registrationClient.MutatingWebhookConfigurations().Delete(configName, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
} else {
@ -147,313 +261,18 @@ func (wrc *WebhookRegistrationClient) DeregisterMutatingWebhook() {
}
}
func (wrc *WebhookRegistrationClient) deregisterValidatingWebhook() {
// removeResourceValidatingWebhookConfiguration removes validating webhook configuration on all resources
func (wrc *WebhookRegistrationClient) removeResourceValidatingWebhookConfiguration() {
var configName string
if wrc.serverIP != "" {
err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.ValidatingWebhookConfigurationDebug, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
}
wrc.ValidationRegistered.UnSet()
return
configName = config.ValidatingWebhookConfigurationDebug
} else {
configName = config.ValidatingWebhookConfigurationName
}
err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(config.ValidatingWebhookConfigurationName, &v1.DeleteOptions{})
err := wrc.registrationClient.ValidatingWebhookConfigurations().Delete(configName, &v1.DeleteOptions{})
if err != nil && !errorsapi.IsNotFound(err) {
glog.Error(err)
}
wrc.ValidationRegistered.UnSet()
}
func (wrc *WebhookRegistrationClient) constructMutatingWebhookConfig(configuration *rest.Config) (*admregapi.MutatingWebhookConfiguration, error) {
var caData []byte
// Check if ca is defined in the secret tls-ca
// assume the key and signed cert have been defined in secret tls.kyverno
caData = wrc.client.ReadRootCASecret()
if len(caData) == 0 {
// load the CA from kubeconfig
caData = extractCA(configuration)
}
if len(caData) == 0 {
return nil, errors.New("Unable to extract CA data from configuration")
}
if wrc.serverIP != "" {
return wrc.contructDebugMutatingWebhookConfig(caData), nil
}
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.MutatingWebhookConfigurationName,
Labels: config.KubePolicyAppLabels,
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
Webhooks: []admregapi.Webhook{
constructWebhook(
config.MutatingWebhookName,
config.MutatingWebhookServicePath,
caData,
false,
wrc.timeoutSeconds,
),
},
}, nil
}
func (wrc *WebhookRegistrationClient) contructDebugMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.MutatingWebhookServicePath)
glog.V(3).Infof("Debug MutatingWebhookConfig is registered with url %s\n", url)
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.MutatingWebhookConfigurationDebug,
Labels: config.KubePolicyAppLabels,
},
Webhooks: []admregapi.Webhook{
constructDebugWebhook(
config.MutatingWebhookName,
url,
caData,
false,
wrc.timeoutSeconds),
},
}
}
func (wrc *WebhookRegistrationClient) constructValidatingWebhookConfig(configuration *rest.Config) (*admregapi.ValidatingWebhookConfiguration, error) {
// Check if ca is defined in the secret tls-ca
// assume the key and signed cert have been defined in secret tls.kyverno
caData := wrc.client.ReadRootCASecret()
if len(caData) == 0 {
// load the CA from kubeconfig
caData = extractCA(configuration)
}
if len(caData) == 0 {
return nil, errors.New("Unable to extract CA data from configuration")
}
if wrc.serverIP != "" {
return wrc.contructDebugValidatingWebhookConfig(caData), nil
}
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.ValidatingWebhookConfigurationName,
Labels: config.KubePolicyAppLabels,
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
Webhooks: []admregapi.Webhook{
constructWebhook(
config.ValidatingWebhookName,
config.ValidatingWebhookServicePath,
caData,
true,
wrc.timeoutSeconds),
},
}, nil
}
func (wrc *WebhookRegistrationClient) contructDebugValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.ValidatingWebhookServicePath)
glog.V(3).Infof("Debug ValidatingWebhookConfig is registered with url %s\n", url)
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.ValidatingWebhookConfigurationDebug,
Labels: config.KubePolicyAppLabels,
},
Webhooks: []admregapi.Webhook{
constructDebugWebhook(
config.ValidatingWebhookName,
url,
caData,
true,
wrc.timeoutSeconds),
},
}
}
func (wrc *WebhookRegistrationClient) contructPolicyValidatingWebhookConfig() (*admregapi.ValidatingWebhookConfiguration, error) {
// Check if ca is defined in the secret tls-ca
// assume the key and signed cert have been defined in secret tls.kyverno
caData := wrc.client.ReadRootCASecret()
if len(caData) == 0 {
// load the CA from kubeconfig
caData = extractCA(wrc.clientConfig)
}
if len(caData) == 0 {
return nil, errors.New("Unable to extract CA data from configuration")
}
if wrc.serverIP != "" {
return wrc.contructDebugPolicyValidatingWebhookConfig(caData), nil
}
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.PolicyValidatingWebhookConfigurationName,
Labels: config.KubePolicyAppLabels,
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
Webhooks: []admregapi.Webhook{
constructWebhook(
config.PolicyValidatingWebhookName,
config.PolicyValidatingWebhookServicePath,
caData,
true,
wrc.timeoutSeconds),
},
}, nil
}
func (wrc *WebhookRegistrationClient) contructDebugPolicyValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.PolicyValidatingWebhookServicePath)
glog.V(3).Infof("Debug PolicyValidatingWebhookConfig is registered with url %s\n", url)
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.PolicyValidatingWebhookConfigurationDebug,
Labels: config.KubePolicyAppLabels,
},
Webhooks: []admregapi.Webhook{
constructDebugWebhook(
config.PolicyValidatingWebhookName,
url,
caData,
true,
wrc.timeoutSeconds),
},
}
}
func constructWebhook(name, servicePath string, caData []byte, validation bool, timeoutSeconds int32) admregapi.Webhook {
resource := "*/*"
apiGroups := "*"
apiversions := "*"
if servicePath == config.PolicyValidatingWebhookServicePath {
resource = "policies/*"
apiGroups = "kyverno.io"
apiversions = "v1alpha1"
}
operationtypes := []admregapi.OperationType{
admregapi.Create,
admregapi.Update,
}
// // Add operation DELETE for validation
// if validation {
// operationtypes = append(operationtypes, admregapi.Delete)
// }
return admregapi.Webhook{
Name: name,
ClientConfig: admregapi.WebhookClientConfig{
Service: &admregapi.ServiceReference{
Namespace: config.KubePolicyNamespace,
Name: config.WebhookServiceName,
Path: &servicePath,
},
CABundle: caData,
},
Rules: []admregapi.RuleWithOperations{
admregapi.RuleWithOperations{
Operations: operationtypes,
Rule: admregapi.Rule{
APIGroups: []string{
apiGroups,
},
APIVersions: []string{
apiversions,
},
Resources: []string{
resource,
},
},
},
},
TimeoutSeconds: &timeoutSeconds,
}
}
func constructDebugWebhook(name, url string, caData []byte, validation bool, timeoutSeconds int32) admregapi.Webhook {
resource := "*/*"
apiGroups := "*"
apiversions := "*"
if strings.Contains(url, config.PolicyValidatingWebhookServicePath) {
resource = "policies/*"
apiGroups = "kyverno.io"
apiversions = "v1alpha1"
}
operationtypes := []admregapi.OperationType{
admregapi.Create,
admregapi.Update,
}
// // Add operation DELETE for validation
// if validation {
// operationtypes = append(operationtypes, admregapi.Delete)
// }
return admregapi.Webhook{
Name: name,
ClientConfig: admregapi.WebhookClientConfig{
URL: &url,
CABundle: caData,
},
Rules: []admregapi.RuleWithOperations{
admregapi.RuleWithOperations{
Operations: operationtypes,
Rule: admregapi.Rule{
APIGroups: []string{
apiGroups,
},
APIVersions: []string{
apiversions,
},
Resources: []string{
resource,
},
},
},
},
TimeoutSeconds: &timeoutSeconds,
}
}
func (wrc *WebhookRegistrationClient) constructOwner() v1.OwnerReference {
kubePolicyDeployment, err := wrc.client.GetKubePolicyDeployment()
if err != nil {
glog.Errorf("Error when constructing OwnerReference, err: %v\n", err)
return v1.OwnerReference{}
}
return v1.OwnerReference{
APIVersion: config.DeploymentAPIVersion,
Kind: config.DeploymentKind,
Name: kubePolicyDeployment.ObjectMeta.Name,
UID: kubePolicyDeployment.ObjectMeta.UID,
}
}
// ExtractCA used for extraction CA from config
func extractCA(config *rest.Config) (result []byte) {
fileName := config.TLSClientConfig.CAFile
if fileName != "" {
result, err := ioutil.ReadFile(fileName)
if err != nil {
return nil
}
return result
}
return config.TLSClientConfig.CAData
}

View file

@ -0,0 +1,110 @@
package webhookconfig
import (
"fmt"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/config"
admregapi "k8s.io/api/admissionregistration/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (wrc *WebhookRegistrationClient) contructDebugMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.MutatingWebhookServicePath)
glog.V(3).Infof("Debug MutatingWebhookConfig is registered with url %s\n", url)
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.MutatingWebhookConfigurationDebug,
Labels: config.KubePolicyAppLabels,
},
Webhooks: []admregapi.Webhook{
generateDebugWebhook(
config.MutatingWebhookName,
url,
caData,
true,
wrc.timeoutSeconds,
"*/*",
"*",
"*",
[]admregapi.OperationType{admregapi.Create},
),
},
}
}
func (wrc *WebhookRegistrationClient) constructMutatingWebhookConfig(caData []byte) *admregapi.MutatingWebhookConfiguration {
return &admregapi.MutatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.MutatingWebhookConfigurationName,
Labels: config.KubePolicyAppLabels,
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
Webhooks: []admregapi.Webhook{
generateWebhook(
config.MutatingWebhookName,
config.MutatingWebhookServicePath,
caData,
false,
wrc.timeoutSeconds,
"*/*",
"*",
"*",
[]admregapi.OperationType{admregapi.Create},
),
},
}
}
func (wrc *WebhookRegistrationClient) constructValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.ValidatingWebhookConfigurationName,
Labels: config.KubePolicyAppLabels,
OwnerReferences: []v1.OwnerReference{
wrc.constructOwner(),
},
},
Webhooks: []admregapi.Webhook{
generateWebhook(
config.ValidatingWebhookName,
config.ValidatingWebhookServicePath,
caData,
true,
wrc.timeoutSeconds,
"*/*",
"*",
"*",
[]admregapi.OperationType{admregapi.Create},
),
},
}
}
func (wrc *WebhookRegistrationClient) contructDebugValidatingWebhookConfig(caData []byte) *admregapi.ValidatingWebhookConfiguration {
url := fmt.Sprintf("https://%s%s", wrc.serverIP, config.ValidatingWebhookServicePath)
glog.V(3).Infof("Debug ValidatingWebhookConfig is registered with url %s\n", url)
return &admregapi.ValidatingWebhookConfiguration{
ObjectMeta: v1.ObjectMeta{
Name: config.ValidatingWebhookConfigurationDebug,
Labels: config.KubePolicyAppLabels,
},
Webhooks: []admregapi.Webhook{
generateDebugWebhook(
config.ValidatingWebhookName,
url,
caData,
true,
wrc.timeoutSeconds,
"*/*",
"*",
"*",
[]admregapi.OperationType{admregapi.Create},
),
},
}
}

View file

@ -12,7 +12,7 @@ import (
)
// HandleMutation handles mutating webhook admission request
func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool, engine.EngineResponse) {
func (ws *WebhookServer) handleMutation(request *v1beta1.AdmissionRequest) (bool, engine.EngineResponse) {
var patches [][]byte
var policyInfos []info.PolicyInfo
var policyStats []policyctr.PolicyStat

View file

@ -0,0 +1,84 @@
package webhooks
import (
"encoding/json"
"fmt"
"strings"
"github.com/golang/glog"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1"
"github.com/nirmata/kyverno/pkg/utils"
v1beta1 "k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (ws *WebhookServer) handlePolicyMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
var policy *kyverno.Policy
raw := request.Object.Raw
//TODO: can this happen? wont this be picked by OpenAPI spec schema ?
if err := json.Unmarshal(raw, &policy); err != nil {
glog.Errorf("Failed to unmarshal policy admission request, err %v\n", err)
return &v1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Message: fmt.Sprintf("failed to default value, check kyverno controller logs for details: %v", err),
},
}
}
// Generate JSON Patches for defaults
patches, updateMsgs := generateJSONPatchesForDefaults(policy)
if patches != nil {
patchType := v1beta1.PatchTypeJSONPatch
glog.V(4).Infof("defaulted values %v policy %s", updateMsgs, policy.Name)
return &v1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{
Message: strings.Join(updateMsgs, "'"),
},
Patch: patches,
PatchType: &patchType,
}
}
glog.V(4).Infof("nothing to default for policy %s", policy.Name)
return &v1beta1.AdmissionResponse{
Allowed: true,
}
}
func generateJSONPatchesForDefaults(policy *kyverno.Policy) ([]byte, []string) {
var patches [][]byte
var updateMsgs []string
// default 'ValidationFailureAction'
if patch, updateMsg := defaultvalidationFailureAction(policy); patch != nil {
patches = append(patches, patch)
updateMsgs = append(updateMsgs, updateMsg)
}
return utils.JoinPatches(patches), updateMsgs
}
func defaultvalidationFailureAction(policy *kyverno.Policy) ([]byte, string) {
// default ValidationFailureAction to "enforce" if not specified
if policy.Spec.ValidationFailureAction == "" {
glog.V(4).Infof("defaulting policy %s 'ValidationFailureAction' to '%s'", policy.Name, BlockChanges)
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
Value string `json:"value"`
}{
"/spec/validationFailureAction",
"add",
BlockChanges, //enforce
}
patchByte, err := json.Marshal(jsonPatch)
if err != nil {
glog.Errorf("failed to set default 'ValidationFailureAction' to '%s' for policy %s", BlockChanges, policy.Name)
return nil, ""
}
glog.V(4).Infof("generate JSON Patch to set default 'ValidationFailureAction' to '%s' for policy %s", BlockChanges, policy.Name)
return patchByte, fmt.Sprintf("default 'ValidationFailureAction' to '%s'", BlockChanges)
}
return nil, ""
}

View file

@ -13,16 +13,13 @@ import (
)
//HandlePolicyValidation performs the validation check on policy resource
func (ws *WebhookServer) HandlePolicyValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) handlePolicyValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
var policy *kyverno.Policy
admissionResp := &v1beta1.AdmissionResponse{
Allowed: true,
}
// nothing to do on DELETE
if request.Operation == v1beta1.Delete {
return admissionResp
}
//TODO: can this happen? wont this be picked by OpenAPI spec schema ?
raw := request.Object.Raw
if err := json.Unmarshal(raw, &policy); err != nil {
glog.Errorf("Failed to unmarshal policy admission request, err %v\n", err)

View file

@ -42,6 +42,7 @@ type WebhookServer struct {
// API to send policy stats for aggregation
policyStatus policy.PolicyStatusInterface
filterK8Resources []utils.K8Resource
cleanUp chan<- struct{}
}
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
@ -55,7 +56,8 @@ func NewWebhookServer(
eventGen event.Interface,
webhookRegistrationClient *webhookconfig.WebhookRegistrationClient,
policyStatus policy.PolicyStatusInterface,
filterK8Resources string) (*WebhookServer, error) {
filterK8Resources string,
cleanUp chan<- struct{}) (*WebhookServer, error) {
if tlsPair == nil {
return nil, errors.New("NewWebhookServer is not initialized properly")
@ -80,11 +82,13 @@ func NewWebhookServer(
webhookRegistrationClient: webhookRegistrationClient,
policyStatus: policyStatus,
filterK8Resources: utils.ParseKinds(filterK8Resources),
cleanUp: cleanUp,
}
mux := http.NewServeMux()
mux.HandleFunc(config.MutatingWebhookServicePath, ws.serve)
mux.HandleFunc(config.ValidatingWebhookServicePath, ws.serve)
mux.HandleFunc(config.PolicyValidatingWebhookServicePath, ws.serve)
mux.HandleFunc(config.PolicyMutatingWebhookServicePath, ws.serve)
ws.server = http.Server{
Addr: ":443", // Listen on port for HTTPS requests
@ -114,9 +118,11 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
// Resource UPDATE
switch r.URL.Path {
case config.MutatingWebhookServicePath:
admissionReview.Response = ws.HandleAdmissionRequest(admissionReview.Request)
admissionReview.Response = ws.handleAdmissionRequest(admissionReview.Request)
case config.PolicyValidatingWebhookServicePath:
admissionReview.Response = ws.HandlePolicyValidation(admissionReview.Request)
admissionReview.Response = ws.handlePolicyValidation(admissionReview.Request)
case config.PolicyMutatingWebhookServicePath:
admissionReview.Response = ws.handlePolicyMutation(admissionReview.Request)
}
}
@ -134,10 +140,10 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
}
}
func (ws *WebhookServer) HandleAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) handleAdmissionRequest(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
var response *v1beta1.AdmissionResponse
allowed, engineResponse := ws.HandleMutation(request)
allowed, engineResponse := ws.handleMutation(request)
if !allowed {
// TODO: add failure message to response
return &v1beta1.AdmissionResponse{
@ -145,7 +151,7 @@ func (ws *WebhookServer) HandleAdmissionRequest(request *v1beta1.AdmissionReques
}
}
response = ws.HandleValidation(request, engineResponse.PatchedResource)
response = ws.handleValidation(request, engineResponse.PatchedResource)
if response.Allowed && len(engineResponse.Patches) > 0 {
patchType := v1beta1.PatchTypeJSONPatch
response.Patch = engine.JoinPatches(engineResponse.Patches)
@ -159,9 +165,8 @@ func (ws *WebhookServer) HandleAdmissionRequest(request *v1beta1.AdmissionReques
func (ws *WebhookServer) RunAsync() {
go func(ws *WebhookServer) {
glog.V(3).Infof("serving on %s\n", ws.server.Addr)
err := ws.server.ListenAndServeTLS("", "")
if err != nil {
glog.Fatalf("error serving TLS: %v\n", err)
if err := ws.server.ListenAndServeTLS("", ""); err != http.ErrServerClosed {
glog.Infof("HTTP server error: %v", err)
}
}(ws)
glog.Info("Started Webhook Server")
@ -175,6 +180,10 @@ func (ws *WebhookServer) Stop() {
glog.Info("Server Shutdown error: ", err)
ws.server.Close()
}
// cleanUp
// remove the static webhookconfigurations for policy CRD
ws.webhookRegistrationClient.RemovePolicyWebhookConfigurations(ws.cleanUp)
}
// bodyToAdmissionReview creates AdmissionReview object from request body

View file

@ -16,7 +16,7 @@ import (
// HandleValidation handles validating webhook admission request
// If there are no errors in validating rule we apply generation rules
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest, resource unstructured.Unstructured) *v1beta1.AdmissionResponse {
func (ws *WebhookServer) handleValidation(request *v1beta1.AdmissionRequest, resource unstructured.Unstructured) *v1beta1.AdmissionResponse {
var policyInfos []info.PolicyInfo
var policyStats []policyctr.PolicyStat
// gather stats from the engine response

View file

@ -33,7 +33,7 @@ func (ws *WebhookServer) registerWebhookConfigurations(policy kyverno.Policy) er
}
if !ws.webhookRegistrationClient.MutationRegistered.IsSet() {
if err := ws.webhookRegistrationClient.RegisterMutatingWebhook(); err != nil {
if err := ws.webhookRegistrationClient.CreateResourceMutatingWebhookConfiguration(); err != nil {
return err
}
glog.Infof("Mutating webhook registered")
@ -47,7 +47,7 @@ func (ws *WebhookServer) deregisterWebhookConfigurations(policy kyverno.Policy)
// deregister webhook if no mutate/validate policy found in cluster
if !HasMutateOrValidatePolicies(policies) {
ws.webhookRegistrationClient.DeregisterMutatingWebhook()
ws.webhookRegistrationClient.RemoveResourceMutatingWebhookConfiguration()
glog.Infoln("Mutating webhook deregistered")
}