1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-31 03:45:17 +00:00

Merge pull request #45 from nirmata/28-Stateless-policy-engine

28 stateless policy engine
This commit is contained in:
Shivkumar Dudhani 2019-05-17 14:39:16 -07:00 committed by GitHub
commit 4b380f758f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 145 additions and 106 deletions

View file

@ -29,14 +29,14 @@ spec:
required: required:
- name - name
- resource - resource
parameters: properties:
name: name:
type: string type: string
resource: resource:
type: object type: object
required: required:
- kind - kind
parameters: properties:
kind: kind:
type: string type: string
enum: enum:
@ -115,36 +115,33 @@ spec:
pattern: pattern:
AnyValue: {} AnyValue: {}
generate: generate:
type: array type: object
items: required:
type: object - kind
required: - name
- kind properties:
- name kind:
- copyFrom type: string
properties: name:
kind: type: string
type: string copyFrom:
name: type: object
type: string required:
copyFrom: - namespace
type: object - name
required: properties:
- namespace namespace:
- name
properties:
namespace:
type: string
name:
type: string
data:
type: object
additionalProperties:
type: string type: string
labels: name:
type: object
additionalProperties:
type: string type: string
data:
type: object
additionalProperties:
type: string
labels:
type: object
additionalProperties:
type: string
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service

View file

@ -4,15 +4,17 @@ metadata :
name: "policy-configmapgenerator-test" name: "policy-configmapgenerator-test"
spec: spec:
rules: rules:
- name: "Policy ConfigMap sample rule" - name: "copyCM"
resource: resource :
kind : Namespace kind : Namespace
name: "ns2" selector:
generate: matchLabels:
LabelForSelector : "namespace2"
generate :
kind: ConfigMap kind: ConfigMap
name: copied-cm name : copied-cm
copyFrom: copyFrom :
namespace: default namespace : default
name: game-config name : game-config
data: data :
secretData: "data from cmg" secretData: "data from cmg"

View file

@ -3,42 +3,47 @@
# To apply this policy you need to create secret and configMap in "default" namespace # To apply this policy you need to create secret and configMap in "default" namespace
# and then create a namespace # and then create a namespace
apiVersion : policy.nirmata.io/v1alpha1 apiVersion : kubepolicy.nirmata.io/v1alpha1
kind : Policy kind : Policy
metadata : metadata :
name : "policy-ns-patch-cmg-sg" name : "policy-ns-patch-cmg-sg"
spec : spec :
failurePolicy: stopOnError
rules: rules:
- resource : - name: "patchNamespace2"
resource :
kind : Namespace kind : Namespace
selector: selector:
matchLabels: matchLabels:
LabelForSelector : "namespace2" LabelForSelector : "namespace2"
patch: mutate:
- path: "/metadata/labels/isMutatedByPolicy" patches:
op: add - path: "/metadata/labels/isMutatedByPolicy"
value: "true" op: add
value: "true"
- resource : - name: "copyCM"
resource :
kind : Namespace kind : Namespace
selector: selector:
matchLabels: matchLabels:
LabelForSelector : "namespace2" LabelForSelector : "namespace2"
configMapGenerator : generate :
kind: ConfigMap
name : copied-cm name : copied-cm
copyFrom : copyFrom :
namespace : default namespace : default
name : game-config name : game-config
data : data :
secretData: "data from cmg" secretData: "data from cmg"
- resource : - name: "generateCM"
resource :
kind : Namespace kind : Namespace
selector: selector:
matchLabels: matchLabels:
LabelForSelector : "namespace2" LabelForSelector : "namespace2"
configMapGenerator : generate :
kind: ConfigMap
name : generated-cm name : generated-cm
data : data :
secretData: "very sensitive data from cmg" secretData: "very sensitive data from cmg"
@ -49,13 +54,12 @@ spec :
image.public.key=771 image.public.key=771
rsa.public.key=42 rsa.public.key=42
- resource : - name: "generateSecret"
resource :
kind : Namespace kind : Namespace
selector: name: ns2
matchLabels: generate :
LabelForSelector : "namespace2" kind: Secret
secretGenerator :
name : generated-secrets name : generated-secrets
data : data :
foo : bar foo : bar
@ -66,13 +70,12 @@ spec :
foo1=bar1 foo1=bar1
foo2=bar2 foo2=bar2
- resource : - name: "copySecret"
resource :
kind : Namespace kind : Namespace
selector: name: ns2
matchLabels: generate :
LabelForSelector : "namespace2" kind: Secret
secretGenerator :
name : copied-secrets name : copied-secrets
copyFrom : copyFrom :
namespace : default namespace : default

