mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-31 03:45:17 +00:00
Merge pull request #31 from nirmata/policy-v2
Update project to Policy v2 specification
This commit is contained in:
commit
77a52f4586
26 changed files with 478 additions and 662 deletions
20
Gopkg.toml
20
Gopkg.toml
|
@ -2,23 +2,23 @@ required = ["k8s.io/code-generator/cmd/client-gen"]
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "k8s.io/code-generator"
|
name = "k8s.io/code-generator"
|
||||||
branch = "release-1.10"
|
version = "kubernetes-1.14.1"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "k8s.io/apimachinery"
|
name = "k8s.io/apimachinery"
|
||||||
version = "kubernetes-1.10.4"
|
version = "kubernetes-1.14.1"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "k8s.io/api"
|
name = "k8s.io/api"
|
||||||
version = "kubernetes-1.10.4"
|
version = "kubernetes-1.14.1"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "k8s.io/client-go"
|
name = "k8s.io/client-go"
|
||||||
branch = "release-6.0"
|
version = "kubernetes-1.14.0"
|
||||||
|
|
||||||
[[override]]
|
[[constraint]]
|
||||||
name = "github.com/golang/protobuf"
|
name = "github.com/minio/minio"
|
||||||
version = "v1.2.1"
|
branch = "master"
|
||||||
|
|
||||||
[[override]]
|
[[override]]
|
||||||
name = "github.com/gotestyourself/gotest.tools"
|
name = "github.com/gotestyourself/gotest.tools"
|
||||||
|
@ -28,6 +28,6 @@ required = ["k8s.io/code-generator/cmd/client-gen"]
|
||||||
name = "github.com/evanphx/json-patch"
|
name = "github.com/evanphx/json-patch"
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
|
||||||
[[constraint]]
|
[[override]]
|
||||||
branch = "master"
|
name = "github.com/golang/protobuf"
|
||||||
name = "github.com/minio/minio"
|
version = "v1.2.1"
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
apiVersion: apiextensions.k8s.io/v1beta1
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
kind: CustomResourceDefinition
|
kind: CustomResourceDefinition
|
||||||
metadata:
|
metadata:
|
||||||
name: policies.policy.nirmata.io
|
name: policies.kubepolicy.nirmata.io
|
||||||
spec:
|
spec:
|
||||||
group: policy.nirmata.io
|
group: kubepolicy.nirmata.io
|
||||||
versions:
|
versions:
|
||||||
- name: v1alpha1
|
- name: v1alpha1
|
||||||
served: true
|
served: true
|
||||||
|
@ -20,26 +20,23 @@ spec:
|
||||||
properties:
|
properties:
|
||||||
spec:
|
spec:
|
||||||
required:
|
required:
|
||||||
- failurePolicy
|
|
||||||
- rules
|
- rules
|
||||||
properties:
|
properties:
|
||||||
failurePolicy:
|
|
||||||
type: string
|
|
||||||
enum:
|
|
||||||
- continueOnError
|
|
||||||
- stopOnError
|
|
||||||
rules:
|
rules:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
|
- name
|
||||||
- resource
|
- resource
|
||||||
properties:
|
parameters:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
resource:
|
resource:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- kind
|
- kind
|
||||||
properties:
|
parameters:
|
||||||
kind:
|
kind:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
|
@ -85,66 +82,69 @@ spec:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
patch:
|
mutate:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
overlay:
|
||||||
|
AnyValue: {}
|
||||||
|
patches:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- path
|
||||||
|
- op
|
||||||
|
properties:
|
||||||
|
path:
|
||||||
|
type: string
|
||||||
|
op:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- add
|
||||||
|
- replace
|
||||||
|
- remove
|
||||||
|
value:
|
||||||
|
AnyValue: {}
|
||||||
|
validate:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- pattern
|
||||||
|
properties:
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
pattern:
|
||||||
|
AnyValue: {}
|
||||||
|
generate:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: object
|
type: object
|
||||||
required:
|
required:
|
||||||
- path
|
- kind
|
||||||
- op
|
- name
|
||||||
|
- copyFrom
|
||||||
properties:
|
properties:
|
||||||
path:
|
kind:
|
||||||
type: string
|
type: string
|
||||||
op:
|
name:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
copyFrom:
|
||||||
- add
|
type: object
|
||||||
- replace
|
required:
|
||||||
- remove
|
- namespace
|
||||||
value:
|
- name
|
||||||
AnyValue: {}
|
properties:
|
||||||
configMapGenerator:
|
namespace:
|
||||||
type: object
|
type: string
|
||||||
required:
|
name:
|
||||||
- name
|
type: string
|
||||||
properties:
|
data:
|
||||||
name:
|
type: object
|
||||||
type: string
|
additionalProperties:
|
||||||
copyFrom:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- namespace
|
|
||||||
- name
|
|
||||||
properties:
|
|
||||||
namespace:
|
|
||||||
type: string
|
type: string
|
||||||
name:
|
labels:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
type: string
|
type: string
|
||||||
data:
|
|
||||||
type: object
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
secretGenerator:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- name
|
|
||||||
properties:
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
copyFrom:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- namespace
|
|
||||||
- name
|
|
||||||
properties:
|
|
||||||
namespace:
|
|
||||||
type: string
|
|
||||||
name:
|
|
||||||
type: string
|
|
||||||
data:
|
|
||||||
type: object
|
|
||||||
additionalProperties:
|
|
||||||
type: string
|
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
|
|
|
@ -89,7 +89,7 @@ func (kc *KubeClient) fetchCertificateFromRequest(req *certificates.CertificateS
|
||||||
timeStart := time.Now()
|
timeStart := time.Now()
|
||||||
certClient := kc.client.CertificatesV1beta1().CertificateSigningRequests()
|
certClient := kc.client.CertificatesV1beta1().CertificateSigningRequests()
|
||||||
for time.Now().Sub(timeStart) < time.Duration(maxWaitSeconds)*time.Second {
|
for time.Now().Sub(timeStart) < time.Duration(maxWaitSeconds)*time.Second {
|
||||||
r, err := certClient.Get(req.ObjectMeta.Name, defaultGetOptions())
|
r, err := certClient.Get(req.ObjectMeta.Name, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ const certificateField string = "certificate"
|
||||||
// Reads the pair of TLS certificate and key from the specified secret.
|
// Reads the pair of TLS certificate and key from the specified secret.
|
||||||
func (kc *KubeClient) ReadTlsPair(props utils.TlsCertificateProps) *utils.TlsPemPair {
|
func (kc *KubeClient) ReadTlsPair(props utils.TlsCertificateProps) *utils.TlsPemPair {
|
||||||
name := generateSecretName(props)
|
name := generateSecretName(props)
|
||||||
secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, defaultGetOptions())
|
secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
kc.logger.Printf("Unable to get secret %s/%s: %s", props.Namespace, name, err)
|
kc.logger.Printf("Unable to get secret %s/%s: %s", props.Namespace, name, err)
|
||||||
return nil
|
return nil
|
||||||
|
@ -138,7 +138,7 @@ func (kc *KubeClient) ReadTlsPair(props utils.TlsCertificateProps) *utils.TlsPem
|
||||||
// Updates existing secret or creates new one.
|
// Updates existing secret or creates new one.
|
||||||
func (kc *KubeClient) WriteTlsPair(props utils.TlsCertificateProps, pemPair *utils.TlsPemPair) error {
|
func (kc *KubeClient) WriteTlsPair(props utils.TlsCertificateProps, pemPair *utils.TlsPemPair) error {
|
||||||
name := generateSecretName(props)
|
name := generateSecretName(props)
|
||||||
secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, defaultGetOptions())
|
secret, err := kc.client.CoreV1().Secrets(props.Namespace).Get(name, metav1.GetOptions{})
|
||||||
|
|
||||||
if err == nil { // Update existing secret
|
if err == nil { // Update existing secret
|
||||||
if secret.Data == nil {
|
if secret.Data == nil {
|
||||||
|
|
|
@ -22,8 +22,8 @@ import (
|
||||||
|
|
||||||
// KubeClient is the api-client for core Kubernetes objects
|
// KubeClient is the api-client for core Kubernetes objects
|
||||||
type KubeClient struct {
|
type KubeClient struct {
|
||||||
logger *log.Logger
|
|
||||||
client *kubernetes.Clientset
|
client *kubernetes.Clientset
|
||||||
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks parameters and creates new instance of KubeClient
|
// Checks parameters and creates new instance of KubeClient
|
||||||
|
@ -49,12 +49,9 @@ func (kc *KubeClient) GetEventsInterface(namespace string) event.EventInterface
|
||||||
|
|
||||||
func (kc *KubeClient) GetKubePolicyDeployment() (*apps.Deployment, error) {
|
func (kc *KubeClient) GetKubePolicyDeployment() (*apps.Deployment, error) {
|
||||||
kubePolicyDeployment, err := kc.client.
|
kubePolicyDeployment, err := kc.client.
|
||||||
Apps().
|
AppsV1().
|
||||||
Deployments(config.KubePolicyNamespace).
|
Deployments(config.KubePolicyNamespace).
|
||||||
Get(config.KubePolicyDeploymentName, meta.GetOptions{
|
Get(config.KubePolicyDeploymentName, meta.GetOptions{})
|
||||||
ResourceVersion: "1",
|
|
||||||
IncludeUninitialized: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -65,17 +62,16 @@ func (kc *KubeClient) GetKubePolicyDeployment() (*apps.Deployment, error) {
|
||||||
|
|
||||||
// Generates new ConfigMap in given namespace. If the namespace does not exists yet,
|
// Generates new ConfigMap in given namespace. If the namespace does not exists yet,
|
||||||
// waits until it is created for maximum namespaceCreationMaxWaitTime (see below)
|
// waits until it is created for maximum namespaceCreationMaxWaitTime (see below)
|
||||||
func (kc *KubeClient) GenerateConfigMap(generator types.PolicyConfigGenerator, namespace string) error {
|
func (kc *KubeClient) GenerateConfigMap(generator types.Generation, namespace string) error {
|
||||||
kc.logger.Printf("Preparing to create configmap %s/%s", namespace, generator.Name)
|
kc.logger.Printf("Preparing to create configmap %s/%s", namespace, generator.Name)
|
||||||
configMap := &v1.ConfigMap{}
|
configMap := &v1.ConfigMap{}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if generator.CopyFrom != nil {
|
|
||||||
kc.logger.Printf("Copying data from configmap %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name)
|
kc.logger.Printf("Copying data from configmap %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name)
|
||||||
configMap, err = kc.client.CoreV1().ConfigMaps(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, defaultGetOptions())
|
configMap, err = kc.client.CoreV1().ConfigMaps(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configMap.ObjectMeta = metav1.ObjectMeta{
|
configMap.ObjectMeta = metav1.ObjectMeta{
|
||||||
|
@ -100,17 +96,16 @@ func (kc *KubeClient) GenerateConfigMap(generator types.PolicyConfigGenerator, n
|
||||||
|
|
||||||
// Generates new Secret in given namespace. If the namespace does not exists yet,
|
// Generates new Secret in given namespace. If the namespace does not exists yet,
|
||||||
// waits until it is created for maximum namespaceCreationMaxWaitTime (see below)
|
// waits until it is created for maximum namespaceCreationMaxWaitTime (see below)
|
||||||
func (kc *KubeClient) GenerateSecret(generator types.PolicyConfigGenerator, namespace string) error {
|
func (kc *KubeClient) GenerateSecret(generator types.Generation, namespace string) error {
|
||||||
kc.logger.Printf("Preparing to create secret %s/%s", namespace, generator.Name)
|
kc.logger.Printf("Preparing to create secret %s/%s", namespace, generator.Name)
|
||||||
secret := &v1.Secret{}
|
secret := &v1.Secret{}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if generator.CopyFrom != nil {
|
|
||||||
kc.logger.Printf("Copying data from secret %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name)
|
kc.logger.Printf("Copying data from secret %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name)
|
||||||
secret, err = kc.client.CoreV1().Secrets(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, defaultGetOptions())
|
secret, err = kc.client.CoreV1().Secrets(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, metav1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
secret.ObjectMeta = metav1.ObjectMeta{
|
secret.ObjectMeta = metav1.ObjectMeta{
|
||||||
|
@ -133,13 +128,6 @@ func (kc *KubeClient) GenerateSecret(generator types.PolicyConfigGenerator, name
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultGetOptions() metav1.GetOptions {
|
|
||||||
return metav1.GetOptions{
|
|
||||||
ResourceVersion: "1",
|
|
||||||
IncludeUninitialized: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func defaultDeleteOptions() *metav1.DeleteOptions {
|
func defaultDeleteOptions() *metav1.DeleteOptions {
|
||||||
var deletePeriod int64 = 0
|
var deletePeriod int64 = 0
|
||||||
return &metav1.DeleteOptions{
|
return &metav1.DeleteOptions{
|
||||||
|
@ -156,7 +144,7 @@ func (kc *KubeClient) waitUntilNamespaceIsCreated(name string) error {
|
||||||
|
|
||||||
var lastError error = nil
|
var lastError error = nil
|
||||||
for time.Now().Sub(timeStart) < namespaceCreationMaxWaitTime {
|
for time.Now().Sub(timeStart) < namespaceCreationMaxWaitTime {
|
||||||
_, lastError = kc.client.CoreV1().Namespaces().Get(name, defaultGetOptions())
|
_, lastError = kc.client.CoreV1().Namespaces().Get(name, metav1.GetOptions{})
|
||||||
if lastError == nil {
|
if lastError == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
13
main.go
13
main.go
|
@ -5,9 +5,8 @@ import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/nirmata/kube-policy/kubeclient"
|
"github.com/nirmata/kube-policy/kubeclient"
|
||||||
|
"github.com/nirmata/kube-policy/pkg/webhooks"
|
||||||
"github.com/nirmata/kube-policy/policycontroller"
|
"github.com/nirmata/kube-policy/policycontroller"
|
||||||
"github.com/nirmata/kube-policy/server"
|
|
||||||
"github.com/nirmata/kube-policy/webhooks"
|
|
||||||
|
|
||||||
policyclientset "github.com/nirmata/kube-policy/pkg/client/clientset/versioned"
|
policyclientset "github.com/nirmata/kube-policy/pkg/client/clientset/versioned"
|
||||||
informers "github.com/nirmata/kube-policy/pkg/client/informers/externalversions"
|
informers "github.com/nirmata/kube-policy/pkg/client/informers/externalversions"
|
||||||
|
@ -42,7 +41,7 @@ func main() {
|
||||||
|
|
||||||
//TODO wrap the policyInformer inside a factory
|
//TODO wrap the policyInformer inside a factory
|
||||||
policyInformerFactory := informers.NewSharedInformerFactory(policyClientset, 0)
|
policyInformerFactory := informers.NewSharedInformerFactory(policyClientset, 0)
|
||||||
policyInformer := policyInformerFactory.Nirmata().V1alpha1().Policies()
|
policyInformer := policyInformerFactory.Kubepolicy().V1alpha1().Policies()
|
||||||
|
|
||||||
eventController := event.NewEventController(kubeclient, policyInformer.Lister(), nil)
|
eventController := event.NewEventController(kubeclient, policyInformer.Lister(), nil)
|
||||||
violationBuilder := policyviolation.NewPolicyViolationBuilder(kubeclient, policyInformer.Lister(), policyClientset, eventController, nil)
|
violationBuilder := policyviolation.NewPolicyViolationBuilder(kubeclient, policyInformer.Lister(), policyClientset, eventController, nil)
|
||||||
|
@ -56,12 +55,6 @@ func main() {
|
||||||
nil,
|
nil,
|
||||||
kubeclient)
|
kubeclient)
|
||||||
|
|
||||||
mutationWebhook, err := webhooks.CreateMutationWebhook(clientConfig,
|
|
||||||
kubeclient,
|
|
||||||
policyInformer.Lister(),
|
|
||||||
violationBuilder,
|
|
||||||
eventController,
|
|
||||||
nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Error creating mutation webhook: %v\n", err)
|
log.Fatalf("Error creating mutation webhook: %v\n", err)
|
||||||
}
|
}
|
||||||
|
@ -71,7 +64,7 @@ func main() {
|
||||||
log.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err)
|
log.Fatalf("Failed to initialize TLS key/certificate pair: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
server, err := server.NewWebhookServer(tlsPair, mutationWebhook, nil)
|
server, err := webhooks.NewWebhookServer(tlsPair, kubeclient, policyInformer.Lister(), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Unable to create webhook server: %v\n", err)
|
log.Fatalf("Unable to create webhook server: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package policy
|
package policy
|
||||||
|
|
||||||
const (
|
const (
|
||||||
GroupName = "policy.nirmata.io"
|
// GroupName must be the same as specified in Policy CRD
|
||||||
|
GroupName = "kubepolicy.nirmata.io"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// +k8s:deepcopy-gen=package
|
// +k8s:deepcopy-gen=package
|
||||||
// +groupName=nirmata.io
|
// +groupName=kubepolicy.nirmata.io
|
||||||
|
|
||||||
package v1alpha1
|
package v1alpha1
|
||||||
|
|
|
@ -7,89 +7,92 @@ import (
|
||||||
// +genclient
|
// +genclient
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
// An example of the YAML representation of this structure is here:
|
// Policy contains rules to be applied to created resources
|
||||||
// <project_root>/crd/policy-example.yaml
|
|
||||||
type Policy struct {
|
type Policy struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
Spec PolicySpec `json:"spec"`
|
Spec Spec `json:"spec"`
|
||||||
Status PolicyStatus `json:"status"`
|
Status Status `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specification of the Policy.
|
// Spec describes policy behavior by its rules
|
||||||
// failurePolicy can have values "continueOnError" and "stopOnError" (default).
|
type Spec struct {
|
||||||
type PolicySpec struct {
|
Rules []Rule `json:"rules"`
|
||||||
FailurePolicy *string `json:"failurePolicy"`
|
|
||||||
Rules []PolicyRule `json:"rules"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The rule of mutation for the single resource definition.
|
// Rule is set of mutation, validation and generation actions
|
||||||
// Details are listed in the description of each of the substructures.
|
// for the single resource description
|
||||||
type PolicyRule struct {
|
type Rule struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Resource PolicyResource `json:"resource"`
|
ResourceDescription `json:"resource"`
|
||||||
Patches []PolicyPatch `json:"patch,omitempty"`
|
Mutation *Mutation `json:"mutate"`
|
||||||
ConfigMapGenerator *PolicyConfigGenerator `json:"configMapGenerator,omitempty"`
|
Validation *Validation `json:"validate"`
|
||||||
SecretGenerator *PolicyConfigGenerator `json:"secretGenerator,omitempty"`
|
Generation *Generation `json:"generate"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describes the resource to which the PolicyRule will apply.
|
// ResourceDescription describes the resource to which the PolicyRule will be applied.
|
||||||
// Either the name or selector must be specified.
|
type ResourceDescription struct {
|
||||||
// IMPORTANT: If neither is specified, the policy rule will not apply (TBD).
|
|
||||||
type PolicyResource struct {
|
|
||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
Selector *metav1.LabelSelector `json:"selector,omitempty"`
|
Selector *metav1.LabelSelector `json:"selector"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation describes the way how Mutating Webhook will react on resource creation
|
||||||
|
type Mutation struct {
|
||||||
|
Overlay *interface{} `json:"overlay"`
|
||||||
|
Patches []Patch `json:"patches"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=false
|
// +k8s:deepcopy-gen=false
|
||||||
|
|
||||||
// PolicyPatch declares patch operation for created object according to the JSONPatch spec:
|
// Patch declares patch operation for created object according to RFC 6902
|
||||||
// http://jsonpatch.com/
|
type Patch struct {
|
||||||
type PolicyPatch struct {
|
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Operation string `json:"op"`
|
Operation string `json:"op"`
|
||||||
Value interface{} `json:"value"`
|
Value interface{} `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (in *PolicyPatch) DeepCopyInto(out *PolicyPatch) {
|
// Validation describes the way how Validating Webhook will check the resource on creation
|
||||||
if out != nil {
|
type Validation struct {
|
||||||
*out = *in
|
Message *string `json:"message"`
|
||||||
}
|
Pattern interface{} `json:"pattern"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// The declaration for a Secret or a ConfigMap, which will be created in the new namespace.
|
// Generation describes which resources will be created when other resource is created
|
||||||
// Can be applied only when PolicyRule.Resource.Kind is "Namespace".
|
type Generation struct {
|
||||||
type PolicyConfigGenerator struct {
|
Kind string `json:"kind"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
CopyFrom *PolicyCopyFrom `json:"copyFrom"`
|
CopyFrom `json:"copyFrom"`
|
||||||
Data map[string]string `json:"data"`
|
Data map[string]string `json:"data"`
|
||||||
|
Labels map[string]string `json:"labels"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Location of a Secret or a ConfigMap which will be used as source when applying PolicyConfigGenerator
|
// CopyFrom - location of a Secret or a ConfigMap
|
||||||
type PolicyCopyFrom struct {
|
// which will be used as source when applying 'generate'
|
||||||
|
type CopyFrom struct {
|
||||||
Namespace string `json:"namespace"`
|
Namespace string `json:"namespace"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains logs about policy application
|
// Status contains violations for existing resources
|
||||||
type PolicyStatus struct {
|
type Status struct {
|
||||||
Logs []string `json:"log"`
|
|
||||||
Violations []Violation `json:"violations,omitempty"`
|
Violations []Violation `json:"violations,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
|
||||||
|
|
||||||
// List of Policy resources
|
|
||||||
type PolicyList struct {
|
|
||||||
metav1.TypeMeta `json:",inline"`
|
|
||||||
metav1.ListMeta `json:"metadata"`
|
|
||||||
Items []Policy `json:"items"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Violation for the policy
|
// Violation for the policy
|
||||||
type Violation struct {
|
type Violation struct {
|
||||||
Kind string `json:"kind,omitempty"`
|
Kind string `json:"kind,omitempty"`
|
||||||
Resource string `json:"resource,omitempty"`
|
Resource string `json:"resource,omitempty"`
|
||||||
Rule string `json:"rule,omitempty"`
|
Rule string `json:"rule,omitempty"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
Message string `json:"message,omitempty`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// PolicyList is a list of Policy resources
|
||||||
|
type PolicyList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata"`
|
||||||
|
Items []Policy `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
package v1alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Checks if rule is not empty and all substructures are valid
|
|
||||||
func (pr *PolicyRule) Validate() error {
|
|
||||||
err := pr.Resource.Validate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pr.Patches) == 0 && pr.ConfigMapGenerator == nil && pr.SecretGenerator == nil {
|
|
||||||
return errors.New("The rule is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pr.Patches) > 0 {
|
|
||||||
for _, patch := range pr.Patches {
|
|
||||||
err = patch.Validate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pr.ConfigMapGenerator != nil {
|
|
||||||
err = pr.ConfigMapGenerator.Validate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pr.SecretGenerator != nil {
|
|
||||||
err = pr.SecretGenerator.Validate()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if all necesarry fields are present and have values. Also checks a Selector.
|
|
||||||
// Returns error if resource definition is invalid.
|
|
||||||
func (pr *PolicyResource) Validate() error {
|
|
||||||
// TBD: selector or name MUST be specified
|
|
||||||
if pr.Kind == "" {
|
|
||||||
return errors.New("The Kind is not specified")
|
|
||||||
} else if pr.Name == nil && pr.Selector == nil {
|
|
||||||
return errors.New("Neither Name nor Selector is specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pr.Selector != nil {
|
|
||||||
selector, err := metav1.LabelSelectorAsSelector(pr.Selector)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
requirements, _ := selector.Requirements()
|
|
||||||
if len(requirements) == 0 {
|
|
||||||
return errors.New("The requirements are not specified in selector")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if all mandatory PolicyPatch fields are set
|
|
||||||
func (pp *PolicyPatch) Validate() error {
|
|
||||||
if pp.Path == "" {
|
|
||||||
return errors.New("JSONPatch field 'path' is mandatory")
|
|
||||||
}
|
|
||||||
|
|
||||||
if pp.Operation == "add" || pp.Operation == "replace" {
|
|
||||||
if pp.Value == nil {
|
|
||||||
return errors.New(fmt.Sprintf("JSONPatch field 'value' is mandatory for operation '%s'", pp.Operation))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
} else if pp.Operation == "remove" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New(fmt.Sprintf("Unsupported JSONPatch operation '%s'", pp.Operation))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns error if Name or namespace is not cpecified
|
|
||||||
func (pcf *PolicyCopyFrom) Validate() error {
|
|
||||||
if pcf.Name == "" || pcf.Namespace == "" {
|
|
||||||
return errors.New("Name or/and Namespace is not specified")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns error if generator is configured incompletely
|
|
||||||
func (pcg *PolicyConfigGenerator) Validate() error {
|
|
||||||
if pcg.Name == "" {
|
|
||||||
return errors.New("The generator is unnamed")
|
|
||||||
} else if len(pcg.Data) == 0 && pcg.CopyFrom == nil {
|
|
||||||
return errors.New("Neither Data nor CopyFrom (source) is specified")
|
|
||||||
}
|
|
||||||
if pcg.CopyFrom != nil {
|
|
||||||
return pcg.CopyFrom.Validate()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
106
pkg/apis/policy/v1alpha1/utils.go
Normal file
106
pkg/apis/policy/v1alpha1/utils.go
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
package v1alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Validate checks if rule is not empty and all substructures are valid
|
||||||
|
func (r *Rule) Validate() error {
|
||||||
|
err := r.ResourceDescription.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Mutation == nil && r.Validation == nil && r.Generation == nil {
|
||||||
|
return errors.New("The rule is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate checks if all necesarry fields are present and have values. Also checks a Selector.
|
||||||
|
// Returns error if resource definition is invalid.
|
||||||
|
func (pr *ResourceDescription) Validate() error {
|
||||||
|
// TBD: selector or name MUST be specified
|
||||||
|
if pr.Kind == "" {
|
||||||
|
return errors.New("The Kind is not specified")
|
||||||
|
} else if pr.Name == nil && pr.Selector == nil {
|
||||||
|
return errors.New("Neither Name nor Selector is specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pr.Selector != nil {
|
||||||
|
selector, err := metav1.LabelSelectorAsSelector(pr.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
requirements, _ := selector.Requirements()
|
||||||
|
if len(requirements) == 0 {
|
||||||
|
return errors.New("The requirements are not specified in selector")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate if all mandatory PolicyPatch fields are set
|
||||||
|
func (pp *Patch) Validate() error {
|
||||||
|
if pp.Path == "" {
|
||||||
|
return errors.New("JSONPatch field 'path' is mandatory")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pp.Operation == "add" || pp.Operation == "replace" {
|
||||||
|
if pp.Value == nil {
|
||||||
|
return fmt.Errorf("JSONPatch field 'value' is mandatory for operation '%s'", pp.Operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else if pp.Operation == "remove" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate returns error if Name or namespace is not cpecified
|
||||||
|
func (pcf *CopyFrom) Validate() error {
|
||||||
|
if pcf.Name == "" || pcf.Namespace == "" {
|
||||||
|
return errors.New("Name or/and Namespace is not specified")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate returns error if generator is configured incompletely
|
||||||
|
func (pcg *Generation) Validate() error {
|
||||||
|
if pcg.Name == "" {
|
||||||
|
return errors.New("The generator is unnamed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pcg.CopyFrom.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is declared because k8s:deepcopy-gen is
|
||||||
|
// not able to generate this method for interface{} member
|
||||||
|
func (in *Mutation) DeepCopyInto(out *Mutation) {
|
||||||
|
if out != nil {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is declared because k8s:deepcopy-gen is
|
||||||
|
// not able to generate this method for interface{} member
|
||||||
|
func (pp *Patch) DeepCopyInto(out *Patch) {
|
||||||
|
if out != nil {
|
||||||
|
*out = *pp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is declared because k8s:deepcopy-gen is
|
||||||
|
// not able to generate this method for interface{} member
|
||||||
|
func (in *Validation) DeepCopyInto(out *Validation) {
|
||||||
|
if out != nil {
|
||||||
|
*out = *in
|
||||||
|
}
|
||||||
|
}
|
54
pkg/policyengine/generation.go
Normal file
54
pkg/policyengine/generation.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package policyengine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||||
|
"github.com/nirmata/kube-policy/pkg/policyengine/mutation"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: To be reworked due to spec policy-v2
|
||||||
|
|
||||||
|
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
|
||||||
|
func (p *policyEngine) applyRuleGenerators(rawResource []byte, rule kubepolicy.Rule) error {
|
||||||
|
kind := mutation.ParseKindFromObject(rawResource)
|
||||||
|
|
||||||
|
// configMapGenerator and secretGenerator can be applied only to namespaces
|
||||||
|
if kind == "Namespace" {
|
||||||
|
namespaceName := mutation.ParseNameFromObject(rawResource)
|
||||||
|
|
||||||
|
err := p.applyConfigGenerator(rule.Generation, namespaceName, "ConfigMap")
|
||||||
|
if err == nil {
|
||||||
|
err = p.applyConfigGenerator(rule.Generation, namespaceName, "Secret")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request.
|
||||||
|
func (p *policyEngine) applyConfigGenerator(generator *kubepolicy.Generation, namespace string, configKind string) error {
|
||||||
|
if generator == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := generator.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Generator for '%s' is invalid: %s", configKind, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch configKind {
|
||||||
|
case "ConfigMap":
|
||||||
|
err = p.kubeClient.GenerateConfigMap(*generator, namespace)
|
||||||
|
case "Secret":
|
||||||
|
err = p.kubeClient.GenerateSecret(*generator, namespace)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("Unsupported config Kind '%s'", configKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to apply generator for %s '%s/%s' : %s", configKind, namespace, generator.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,96 +1,61 @@
|
||||||
package policyengine
|
package policyengine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
|
||||||
"github.com/nirmata/kube-policy/pkg/policyengine/mutation"
|
"github.com/nirmata/kube-policy/pkg/policyengine/mutation"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *policyEngine) ProcessMutation(policy types.Policy, rawResource []byte) ([]mutation.PatchBytes, error) {
|
// Mutate performs mutation. Overlay first and then mutation patches
|
||||||
patchingSets := mutation.GetPolicyPatchingSets(policy)
|
func (p *policyEngine) Mutate(policy kubepolicy.Policy, rawResource []byte) []mutation.PatchBytes {
|
||||||
var policyPatches []mutation.PatchBytes
|
var policyPatches []mutation.PatchBytes
|
||||||
|
|
||||||
for ruleIdx, rule := range policy.Spec.Rules {
|
for i, rule := range policy.Spec.Rules {
|
||||||
|
|
||||||
|
// Checks for preconditions
|
||||||
|
// TODO: Rework PolicyEngine interface that it receives not a policy, but mutation object for
|
||||||
|
// Mutate, validation for Validate and so on. It will allow to bring this checks outside of PolicyEngine
|
||||||
|
// to common part as far as they present for all: mutation, validation, generation
|
||||||
|
|
||||||
err := rule.Validate()
|
err := rule.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logger.Printf("Invalid rule detected: #%s in policy %s, err: %v\n", rule.Name, policy.ObjectMeta.Name, err)
|
p.logger.Printf("Rule has invalid structure: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.Resource); !ok {
|
ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.ResourceDescription)
|
||||||
p.logger.Printf("Rule %d of policy %s is not applicable to the request", ruleIdx, policy.Name)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = p.applyRuleGenerators(rawResource, rule)
|
|
||||||
if err != nil && patchingSets == mutation.PatchingSetsStopOnError {
|
|
||||||
return nil, fmt.Errorf("Failed to apply generators from rule #%s: %v", rule.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rulePatchesProcessed, err := mutation.ProcessPatches(rule.Patches, rawResource, patchingSets)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to process patches from rule #%s: %v", rule.Name, err)
|
p.logger.Printf("Rule has invalid data: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if rulePatchesProcessed != nil {
|
if !ok {
|
||||||
policyPatches = append(policyPatches, rulePatchesProcessed...)
|
p.logger.Printf("Rule is not applicable t the request: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||||
p.logger.Printf("Rule %d: prepared %d patches", ruleIdx, len(rulePatchesProcessed))
|
continue
|
||||||
// TODO: add PolicyApplied events per rule for policy and resource
|
|
||||||
} else {
|
|
||||||
p.logger.Printf("Rule %d: no patches prepared", ruleIdx)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// empty patch, return error to deny resource creation
|
// Process Overlay
|
||||||
if policyPatches == nil {
|
|
||||||
return nil, fmt.Errorf("no patches prepared")
|
|
||||||
}
|
|
||||||
|
|
||||||
return policyPatches, nil
|
if rule.Mutation.Overlay != nil {
|
||||||
}
|
overlayPatches, err := mutation.ProcessOverlay(rule.Mutation.Overlay, rawResource)
|
||||||
|
if err != nil {
|
||||||
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
|
p.logger.Printf("Overlay application failed: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||||
func (p *policyEngine) applyRuleGenerators(rawResource []byte, rule types.PolicyRule) error {
|
} else {
|
||||||
kind := mutation.ParseKindFromObject(rawResource)
|
policyPatches = append(policyPatches, overlayPatches...)
|
||||||
|
}
|
||||||
// configMapGenerator and secretGenerator can be applied only to namespaces
|
|
||||||
if kind == "Namespace" {
|
|
||||||
namespaceName := mutation.ParseNameFromObject(rawResource)
|
|
||||||
|
|
||||||
err := p.applyConfigGenerator(rule.ConfigMapGenerator, namespaceName, "ConfigMap")
|
|
||||||
if err == nil {
|
|
||||||
err = p.applyConfigGenerator(rule.SecretGenerator, namespaceName, "Secret")
|
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
// Process Patches
|
||||||
|
|
||||||
|
if rule.Mutation.Patches != nil {
|
||||||
|
processedPatches, err := mutation.ProcessPatches(rule.Mutation.Patches, rawResource)
|
||||||
|
if err != nil {
|
||||||
|
p.logger.Printf("Patches application failed: rule number = %d, rule name = %s in policy %s, err: %v\n", i, rule.Name, policy.ObjectMeta.Name, err)
|
||||||
|
} else {
|
||||||
|
policyPatches = append(policyPatches, processedPatches...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
return policyPatches
|
||||||
|
|
||||||
// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request.
|
|
||||||
func (p *policyEngine) applyConfigGenerator(generator *types.PolicyConfigGenerator, namespace string, configKind string) error {
|
|
||||||
if generator == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := generator.Validate()
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(fmt.Sprintf("Generator for '%s' is invalid: %s", configKind, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch configKind {
|
|
||||||
case "ConfigMap":
|
|
||||||
err = p.kubeClient.GenerateConfigMap(*generator, namespace)
|
|
||||||
case "Secret":
|
|
||||||
err = p.kubeClient.GenerateSecret(*generator, namespace)
|
|
||||||
default:
|
|
||||||
err = errors.New(fmt.Sprintf("Unsupported config Kind '%s'", configKind))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(fmt.Sprintf("Unable to apply generator for %s '%s/%s' : %s", configKind, namespace, generator.Name, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,25 +8,25 @@ import (
|
||||||
|
|
||||||
// kind is the type of object being manipulated
|
// kind is the type of object being manipulated
|
||||||
// Checks requests kind, name and labels to fit the policy
|
// Checks requests kind, name and labels to fit the policy
|
||||||
func IsRuleApplicableToResource(resourceRaw []byte, policyResource types.PolicyResource) (bool, error) {
|
func IsRuleApplicableToResource(resourceRaw []byte, description types.ResourceDescription) (bool, error) {
|
||||||
// kind := ParseKindFromObject(resourceRaw)
|
kind := ParseKindFromObject(resourceRaw)
|
||||||
// if policyResource.Kind != kind {
|
if description.Kind != kind {
|
||||||
// return false, nil
|
return false, nil
|
||||||
// }
|
}
|
||||||
|
|
||||||
if resourceRaw != nil {
|
if resourceRaw != nil {
|
||||||
meta := ParseMetadataFromObject(resourceRaw)
|
meta := ParseMetadataFromObject(resourceRaw)
|
||||||
name := ParseNameFromObject(resourceRaw)
|
name := ParseNameFromObject(resourceRaw)
|
||||||
|
|
||||||
if policyResource.Name != nil {
|
if description.Name != nil {
|
||||||
|
|
||||||
if !wildcard.Match(*policyResource.Name, name) {
|
if !wildcard.Match(*description.Name, name) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if policyResource.Selector != nil {
|
if description.Selector != nil {
|
||||||
selector, err := metav1.LabelSelectorAsSelector(policyResource.Selector)
|
selector, err := metav1.LabelSelectorAsSelector(description.Selector)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
|
|
6
pkg/policyengine/mutation/overlay.go
Normal file
6
pkg/policyengine/mutation/overlay.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package mutation
|
||||||
|
|
||||||
|
func ProcessOverlay(overlay interface{}, rawResource []byte) ([]PatchBytes, error) {
|
||||||
|
// TODO: Overlay to be implemented
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -5,60 +5,37 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
jsonpatch "github.com/evanphx/json-patch"
|
jsonpatch "github.com/evanphx/json-patch"
|
||||||
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||||
)
|
|
||||||
|
|
||||||
type PatchingSets uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
PatchingSetsStopOnError PatchingSets = 0
|
|
||||||
PatchingSetsContinueOnRemoveFailure PatchingSets = 1
|
|
||||||
PatchingSetsContinueAlways PatchingSets = 255
|
|
||||||
|
|
||||||
PatchingSetsDefault PatchingSets = PatchingSetsContinueOnRemoveFailure
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PatchBytes []byte
|
type PatchBytes []byte
|
||||||
|
|
||||||
func GetPolicyPatchingSets(policy types.Policy) PatchingSets {
|
|
||||||
// failurePolicy property is the only available way for now to define behavior on patching error.
|
|
||||||
// TODO: define new failurePolicy values specific for patching and other policy features.
|
|
||||||
if policy.Spec.FailurePolicy != nil && *policy.Spec.FailurePolicy == "continueOnError" {
|
|
||||||
return PatchingSetsContinueAlways
|
|
||||||
}
|
|
||||||
return PatchingSetsDefault
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test patches on given document according to given sets.
|
// Test patches on given document according to given sets.
|
||||||
// Returns array from separate patches that can be applied to the document
|
// Returns array from separate patches that can be applied to the document
|
||||||
// Returns error ONLY in case when creation of resource should be denied.
|
// Returns error ONLY in case when creation of resource should be denied.
|
||||||
func ProcessPatches(patches []types.PolicyPatch, originalDocument []byte, sets PatchingSets) ([]PatchBytes, error) {
|
func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes, error) {
|
||||||
if len(originalDocument) == 0 {
|
if len(resource) == 0 {
|
||||||
return nil, errors.New("Source document for patching is empty")
|
return nil, errors.New("Source document for patching is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
var appliedPatches []PatchBytes
|
var appliedPatches []PatchBytes
|
||||||
patchedDocument := originalDocument
|
|
||||||
for _, patch := range patches {
|
for _, patch := range patches {
|
||||||
patchBytes, err := json.Marshal(patch)
|
patchRaw, err := json.Marshal(patch)
|
||||||
if err != nil && sets == PatchingSetsStopOnError {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
patchedDocument, err = CheckPatch(patchedDocument, patchBytes)
|
_, err = applyPatch(resource, patchRaw)
|
||||||
if err != nil { // Ignore errors on "remove" operations
|
if err != nil {
|
||||||
if sets == PatchingSetsContinueOnRemoveFailure && patch.Operation == "remove" {
|
return nil, err
|
||||||
continue
|
|
||||||
} else if sets != PatchingSetsContinueAlways {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else { // In any case we should collect only valid patches
|
|
||||||
appliedPatches = append(appliedPatches, patchBytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
appliedPatches = append(appliedPatches, patchRaw)
|
||||||
}
|
}
|
||||||
return appliedPatches, nil
|
return appliedPatches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Joins array of serialized JSON patches to the single JSONPatch array
|
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array
|
||||||
func JoinPatches(patches []PatchBytes) PatchBytes {
|
func JoinPatches(patches []PatchBytes) PatchBytes {
|
||||||
var result PatchBytes
|
var result PatchBytes
|
||||||
if len(patches) == 0 {
|
if len(patches) == 0 {
|
||||||
|
@ -76,19 +53,15 @@ func JoinPatches(patches []PatchBytes) PatchBytes {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks patch for document, returns patched document.
|
// ApplyPatch applies patch for resource, returns patched resource.
|
||||||
// On error returns original document and error.
|
func applyPatch(resource []byte, patchRaw []byte) ([]byte, error) {
|
||||||
func CheckPatch(document []byte, patchRaw []byte) (PatchBytes, error) {
|
|
||||||
patchRaw = append([]byte{'['}, patchRaw...) // push [ forward
|
patchRaw = append([]byte{'['}, patchRaw...) // push [ forward
|
||||||
patchRaw = append(patchRaw, ']') // push ] back
|
patchRaw = append(patchRaw, ']') // push ] back
|
||||||
|
|
||||||
patch, err := jsonpatch.DecodePatch(patchRaw)
|
patch, err := jsonpatch.DecodePatch(patchRaw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return document, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
patchedDocument, err := patch.Apply(document)
|
return patch.Apply(resource)
|
||||||
if err != nil {
|
|
||||||
return document, err
|
|
||||||
}
|
|
||||||
return patchedDocument, err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,16 +15,20 @@ type PolicyEngine interface {
|
||||||
// ProcessMutation should be called from admission contoller
|
// ProcessMutation should be called from admission contoller
|
||||||
// when there is an creation / update of the resource
|
// when there is an creation / update of the resource
|
||||||
// ProcessMutation(policy types.Policy, rawResource []byte) (patchBytes []byte, events []Events, err error)
|
// ProcessMutation(policy types.Policy, rawResource []byte) (patchBytes []byte, events []Events, err error)
|
||||||
ProcessMutation(policy types.Policy, rawResource []byte) ([]mutation.PatchBytes, error)
|
Mutate(policy types.Policy, rawResource []byte) []mutation.PatchBytes
|
||||||
|
|
||||||
// ProcessValidation should be called from admission contoller
|
// ProcessValidation should be called from admission contoller
|
||||||
// when there is an creation / update of the resource
|
// when there is an creation / update of the resource
|
||||||
|
// TODO: Change name to Validate
|
||||||
ProcessValidation(policy types.Policy, rawResource []byte)
|
ProcessValidation(policy types.Policy, rawResource []byte)
|
||||||
|
|
||||||
// ProcessExisting should be called from policy controller
|
// ProcessExisting should be called from policy controller
|
||||||
// when there is an create / update of the policy
|
// when there is an create / update of the policy
|
||||||
// we should process the policy on matched resources, generate violations accordingly
|
// we should process the policy on matched resources, generate violations accordingly
|
||||||
|
// TODO: This method should not be in PolicyEngine. Validate will do this work instead
|
||||||
ProcessExisting(policy types.Policy, rawResource []byte) ([]policyviolation.Info, []event.Info, error)
|
ProcessExisting(policy types.Policy, rawResource []byte) ([]policyviolation.Info, []event.Info, error)
|
||||||
|
|
||||||
|
// TODO: Add Generate method
|
||||||
}
|
}
|
||||||
|
|
||||||
type policyEngine struct {
|
type policyEngine struct {
|
||||||
|
@ -43,8 +47,6 @@ func (p *policyEngine) ProcessExisting(policy types.Policy, rawResource []byte)
|
||||||
var violations []policyviolation.Info
|
var violations []policyviolation.Info
|
||||||
var events []event.Info
|
var events []event.Info
|
||||||
|
|
||||||
patchingSets := mutation.GetPolicyPatchingSets(policy)
|
|
||||||
|
|
||||||
for _, rule := range policy.Spec.Rules {
|
for _, rule := range policy.Spec.Rules {
|
||||||
err := rule.Validate()
|
err := rule.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -52,12 +54,12 @@ func (p *policyEngine) ProcessExisting(policy types.Policy, rawResource []byte)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.Resource); !ok {
|
if ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.ResourceDescription); !ok {
|
||||||
p.logger.Printf("Rule %s of policy %s is not applicable to the request", rule.Name, policy.Name)
|
p.logger.Printf("Rule %s of policy %s is not applicable to the request", rule.Name, policy.Name)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
violation, eventInfos, err := p.processRuleOnResource(policy.Name, rule, rawResource, patchingSets)
|
violation, eventInfos, err := p.processRuleOnResource(policy.Name, rule, rawResource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logger.Printf("Failed to process rule %s, err: %v\n", rule.Name, err)
|
p.logger.Printf("Failed to process rule %s, err: %v\n", rule.Name, err)
|
||||||
continue
|
continue
|
||||||
|
@ -71,7 +73,7 @@ func (p *policyEngine) ProcessExisting(policy types.Policy, rawResource []byte)
|
||||||
return violations, events, nil
|
return violations, events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *policyEngine) processRuleOnResource(policyName string, rule types.PolicyRule, rawResource []byte, patchingSets mutation.PatchingSets) (
|
func (p *policyEngine) processRuleOnResource(policyName string, rule types.Rule, rawResource []byte) (
|
||||||
policyviolation.Info, []event.Info, error) {
|
policyviolation.Info, []event.Info, error) {
|
||||||
|
|
||||||
var violationInfo policyviolation.Info
|
var violationInfo policyviolation.Info
|
||||||
|
@ -81,7 +83,7 @@ func (p *policyEngine) processRuleOnResource(policyName string, rule types.Polic
|
||||||
resourceName := mutation.ParseNameFromObject(rawResource)
|
resourceName := mutation.ParseNameFromObject(rawResource)
|
||||||
resourceNamespace := mutation.ParseNamespaceFromObject(rawResource)
|
resourceNamespace := mutation.ParseNamespaceFromObject(rawResource)
|
||||||
|
|
||||||
rulePatchesProcessed, err := mutation.ProcessPatches(rule.Patches, nil, patchingSets)
|
rulePatchesProcessed, err := mutation.ProcessPatches(rule.Mutation.Patches, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return violationInfo, eventInfos, fmt.Errorf("Failed to process patches from rule %s: %v", rule.Name, err)
|
return violationInfo, eventInfos, fmt.Errorf("Failed to process patches from rule %s: %v", rule.Name, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,7 +89,7 @@ func (b *builder) processViolation(info Info) error {
|
||||||
|
|
||||||
modifiedPolicy.Status.Violations = modifiedViolations
|
modifiedPolicy.Status.Violations = modifiedViolations
|
||||||
// Violations are part of the status sub resource, so we can use the Update Status api instead of updating the policy object
|
// Violations are part of the status sub resource, so we can use the Update Status api instead of updating the policy object
|
||||||
_, err = b.policyInterface.NirmataV1alpha1().Policies(namespace).UpdateStatus(modifiedPolicy)
|
_, err = b.policyInterface.KubepolicyV1alpha1().Policies(namespace).UpdateStatus(modifiedPolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package server
|
package webhooks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -10,31 +10,42 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/nirmata/kube-policy/config"
|
"github.com/nirmata/kube-policy/config"
|
||||||
|
"github.com/nirmata/kube-policy/kubeclient"
|
||||||
|
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||||
|
policylister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1"
|
||||||
|
"github.com/nirmata/kube-policy/pkg/policyengine"
|
||||||
|
"github.com/nirmata/kube-policy/pkg/policyengine/mutation"
|
||||||
"github.com/nirmata/kube-policy/utils"
|
"github.com/nirmata/kube-policy/utils"
|
||||||
"github.com/nirmata/kube-policy/webhooks"
|
|
||||||
|
|
||||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WebhookServer contains configured TLS server with MutationWebhook.
|
// WebhookServer contains configured TLS server with MutationWebhook.
|
||||||
// MutationWebhook gets policies from policyController and takes control of the cluster with kubeclient.
|
// MutationWebhook gets policies from policyController and takes control of the cluster with kubeclient.
|
||||||
type WebhookServer struct {
|
type WebhookServer struct {
|
||||||
server http.Server
|
server http.Server
|
||||||
mutationWebhook *webhooks.MutationWebhook
|
policyEngine policyengine.PolicyEngine
|
||||||
logger *log.Logger
|
policyLister policylister.PolicyLister
|
||||||
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
|
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
|
||||||
// Policy Controller and Kubernetes Client should be initialized in configuration
|
// Policy Controller and Kubernetes Client should be initialized in configuration
|
||||||
func NewWebhookServer(tlsPair *utils.TlsPemPair, mutationWebhook *webhooks.MutationWebhook, logger *log.Logger) (*WebhookServer, error) {
|
func NewWebhookServer(
|
||||||
|
tlsPair *utils.TlsPemPair,
|
||||||
|
kubeclient *kubeclient.KubeClient,
|
||||||
|
policyLister policylister.PolicyLister,
|
||||||
|
logger *log.Logger) (*WebhookServer, error) {
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
logger = log.New(os.Stdout, "HTTPS Server: ", log.LstdFlags|log.Lshortfile)
|
logger = log.New(os.Stdout, "HTTPS Server: ", log.LstdFlags|log.Lshortfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsPair == nil || mutationWebhook == nil {
|
if tlsPair == nil {
|
||||||
return nil, errors.New("NewWebhookServer is not initialized properly")
|
return nil, errors.New("NewWebhookServer is not initialized properly")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,10 +55,12 @@ func NewWebhookServer(tlsPair *utils.TlsPemPair, mutationWebhook *webhooks.Mutat
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
tlsConfig.Certificates = []tls.Certificate{pair}
|
tlsConfig.Certificates = []tls.Certificate{pair}
|
||||||
|
policyEngine := policyengine.NewPolicyEngine(kubeclient, logger)
|
||||||
|
|
||||||
ws := &WebhookServer{
|
ws := &WebhookServer{
|
||||||
logger: logger,
|
policyEngine: policyEngine,
|
||||||
mutationWebhook: mutationWebhook,
|
policyLister: policyLister,
|
||||||
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
@ -74,8 +87,8 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var admissionResponse *v1beta1.AdmissionResponse
|
var admissionResponse *v1beta1.AdmissionResponse
|
||||||
if webhooks.AdmissionIsRequired(admissionReview.Request) {
|
if AdmissionIsRequired(admissionReview.Request) {
|
||||||
admissionResponse = ws.mutationWebhook.Mutate(admissionReview.Request)
|
admissionResponse = ws.Mutate(admissionReview.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
if admissionResponse == nil {
|
if admissionResponse == nil {
|
||||||
|
@ -154,3 +167,57 @@ func (ws *WebhookServer) Stop() {
|
||||||
ws.server.Close()
|
ws.server.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *WebhookServer) Mutate(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
|
||||||
|
ws.logger.Printf("AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v patchOperation=%v UserInfo=%v",
|
||||||
|
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation, request.UserInfo)
|
||||||
|
|
||||||
|
policies, err := ws.getPolicies()
|
||||||
|
if err != nil {
|
||||||
|
utilruntime.HandleError(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(policies) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var allPatches []mutation.PatchBytes
|
||||||
|
for _, policy := range policies {
|
||||||
|
ws.logger.Printf("Applying policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules))
|
||||||
|
|
||||||
|
policyPatches := ws.policyEngine.Mutate(policy, request.Object.Raw)
|
||||||
|
allPatches = append(allPatches, policyPatches...)
|
||||||
|
|
||||||
|
if len(policyPatches) > 0 {
|
||||||
|
namespace := mutation.ParseNamespaceFromObject(request.Object.Raw)
|
||||||
|
name := mutation.ParseNameFromObject(request.Object.Raw)
|
||||||
|
ws.logger.Printf("Policy %s applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
patchType := v1beta1.PatchTypeJSONPatch
|
||||||
|
return &v1beta1.AdmissionResponse{
|
||||||
|
Allowed: true,
|
||||||
|
Patch: mutation.JoinPatches(allPatches),
|
||||||
|
PatchType: &patchType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *WebhookServer) getPolicies() ([]kubepolicy.Policy, error) {
|
||||||
|
selector := labels.NewSelector()
|
||||||
|
cachedPolicies, err := ws.policyLister.List(selector)
|
||||||
|
if err != nil {
|
||||||
|
ws.logger.Printf("Error: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var policies []kubepolicy.Policy
|
||||||
|
for _, elem := range cachedPolicies {
|
||||||
|
policies = append(policies, *elem.DeepCopy())
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(policies, func(i, j int) bool {
|
||||||
|
return policies[i].CreationTimestamp.Time.Before(policies[j].CreationTimestamp.Time)
|
||||||
|
})
|
||||||
|
return policies, nil
|
||||||
|
}
|
|
@ -23,6 +23,6 @@ func AdmissionIsRequired(request *v1beta1.AdmissionRequest) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks requests kind, name and labels to fit the policy
|
// Checks requests kind, name and labels to fit the policy
|
||||||
func IsRuleApplicableToRequest(policyResource types.PolicyResource, request *v1beta1.AdmissionRequest) (bool, error) {
|
func IsRuleApplicableToRequest(policyResource types.ResourceDescription, request *v1beta1.AdmissionRequest) (bool, error) {
|
||||||
return mutation.IsRuleApplicableToResource(request.Object.Raw, policyResource)
|
return mutation.IsRuleApplicableToResource(request.Object.Raw, policyResource)
|
||||||
}
|
}
|
|
@ -70,7 +70,7 @@ func (pc *PolicyController) processPolicy(policy types.Policy) (
|
||||||
return violations, events, nil
|
return violations, events, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pc *PolicyController) filterResourceByRule(rule types.PolicyRule) ([]runtime.Object, error) {
|
func (pc *PolicyController) filterResourceByRule(rule types.Rule) ([]runtime.Object, error) {
|
||||||
var targetResources []runtime.Object
|
var targetResources []runtime.Object
|
||||||
// TODO: make this namespace all
|
// TODO: make this namespace all
|
||||||
var namespace = "default"
|
var namespace = "default"
|
||||||
|
@ -79,7 +79,7 @@ func (pc *PolicyController) filterResourceByRule(rule types.PolicyRule) ([]runti
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the resource list from kind
|
// Get the resource list from kind
|
||||||
resources, err := pc.kubeClient.ListResource(rule.Resource.Kind, namespace)
|
resources, err := pc.kubeClient.ListResource(rule.ResourceDescription.Kind, namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ func (pc *PolicyController) filterResourceByRule(rule types.PolicyRule) ([]runti
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter the resource by name and label
|
// filter the resource by name and label
|
||||||
if ok, _ := mutation.IsRuleApplicableToResource(rawResource, rule.Resource); ok {
|
if ok, _ := mutation.IsRuleApplicableToResource(rawResource, rule.ResourceDescription); ok {
|
||||||
targetResources = append(targetResources, resource)
|
targetResources = append(targetResources, resource)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,15 @@ set -o errexit
|
||||||
set -o nounset
|
set -o nounset
|
||||||
set -o pipefail
|
set -o pipefail
|
||||||
|
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Linux*) linkutil=readlink;;
|
||||||
|
Darwin*) linkutil=greadlink;;
|
||||||
|
*) machine="UNKNOWN:${unameOut}"
|
||||||
|
esac
|
||||||
|
|
||||||
# get nirmata root
|
# get nirmata root
|
||||||
NIRMATA_DIR=$(dirname ${BASH_SOURCE})/..
|
NIRMATA_DIR=$(dirname ${BASH_SOURCE})/..
|
||||||
NIRMATA_ROOT=$(greadlink -f ${NIRMATA_DIR})
|
NIRMATA_ROOT=$(${linkutil} -f ${NIRMATA_DIR})
|
||||||
|
|
||||||
# get relative path to code generation script
|
# get relative path to code generation script
|
||||||
CODEGEN_PKG=${NIRMATA_DIR}/vendor/k8s.io/code-generator
|
CODEGEN_PKG=${NIRMATA_DIR}/vendor/k8s.io/code-generator
|
||||||
|
|
|
@ -1,239 +0,0 @@
|
||||||
package webhooks
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
kubeclient "github.com/nirmata/kube-policy/kubeclient"
|
|
||||||
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
|
||||||
policylister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1"
|
|
||||||
event "github.com/nirmata/kube-policy/pkg/event"
|
|
||||||
policyengine "github.com/nirmata/kube-policy/pkg/policyengine"
|
|
||||||
mutation "github.com/nirmata/kube-policy/pkg/policyengine/mutation"
|
|
||||||
policyviolation "github.com/nirmata/kube-policy/pkg/policyviolation"
|
|
||||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
|
||||||
rest "k8s.io/client-go/rest"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MutationWebhook is a data type that represents
|
|
||||||
// business logic for resource mutation
|
|
||||||
type MutationWebhook struct {
|
|
||||||
kubeclient *kubeclient.KubeClient
|
|
||||||
policyEngine policyengine.PolicyEngine
|
|
||||||
policyLister policylister.PolicyLister
|
|
||||||
registration *MutationWebhookRegistration
|
|
||||||
violationBuilder policyviolation.Generator
|
|
||||||
eventBuilder event.Generator
|
|
||||||
logger *log.Logger
|
|
||||||
}
|
|
||||||
|
|
||||||
// Registers mutation webhook in cluster and creates object for this webhook
|
|
||||||
func CreateMutationWebhook(
|
|
||||||
clientConfig *rest.Config,
|
|
||||||
kubeclient *kubeclient.KubeClient,
|
|
||||||
policyLister policylister.PolicyLister,
|
|
||||||
violationBuilder policyviolation.Generator,
|
|
||||||
eventController event.Generator,
|
|
||||||
logger *log.Logger) (*MutationWebhook, error) {
|
|
||||||
if clientConfig == nil || kubeclient == nil {
|
|
||||||
return nil, errors.New("Some parameters are not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
registration, err := NewMutationWebhookRegistration(clientConfig, kubeclient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = registration.Register()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if logger == nil {
|
|
||||||
logger = log.New(os.Stdout, "Mutation WebHook: ", log.LstdFlags|log.Lshortfile)
|
|
||||||
}
|
|
||||||
policyengine := policyengine.NewPolicyEngine(kubeclient, logger)
|
|
||||||
|
|
||||||
return &MutationWebhook{
|
|
||||||
kubeclient: kubeclient,
|
|
||||||
policyEngine: policyengine,
|
|
||||||
policyLister: policyLister,
|
|
||||||
registration: registration,
|
|
||||||
violationBuilder: violationBuilder,
|
|
||||||
eventBuilder: eventController,
|
|
||||||
logger: logger,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mw *MutationWebhook) getPolicies() ([]types.Policy, error) {
|
|
||||||
selector := labels.NewSelector()
|
|
||||||
cachedPolicies, err := mw.policyLister.List(selector)
|
|
||||||
if err != nil {
|
|
||||||
mw.logger.Printf("Error: %v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var policies []types.Policy
|
|
||||||
for _, elem := range cachedPolicies {
|
|
||||||
policies = append(policies, *elem.DeepCopy())
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(policies, func(i, j int) bool {
|
|
||||||
return policies[i].CreationTimestamp.Time.Before(policies[j].CreationTimestamp.Time)
|
|
||||||
})
|
|
||||||
return policies, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mutate applies admission to request
|
|
||||||
func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
|
|
||||||
mw.logger.Printf("AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v patchOperation=%v UserInfo=%v",
|
|
||||||
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation, request.UserInfo)
|
|
||||||
|
|
||||||
policies, err := mw.getPolicies()
|
|
||||||
if err != nil {
|
|
||||||
utilruntime.HandleError(err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if len(policies) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var allPatches []mutation.PatchBytes
|
|
||||||
for _, policy := range policies {
|
|
||||||
mw.logger.Printf("Applying policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules))
|
|
||||||
|
|
||||||
policyPatches, err := mw.applyPolicyRules(request, policy)
|
|
||||||
if err != nil {
|
|
||||||
//TODO Log Policy Error
|
|
||||||
|
|
||||||
errStr := fmt.Sprintf("Unable to apply policy %s: %v", policy.Name, err)
|
|
||||||
mw.logger.Printf("Denying the request because of error: %s", errStr)
|
|
||||||
return mw.denyResourceCreation(errStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(policyPatches) > 0 {
|
|
||||||
namespace := mutation.ParseNamespaceFromObject(request.Object.Raw)
|
|
||||||
name := mutation.ParseNameFromObject(request.Object.Raw)
|
|
||||||
//TODO Log Policy Info
|
|
||||||
mw.logger.Printf("%s applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name)
|
|
||||||
|
|
||||||
allPatches = append(allPatches, policyPatches...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
patchType := v1beta1.PatchTypeJSONPatch
|
|
||||||
return &v1beta1.AdmissionResponse{
|
|
||||||
Allowed: true,
|
|
||||||
Patch: mutation.JoinPatches(allPatches),
|
|
||||||
PatchType: &patchType,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Applies all policy rules to the created object and returns list of processed JSON patches.
|
|
||||||
// May return nil patches if it is not necessary to create patches for requested object.
|
|
||||||
// Returns error ONLY in case when creation of resource should be denied.
|
|
||||||
func (mw *MutationWebhook) applyPolicyRules(request *v1beta1.AdmissionRequest, policy types.Policy) ([]mutation.PatchBytes, error) {
|
|
||||||
return mw.policyEngine.ProcessMutation(policy, request.Object.Raw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// kind is the type of object being manipulated, e.g. request.Kind.kind
|
|
||||||
func (mw *MutationWebhook) applyPolicyRulesOnResource(kind string, rawResource []byte, policy types.Policy) ([]mutation.PatchBytes, error) {
|
|
||||||
patchingSets := mutation.GetPolicyPatchingSets(policy)
|
|
||||||
var policyPatches []mutation.PatchBytes
|
|
||||||
|
|
||||||
for ruleIdx, rule := range policy.Spec.Rules {
|
|
||||||
err := rule.Validate()
|
|
||||||
if err != nil {
|
|
||||||
mw.logger.Printf("Invalid rule detected: #%d in policy %s, err: %v\n", ruleIdx, policy.ObjectMeta.Name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, err := mutation.IsRuleApplicableToResource(rawResource, rule.Resource); !ok {
|
|
||||||
mw.logger.Printf("Rule %d of policy %s is not applicable to the request", ruleIdx, policy.Name)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// configMapGenerator and secretGenerator can be applied only to namespaces
|
|
||||||
if kind == "Namespace" {
|
|
||||||
err = mw.applyRuleGenerators(rawResource, rule)
|
|
||||||
if err != nil && patchingSets == mutation.PatchingSetsStopOnError {
|
|
||||||
return nil, fmt.Errorf("Failed to apply generators from rule #%d: %s", ruleIdx, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rulePatchesProcessed, err := mutation.ProcessPatches(rule.Patches, rawResource, patchingSets)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("Failed to process patches from rule #%d: %s", ruleIdx, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rulePatchesProcessed != nil {
|
|
||||||
policyPatches = append(policyPatches, rulePatchesProcessed...)
|
|
||||||
mw.logger.Printf("Rule %d: prepared %d patches", ruleIdx, len(rulePatchesProcessed))
|
|
||||||
} else {
|
|
||||||
mw.logger.Printf("Rule %d: no patches prepared", ruleIdx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// empty patch, return error to deny resource creation
|
|
||||||
if policyPatches == nil {
|
|
||||||
return nil, fmt.Errorf("no patches prepared")
|
|
||||||
}
|
|
||||||
|
|
||||||
return policyPatches, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
|
|
||||||
func (mw *MutationWebhook) applyRuleGenerators(rawResource []byte, rule types.PolicyRule) error {
|
|
||||||
namespaceName := mutation.ParseNameFromObject(rawResource)
|
|
||||||
|
|
||||||
err := mw.applyConfigGenerator(rule.ConfigMapGenerator, namespaceName, "ConfigMap")
|
|
||||||
if err == nil {
|
|
||||||
err = mw.applyConfigGenerator(rule.SecretGenerator, namespaceName, "Secret")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates resourceKind (ConfigMap or Secret) with parameters specified in generator in cluster specified in request.
|
|
||||||
func (mw *MutationWebhook) applyConfigGenerator(generator *types.PolicyConfigGenerator, namespace string, configKind string) error {
|
|
||||||
if generator == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err := generator.Validate()
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(fmt.Sprintf("Generator for '%s' is invalid: %s", configKind, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch configKind {
|
|
||||||
case "ConfigMap":
|
|
||||||
err = mw.kubeclient.GenerateConfigMap(*generator, namespace)
|
|
||||||
case "Secret":
|
|
||||||
err = mw.kubeclient.GenerateSecret(*generator, namespace)
|
|
||||||
default:
|
|
||||||
err = errors.New(fmt.Sprintf("Unsupported config Kind '%s'", configKind))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(fmt.Sprintf("Unable to apply generator for %s '%s/%s' : %s", configKind, namespace, generator.Name, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Forms AdmissionResponse with denial of resource creation and error message
|
|
||||||
func (mw *MutationWebhook) denyResourceCreation(errStr string) *v1beta1.AdmissionResponse {
|
|
||||||
return &v1beta1.AdmissionResponse{
|
|
||||||
Result: &metav1.Status{
|
|
||||||
Message: errStr,
|
|
||||||
},
|
|
||||||
Allowed: false,
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue