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:
commit
4b380f758f
13 changed files with 145 additions and 106 deletions
|
@ -29,14 +29,14 @@ spec:
|
|||
required:
|
||||
- name
|
||||
- resource
|
||||
parameters:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
resource:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
parameters:
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
enum:
|
||||
|
@ -115,13 +115,10 @@ spec:
|
|||
pattern:
|
||||
AnyValue: {}
|
||||
generate:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
required:
|
||||
- kind
|
||||
- name
|
||||
- copyFrom
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
|
|
|
@ -4,15 +4,17 @@ metadata :
|
|||
name: "policy-configmapgenerator-test"
|
||||
spec:
|
||||
rules:
|
||||
- name: "Policy ConfigMap sample rule"
|
||||
resource:
|
||||
- name: "copyCM"
|
||||
resource :
|
||||
kind : Namespace
|
||||
name: "ns2"
|
||||
generate:
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
generate :
|
||||
kind: ConfigMap
|
||||
name: copied-cm
|
||||
copyFrom:
|
||||
namespace: default
|
||||
name: game-config
|
||||
data:
|
||||
name : copied-cm
|
||||
copyFrom :
|
||||
namespace : default
|
||||
name : game-config
|
||||
data :
|
||||
secretData: "data from cmg"
|
|
@ -3,29 +3,32 @@
|
|||
# To apply this policy you need to create secret and configMap in "default" namespace
|
||||
# and then create a namespace
|
||||
|
||||
apiVersion : policy.nirmata.io/v1alpha1
|
||||
apiVersion : kubepolicy.nirmata.io/v1alpha1
|
||||
kind : Policy
|
||||
metadata :
|
||||
name : "policy-ns-patch-cmg-sg"
|
||||
spec :
|
||||
failurePolicy: stopOnError
|
||||
rules:
|
||||
- resource :
|
||||
- name: "patchNamespace2"
|
||||
resource :
|
||||
kind : Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
patch:
|
||||
mutate:
|
||||
patches:
|
||||
- path: "/metadata/labels/isMutatedByPolicy"
|
||||
op: add
|
||||
value: "true"
|
||||
|
||||
- resource :
|
||||
- name: "copyCM"
|
||||
resource :
|
||||
kind : Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
configMapGenerator :
|
||||
generate :
|
||||
kind: ConfigMap
|
||||
name : copied-cm
|
||||
copyFrom :
|
||||
namespace : default
|
||||
|
@ -33,12 +36,14 @@ spec :
|
|||
data :
|
||||
secretData: "data from cmg"
|
||||
|
||||
- resource :
|
||||
- name: "generateCM"
|
||||
resource :
|
||||
kind : Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
configMapGenerator :
|
||||
generate :
|
||||
kind: ConfigMap
|
||||
name : generated-cm
|
||||
data :
|
||||
secretData: "very sensitive data from cmg"
|
||||
|
@ -49,13 +54,12 @@ spec :
|
|||
image.public.key=771
|
||||
rsa.public.key=42
|
||||
|
||||
- resource :
|
||||
- name: "generateSecret"
|
||||
resource :
|
||||
kind : Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
|
||||
secretGenerator :
|
||||
name: ns2
|
||||
generate :
|
||||
kind: Secret
|
||||
name : generated-secrets
|
||||
data :
|
||||
foo : bar
|
||||
|
@ -66,13 +70,12 @@ spec :
|
|||
foo1=bar1
|
||||
foo2=bar2
|
||||
|
||||
- resource :
|
||||
- name: "copySecret"
|
||||
resource :
|
||||
kind : Namespace
|
||||
selector:
|
||||
matchLabels:
|
||||
LabelForSelector : "namespace2"
|
||||
|
||||
secretGenerator :
|
||||
name: ns2
|
||||
generate :
|
||||
kind: Secret
|
||||
name : copied-secrets
|
||||
copyFrom :
|
||||
namespace : default
|
||||
|
|
2
init.go
2
init.go
|
@ -5,8 +5,8 @@ import (
|
|||
"log"
|
||||
"net/url"
|
||||
|
||||
"github.com/nirmata/kube-policy/config"
|
||||
"github.com/nirmata/kube-policy/kubeclient"
|
||||
"github.com/nirmata/kube-policy/pkg/config"
|
||||
"github.com/nirmata/kube-policy/pkg/tls"
|
||||
|
||||
rest "k8s.io/client-go/rest"
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/nirmata/kube-policy/config"
|
||||
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
"github.com/nirmata/kube-policy/pkg/config"
|
||||
apps "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -67,11 +67,13 @@ func (kc *KubeClient) GenerateConfigMap(generator types.Generation, namespace st
|
|||
|
||||
var err error
|
||||
|
||||
if generator.CopyFrom != nil {
|
||||
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, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
configMap.ObjectMeta = metav1.ObjectMeta{
|
||||
Name: generator.Name,
|
||||
|
@ -101,11 +103,13 @@ func (kc *KubeClient) GenerateSecret(generator types.Generation, namespace strin
|
|||
|
||||
var err error
|
||||
|
||||
if generator.CopyFrom != nil {
|
||||
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, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
secret.ObjectMeta = metav1.ObjectMeta{
|
||||
Name: generator.Name,
|
||||
|
|
2
main.go
2
main.go
|
@ -59,7 +59,7 @@ func main() {
|
|||
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 {
|
||||
log.Fatalf("Unable to create webhook server: %v\n", err)
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ type Validation struct {
|
|||
type Generation struct {
|
||||
Kind string `json:"kind"`
|
||||
Name string `json:"name"`
|
||||
CopyFrom `json:"copyFrom"`
|
||||
CopyFrom *CopyFrom `json:"copyFrom"`
|
||||
Data map[string]string `json:"data"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
||||
|
|
|
@ -64,21 +64,12 @@ func (pp *Patch) Validate() error {
|
|||
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 == "" || pcg.Kind == "" {
|
||||
return errors.New("Name or/and Kind of generator is not specified")
|
||||
if len(pcg.Data) == 0 && pcg.CopyFrom == nil {
|
||||
return fmt.Errorf("Neither Data nor CopyFrom (source) of %s/%s is specified", pcg.Kind, pcg.Name)
|
||||
}
|
||||
|
||||
return pcg.CopyFrom.Validate()
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is declared because k8s:deepcopy-gen is
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
kubeClient "github.com/nirmata/kube-policy/kubeclient"
|
||||
kubepolicy "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
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
|
||||
// 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
|
||||
if gvk.Kind != "Namespace" {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
var generateResps []GenerationResponse
|
||||
|
||||
for i, rule := range policy.Spec.Rules {
|
||||
|
||||
// Checks for preconditions
|
||||
|
@ -46,26 +46,39 @@ func Generate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVers
|
|||
continue
|
||||
}
|
||||
|
||||
generateResps, err = applyRuleGenerator(rawResource, rule.Generation)
|
||||
err = applyRuleGenerator(rawResource, rule.Generation, kubeClient)
|
||||
if err != nil {
|
||||
log.Printf("Failed to apply rule generator: %v", err)
|
||||
} else {
|
||||
generateResps = append(generateResps, generateResps...)
|
||||
}
|
||||
}
|
||||
|
||||
return generateResps
|
||||
}
|
||||
|
||||
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
|
||||
// TODO: plan to support all kinds of generator
|
||||
func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation) ([]GenerationResponse, error) {
|
||||
var generationResponse []GenerationResponse
|
||||
func applyRuleGenerator(rawResource []byte, generator *kubepolicy.Generation, kubeClient *kubeClient.KubeClient) error {
|
||||
if generator == nil {
|
||||
return nil, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
namespaceName := ParseNameFromObject(rawResource)
|
||||
generationResponse = append(generationResponse, GenerationResponse{generator, namespaceName})
|
||||
return generationResponse, nil
|
||||
err := generator.Validate()
|
||||
if err != 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
|
||||
}
|
||||
|
|
|
@ -116,3 +116,24 @@ func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) {
|
|||
assert.Assert(t, len(patchesBytes) == 1)
|
||||
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")
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ import (
|
|||
"errors"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/nirmata/kube-policy/config"
|
||||
kubeclient "github.com/nirmata/kube-policy/kubeclient"
|
||||
"github.com/nirmata/kube-policy/pkg/config"
|
||||
|
||||
admregapi "k8s.io/api/admissionregistration/v1beta1"
|
||||
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
|
|
@ -12,8 +12,9 @@ import (
|
|||
"os"
|
||||
"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"
|
||||
"github.com/nirmata/kube-policy/pkg/config"
|
||||
engine "github.com/nirmata/kube-policy/pkg/engine"
|
||||
"github.com/nirmata/kube-policy/pkg/engine/mutation"
|
||||
tlsutils "github.com/nirmata/kube-policy/pkg/tls"
|
||||
|
@ -27,6 +28,7 @@ import (
|
|||
type WebhookServer struct {
|
||||
server http.Server
|
||||
policyLister policylister.PolicyLister
|
||||
kubeClient *kubeClient.KubeClient
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
|
@ -35,6 +37,7 @@ type WebhookServer struct {
|
|||
func NewWebhookServer(
|
||||
tlsPair *tlsutils.TlsPemPair,
|
||||
policyLister policylister.PolicyLister,
|
||||
kubeClient *kubeClient.KubeClient,
|
||||
logger *log.Logger) (*WebhookServer, error) {
|
||||
if logger == nil {
|
||||
logger = log.New(os.Stdout, "Webhook Server: ", log.LstdFlags)
|
||||
|
@ -53,6 +56,7 @@ func NewWebhookServer(
|
|||
|
||||
ws := &WebhookServer{
|
||||
policyLister: policyLister,
|
||||
kubeClient: kubeClient,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
|
@ -174,6 +178,7 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
|
|||
|
||||
allowed := true
|
||||
for _, policy := range policies {
|
||||
// validation
|
||||
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 {
|
||||
|
@ -183,6 +188,9 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
|
|||
} else {
|
||||
ws.logger.Println("Validation is successful")
|
||||
}
|
||||
|
||||
// generation
|
||||
engine.Generate(*policy, request.Object.Raw, ws.kubeClient, request.Kind)
|
||||
}
|
||||
|
||||
return &v1beta1.AdmissionResponse{
|
||||
|
|
Loading…
Add table
Reference in a new issue