View file

@ -5,8 +5,8 @@ import (
"log" "log"
"net/url" "net/url"
"github.com/nirmata/kube-policy/config"
"github.com/nirmata/kube-policy/kubeclient" "github.com/nirmata/kube-policy/kubeclient"
"github.com/nirmata/kube-policy/pkg/config"
"github.com/nirmata/kube-policy/pkg/tls" "github.com/nirmata/kube-policy/pkg/tls"
rest "k8s.io/client-go/rest" rest "k8s.io/client-go/rest"

View file

@ -6,8 +6,8 @@ import (
"os" "os"
"time" "time"
"github.com/nirmata/kube-policy/config"
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kube-policy/pkg/config"
apps "k8s.io/api/apps/v1" apps "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -67,10 +67,12 @@ func (kc *KubeClient) GenerateConfigMap(generator types.Generation, namespace st
var err error var err error
kc.logger.Printf("Copying data from configmap %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name) if generator.CopyFrom != nil {
configMap, err = kc.client.CoreV1().ConfigMaps(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, metav1.GetOptions{}) kc.logger.Printf("Copying data from configmap %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name)
if err != nil { configMap, err = kc.client.CoreV1().ConfigMaps(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, metav1.GetOptions{})
return err if err != nil {
return err
}
} }
configMap.ObjectMeta = metav1.ObjectMeta{ configMap.ObjectMeta = metav1.ObjectMeta{
@ -101,10 +103,12 @@ func (kc *KubeClient) GenerateSecret(generator types.Generation, namespace strin
var err error var err error
kc.logger.Printf("Copying data from secret %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name) if generator.CopyFrom != nil {
secret, err = kc.client.CoreV1().Secrets(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, metav1.GetOptions{}) kc.logger.Printf("Copying data from secret %s/%s", generator.CopyFrom.Namespace, generator.CopyFrom.Name)
if err != nil { secret, err = kc.client.CoreV1().Secrets(generator.CopyFrom.Namespace).Get(generator.CopyFrom.Name, metav1.GetOptions{})
return err if err != nil {
return err
}
} }
secret.ObjectMeta = metav1.ObjectMeta{ secret.ObjectMeta = metav1.ObjectMeta{

View file

@ -59,7 +59,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 := webhooks.NewWebhookServer(tlsPair, policyInformer.Lister(), nil) server, err := webhooks.NewWebhookServer(tlsPair, policyInformer.Lister(), kubeclient, 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)
} }

View file

@ -60,9 +60,9 @@ type Validation struct {
// Generation describes which resources will be created when other resource is created // Generation describes which resources will be created when other resource is created
type Generation struct { type Generation struct {
Kind string `json:"kind"` Kind string `json:"kind"`
Name string `json:"name"` Name string `json:"name"`
CopyFrom `json:"copyFrom"` CopyFrom *CopyFrom `json:"copyFrom"`
Data map[string]string `json:"data"` Data map[string]string `json:"data"`
Labels map[string]string `json:"labels"` Labels map[string]string `json:"labels"`
} }

View file

@ -64,21 +64,12 @@ func (pp *Patch) Validate() error {
return fmt.Errorf("Unsupported JSONPatch operation '%s'", pp.Operation) 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 // Validate returns error if generator is configured incompletely
func (pcg *Generation) Validate() error { func (pcg *Generation) Validate() error {
if pcg.Name == "" || pcg.Kind == "" { if len(pcg.Data) == 0 && pcg.CopyFrom == nil {
return errors.New("Name or/and Kind of generator is not specified") return fmt.Errorf("Neither Data nor CopyFrom (source) of %s/%s is specified", pcg.Kind, pcg.Name)
} }
return nil
return pcg.CopyFrom.Validate()
} }
// DeepCopyInto is declared because k8s:deepcopy-gen is // DeepCopyInto is declared because k8s:deepcopy-gen is

View file

@ -1,8 +1,10 @@
package engine package engine
import ( import (
"fmt"
"log" "log"
kubeClient "github.com/nirmata/kube-policy/kubeclient"
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
@ -14,14 +16,12 @@ type GenerationResponse struct {
// Generate should be called to process generate rules on the resource // Generate should be called to process generate rules on the resource
// TODO: extend kubeclient(will change to dynamic client) to create resources // TODO: extend kubeclient(will change to dynamic client) to create resources
func Generate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) []GenerationResponse { func Generate(policy kubepolicy.Policy, rawResource []byte, kubeClient *kubeClient.KubeClient, gvk metav1.GroupVersionKind) {
// configMapGenerator and secretGenerator can be applied only to namespaces // configMapGenerator and secretGenerator can be applied only to namespaces
if gvk.Kind != "Namespace" { if gvk.Kind != "Namespace" {
return nil return
} }
var generateResps []GenerationResponse
for i, rule := range policy.Spec.Rules { for i, rule := range policy.Spec.Rules {
// Checks for preconditions // Checks for preconditions
@ -46,26 +46,39 @@ func Generate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers
continue continue
} }
generateResps, err = applyRuleGenerator(rawResource, rule.Generation) err = applyRuleGenerator(rawResource, rule.Generation, kubeClient)
if err != nil { if err != nil {
log.Printf("Failed to apply rule generator: %v", err) log.Printf("Failed to apply rule generator: %v", err)
} else {
generateResps = append(generateResps, generateResps...)
} }
} }
return generateResps
} }
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule // Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
// TODO: plan to support all kinds of generator // TODO: plan to support all kinds of generator
func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation) ([]GenerationResponse, error) { func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation, kubeClient *kubeClient.KubeClient) error {
var generationResponse []GenerationResponse
if generator == nil { if generator == nil {
return nil, nil return nil
} }
namespaceName := ParseNameFromObject(rawResource) err := generator.Validate()
generationResponse = append(generationResponse, GenerationResponse{generator, namespaceName}) if err != nil {
return generationResponse, nil return fmt.Errorf("Generator for '%s/%s' is invalid: %s", generator.Kind, generator.Name, err)
}
namespace := ParseNameFromObject(rawResource)
switch generator.Kind {
case "ConfigMap":
err = kubeClient.GenerateConfigMap(*generator, namespace)
case "Secret":
err = kubeClient.GenerateSecret(*generator, namespace)
default:
err = fmt.Errorf("Unsupported config Kind '%s'", generator.Kind)
}
if err != nil {
return fmt.Errorf("Unable to apply generator for %s '%s/%s' : %v", generator.Kind, namespace, generator.Name, err)
}
log.Printf("Successfully applied generator %s/%s", generator.Kind, generator.Name)
return nil
} }

View file

@ -116,3 +116,24 @@ func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) {
assert.Assert(t, len(patchesBytes) == 1) assert.Assert(t, len(patchesBytes) == 1)
assertEqStringAndData(t, `{"path":"/metadata/labels/label2","op":"add","value":"label2Value"}`, patchesBytes[0]) assertEqStringAndData(t, `{"path":"/metadata/labels/label2","op":"add","value":"label2Value"}`, patchesBytes[0])
} }
func assertEqDataImpl(t *testing.T, expected, actual []byte, formatModifier string) {
if len(expected) != len(actual) {
t.Errorf("len(expected) != len(actual): %d != %d\n1:"+formatModifier+"\n2:"+formatModifier, len(expected), len(actual), expected, actual)
return
}
for idx, val := range actual {
if val != expected[idx] {
t.Errorf("Slices not equal at index %d:\n1:"+formatModifier+"\n2:"+formatModifier, idx, expected, actual)
}
}
}
func assertEqData(t *testing.T, expected, actual []byte) {
assertEqDataImpl(t, expected, actual, "%x")
}
func assertEqStringAndData(t *testing.T, str string, data []byte) {
assertEqDataImpl(t, []byte(str), data, "%s")
}

View file

@ -4,8 +4,8 @@ import (
"errors" "errors"
"io/ioutil" "io/ioutil"
"github.com/nirmata/kube-policy/config"
kubeclient "github.com/nirmata/kube-policy/kubeclient" kubeclient "github.com/nirmata/kube-policy/kubeclient"
"github.com/nirmata/kube-policy/pkg/config"
admregapi "k8s.io/api/admissionregistration/v1beta1" admregapi "k8s.io/api/admissionregistration/v1beta1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1" meta "k8s.io/apimachinery/pkg/apis/meta/v1"

View file

@ -12,8 +12,9 @@ import (
"os" "os"
"time" "time"
"github.com/nirmata/kube-policy/config" kubeClient "github.com/nirmata/kube-policy/kubeclient"
policylister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1" policylister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1"
"github.com/nirmata/kube-policy/pkg/config"
engine "github.com/nirmata/kube-policy/pkg/engine" engine "github.com/nirmata/kube-policy/pkg/engine"
"github.com/nirmata/kube-policy/pkg/engine/mutation" "github.com/nirmata/kube-policy/pkg/engine/mutation"
tlsutils "github.com/nirmata/kube-policy/pkg/tls" tlsutils "github.com/nirmata/kube-policy/pkg/tls"
@ -27,6 +28,7 @@ import (
type WebhookServer struct { type WebhookServer struct {
server http.Server server http.Server
policyLister policylister.PolicyLister policyLister policylister.PolicyLister
kubeClient *kubeClient.KubeClient
logger *log.Logger logger *log.Logger
} }
@ -35,6 +37,7 @@ type WebhookServer struct {
func NewWebhookServer( func NewWebhookServer(
tlsPair *tlsutils.TlsPemPair, tlsPair *tlsutils.TlsPemPair,
policyLister policylister.PolicyLister, policyLister policylister.PolicyLister,
kubeClient *kubeClient.KubeClient,
logger *log.Logger) (*WebhookServer, error) { logger *log.Logger) (*WebhookServer, error) {
if logger == nil { if logger == nil {
logger = log.New(os.Stdout, "Webhook Server: ", log.LstdFlags) logger = log.New(os.Stdout, "Webhook Server: ", log.LstdFlags)
@ -53,6 +56,7 @@ func NewWebhookServer(
ws := &WebhookServer{ ws := &WebhookServer{
policyLister: policyLister, policyLister: policyLister,
kubeClient: kubeClient,
logger: logger, logger: logger,
} }
@ -174,6 +178,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
allowed := true allowed := true
for _, policy := range policies { for _, policy := range policies {
// validation
ws.logger.Printf("Validating resource with policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules)) ws.logger.Printf("Validating resource with policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules))
if ok := engine.Validate(*policy, request.Object.Raw, request.Kind); !ok { if ok := engine.Validate(*policy, request.Object.Raw, request.Kind); !ok {
@ -183,6 +188,9 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
} else { } else {
ws.logger.Println("Validation is successful") ws.logger.Println("Validation is successful")
} }
// generation
engine.Generate(*policy, request.Object.Raw, ws.kubeClient, request.Kind)
} }
return &v1beta1.AdmissionResponse{ return &v1beta1.AdmissionResponse{