mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
NK-23: Thre creation of default loggers moved to inside classes.
Removed fatal termination from object constructors. Implemented new KubeClient class with test method which creates a Secret. Improved comments for the types structures. Added WebhookServerConfig structure instead of the most parameters to NewWebhookServer.
This commit is contained in:
parent
307df4786f
commit
2ef3bba93d
6 changed files with 425 additions and 329 deletions
|
@ -1,111 +1,109 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
"errors"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
clientset "github.com/nirmata/kube-policy/pkg/client/clientset/versioned"
|
||||
informers "github.com/nirmata/kube-policy/pkg/client/informers/externalversions"
|
||||
lister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1"
|
||||
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
clientset "github.com/nirmata/kube-policy/pkg/client/clientset/versioned"
|
||||
informers "github.com/nirmata/kube-policy/pkg/client/informers/externalversions"
|
||||
lister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1"
|
||||
)
|
||||
|
||||
// PolicyController for CRD
|
||||
type PolicyController struct {
|
||||
policyInformerFactory informers.SharedInformerFactory
|
||||
policyLister lister.PolicyLister
|
||||
logger *log.Logger
|
||||
policyInformerFactory informers.SharedInformerFactory
|
||||
policyLister lister.PolicyLister
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewPolicyController from cmd args
|
||||
func NewPolicyController(masterURL, kubeconfigPath string, logger *log.Logger) (*PolicyController, error) {
|
||||
if logger == nil {
|
||||
logger = log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile)
|
||||
}
|
||||
func NewPolicyController(config *rest.Config, logger *log.Logger) (*PolicyController, error) {
|
||||
if logger == nil {
|
||||
logger = log.New(os.Stdout, "Policy Controller: ", log.LstdFlags|log.Lshortfile)
|
||||
}
|
||||
|
||||
cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfigPath)
|
||||
if err != nil {
|
||||
logger.Printf("Error building kubeconfig: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
return nil, errors.New("Client Config should be set for controller")
|
||||
}
|
||||
|
||||
policyClientset, err := clientset.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
logger.Printf("Error building policy clientset: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
policyClientset, err := clientset.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policyInformerFactory := informers.NewSharedInformerFactory(policyClientset, time.Second*30)
|
||||
policyInformer := policyInformerFactory.Nirmata().V1alpha1().Policies()
|
||||
policyInformerFactory := informers.NewSharedInformerFactory(policyClientset, time.Second*30)
|
||||
policyInformer := policyInformerFactory.Nirmata().V1alpha1().Policies()
|
||||
|
||||
controller := &PolicyController{
|
||||
policyInformerFactory: policyInformerFactory,
|
||||
policyLister: policyInformer.Lister(),
|
||||
logger: logger,
|
||||
}
|
||||
controller := &PolicyController{
|
||||
policyInformerFactory: policyInformerFactory,
|
||||
policyLister: policyInformer.Lister(),
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
policyInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: controller.createPolicyHandler,
|
||||
UpdateFunc: controller.updatePolicyHandler,
|
||||
DeleteFunc: controller.deletePolicyHandler,
|
||||
})
|
||||
policyInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: controller.createPolicyHandler,
|
||||
UpdateFunc: controller.updatePolicyHandler,
|
||||
DeleteFunc: controller.deletePolicyHandler,
|
||||
})
|
||||
|
||||
return controller, nil
|
||||
return controller, nil
|
||||
}
|
||||
|
||||
// Run is main controller thread
|
||||
func (c *PolicyController) Run(stopCh <-chan struct{}) {
|
||||
c.policyInformerFactory.Start(stopCh)
|
||||
c.policyInformerFactory.Start(stopCh)
|
||||
}
|
||||
|
||||
// GetPolicies retrieves all policy resources
|
||||
// from cache. Cache is refreshed by informer
|
||||
func (c *PolicyController) GetPolicies() []types.Policy {
|
||||
// Create nil Selector to grab all the policies
|
||||
selector := labels.NewSelector()
|
||||
cachedPolicies, err := c.policyLister.List(selector)
|
||||
// Create nil Selector to grab all the policies
|
||||
selector := labels.NewSelector()
|
||||
cachedPolicies, err := c.policyLister.List(selector)
|
||||
|
||||
if err != nil {
|
||||
c.logger.Printf("Error: %v", err)
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
c.logger.Printf("Error: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var policies []types.Policy
|
||||
for _, elem := range cachedPolicies {
|
||||
policies = append(policies, *elem.DeepCopy())
|
||||
}
|
||||
var policies []types.Policy
|
||||
for _, elem := range cachedPolicies {
|
||||
policies = append(policies, *elem.DeepCopy())
|
||||
}
|
||||
|
||||
return policies
|
||||
return policies
|
||||
}
|
||||
|
||||
func (c *PolicyController) createPolicyHandler(resource interface{}) {
|
||||
key := c.getResourceKey(resource)
|
||||
c.logger.Printf("Created policy: %s\n", key)
|
||||
key := c.getResourceKey(resource)
|
||||
c.logger.Printf("Created policy: %s\n", key)
|
||||
}
|
||||
|
||||
func (c *PolicyController) updatePolicyHandler(oldResource, newResource interface{}) {
|
||||
oldKey := c.getResourceKey(oldResource)
|
||||
newKey := c.getResourceKey(newResource)
|
||||
oldKey := c.getResourceKey(oldResource)
|
||||
newKey := c.getResourceKey(newResource)
|
||||
|
||||
c.logger.Printf("Updated policy from %s to %s\n", oldKey, newKey)
|
||||
c.logger.Printf("Updated policy from %s to %s\n", oldKey, newKey)
|
||||
}
|
||||
|
||||
func (c *PolicyController) deletePolicyHandler(resource interface{}) {
|
||||
key := c.getResourceKey(resource)
|
||||
c.logger.Printf("Deleted policy: %s\n", key)
|
||||
key := c.getResourceKey(resource)
|
||||
c.logger.Printf("Deleted policy: %s\n", key)
|
||||
}
|
||||
|
||||
func (c *PolicyController) getResourceKey(resource interface{}) string {
|
||||
if key, err := cache.MetaNamespaceKeyFunc(resource); err != nil {
|
||||
c.logger.Fatalf("Error retrieving policy key: %v\n", err)
|
||||
} else {
|
||||
return key
|
||||
}
|
||||
if key, err := cache.MetaNamespaceKeyFunc(resource); err != nil {
|
||||
c.logger.Fatalf("Error retrieving policy key: %v\n", err)
|
||||
} else {
|
||||
return key
|
||||
}
|
||||
|
||||
return ""
|
||||
return ""
|
||||
}
|
||||
|
|
49
kubeclient/kubeclient.go
Normal file
49
kubeclient/kubeclient.go
Normal file
|
@ -0,0 +1,49 @@
|
|||
package kubeclient
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type KubeClient struct {
|
||||
logger *log.Logger
|
||||
client *kubernetes.Clientset
|
||||
}
|
||||
|
||||
func NewKubeClient(config *rest.Config, logger *log.Logger) (*KubeClient, error) {
|
||||
if logger == nil {
|
||||
logger = log.New(os.Stdout, "Policy Controller: ", log.LstdFlags|log.Lshortfile)
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &KubeClient{
|
||||
logger: logger,
|
||||
client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (kc *KubeClient) CopySecret(from *types.PolicyCopyFrom, namespaceTo string) error {
|
||||
// This is the test code, which works
|
||||
var secret v1.Secret
|
||||
secret.Namespace = namespaceTo
|
||||
secret.ObjectMeta.SetName("test-secret")
|
||||
secret.StringData = make(map[string]string)
|
||||
secret.StringData["test-data"] = "test-value"
|
||||
newSecret, err := kc.client.CoreV1().Secrets(namespaceTo).Create(&secret)
|
||||
if err != nil {
|
||||
kc.logger.Printf("Unable to create secret: %s", err)
|
||||
} else {
|
||||
kc.logger.Printf("Secret created: %s", newSecret.Name)
|
||||
}
|
||||
return err
|
||||
}
|
102
main.go
102
main.go
|
@ -1,58 +1,86 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/nirmata/kube-policy/controller"
|
||||
"github.com/nirmata/kube-policy/server"
|
||||
"github.com/nirmata/kube-policy/controller"
|
||||
"github.com/nirmata/kube-policy/kubeclient"
|
||||
"github.com/nirmata/kube-policy/server"
|
||||
|
||||
"k8s.io/sample-controller/pkg/signals"
|
||||
rest "k8s.io/client-go/rest"
|
||||
clientcmd "k8s.io/client-go/tools/clientcmd"
|
||||
signals "k8s.io/sample-controller/pkg/signals"
|
||||
)
|
||||
|
||||
var (
|
||||
masterURL string
|
||||
kubeconfig string
|
||||
cert string
|
||||
key string
|
||||
masterURL string
|
||||
kubeconfig string
|
||||
cert string
|
||||
key string
|
||||
)
|
||||
|
||||
func createClientConfig(masterURL, kubeconfig string) (*rest.Config, error) {
|
||||
// TODO: make possible to create config within a cluster with proper rights
|
||||
config, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
flag.Parse()
|
||||
|
||||
if cert == "" || key == "" {
|
||||
log.Fatal("TLS certificate or/and key is not set")
|
||||
}
|
||||
if cert == "" || key == "" {
|
||||
log.Fatal("TLS certificate or/and key is not set")
|
||||
}
|
||||
|
||||
crdcLogger := log.New(os.Stdout, "Policy Controller: ", log.LstdFlags|log.Lshortfile)
|
||||
controller, err := controller.NewPolicyController(masterURL, kubeconfig, crdcLogger)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating PolicyController! Error: %s\n", err)
|
||||
return
|
||||
}
|
||||
clientConfig, err := createClientConfig(masterURL, kubeconfig)
|
||||
if err != nil {
|
||||
log.Fatalf("Error building kubeconfig: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
httpLogger := log.New(os.Stdout, "HTTPS Server: ", log.LstdFlags|log.Lshortfile)
|
||||
server := server.NewWebhookServer(cert, key, controller, httpLogger)
|
||||
server.RunAsync()
|
||||
controller, err := controller.NewPolicyController(clientConfig, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating PolicyController! Error: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
stopCh := signals.SetupSignalHandler()
|
||||
controller.Run(stopCh)
|
||||
kubeclient, err := kubeclient.NewKubeClient(clientConfig, nil)
|
||||
if err != nil {
|
||||
log.Fatalf("Error creating kubeclient: %v\n", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("Error running PolicyController! Error: %s\n", err)
|
||||
}
|
||||
serverConfig := server.WebhookServerConfig{
|
||||
CertFile: cert,
|
||||
KeyFile: key,
|
||||
Controller: controller,
|
||||
Kubeclient: kubeclient,
|
||||
}
|
||||
|
||||
fmt.Println("Policy Controller has started")
|
||||
<-stopCh
|
||||
server.Stop()
|
||||
fmt.Println("Policy Controller has stopped")
|
||||
server, err := server.NewWebhookServer(serverConfig, nil)
|
||||
server.RunAsync()
|
||||
|
||||
stopCh := signals.SetupSignalHandler()
|
||||
controller.Run(stopCh)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Error running PolicyController! Error: %s\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("Policy Controller has started")
|
||||
<-stopCh
|
||||
server.Stop()
|
||||
fmt.Println("Policy Controller has stopped")
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
|
||||
flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
|
||||
flag.StringVar(&cert, "cert", "", "TLS certificate used in connection with cluster.")
|
||||
flag.StringVar(&key, "key", "", "Key, used in TLS connection.")
|
||||
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
|
||||
flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.")
|
||||
flag.StringVar(&cert, "cert", "", "TLS certificate used in connection with cluster.")
|
||||
flag.StringVar(&key, "key", "", "Key, used in TLS connection.")
|
||||
}
|
||||
|
|
|
@ -1,71 +1,77 @@
|
|||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Policy is a specification for a Policy resource
|
||||
// An example of the YAML representation of this structure is here:
|
||||
// <project_root>/crd/policy-example.yaml
|
||||
type Policy struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Spec PolicySpec `json:"spec"`
|
||||
Status PolicyStatus `json:"status"`
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Spec PolicySpec `json:"spec"`
|
||||
Status PolicyStatus `json:"status"`
|
||||
}
|
||||
|
||||
// PolicySpec is the spec for a Policy resource
|
||||
// Specification of the Policy.
|
||||
type PolicySpec struct {
|
||||
FailurePolicy *string `json:"failurePolicy"`
|
||||
Rules []PolicyRule `json:"rules"`
|
||||
FailurePolicy *string `json:"failurePolicy"`
|
||||
Rules []PolicyRule `json:"rules"`
|
||||
}
|
||||
|
||||
// PolicyRule is policy rule that will be applied to resource
|
||||
// The rule of mutation for the single resource definition.
|
||||
// Details are listed in the description of each of the substructures.
|
||||
type PolicyRule struct {
|
||||
Resource PolicyResource `json:"resource"`
|
||||
Patches []PolicyPatch `json:"patch,omitempty"`
|
||||
ConfigMapGenerator *PolicyConfigGenerator `json:"configMapGenerator,omitempty"`
|
||||
SecretGenerator *PolicyConfigGenerator `json:"secretGenerator,omitempty"`
|
||||
Resource PolicyResource `json:"resource"`
|
||||
Patches []PolicyPatch `json:"patch,omitempty"`
|
||||
ConfigMapGenerator *PolicyConfigGenerator `json:"configMapGenerator,omitempty"`
|
||||
SecretGenerator *PolicyConfigGenerator `json:"secretGenerator,omitempty"`
|
||||
}
|
||||
|
||||
// PolicyResource describes the resource rule applied to
|
||||
// Describes the resource to which the PolicyRule will apply.
|
||||
// Either the name or selector must be specified.
|
||||
// IMPORTANT: If neither is specified, the policy rule will not apply (TBD).
|
||||
type PolicyResource struct {
|
||||
Kind string `json:"kind"`
|
||||
Name *string `json:"name"`
|
||||
Selector *metav1.LabelSelector `json:"selector,omitempty"`
|
||||
Kind string `json:"kind"`
|
||||
Name *string `json:"name"`
|
||||
Selector *metav1.LabelSelector `json:"selector,omitempty"`
|
||||
}
|
||||
|
||||
// PolicyPatch is TODO
|
||||
// PolicyPatch declares patch operation for created object according to the JSONPatch spec:
|
||||
// http://jsonpatch.com/
|
||||
type PolicyPatch struct {
|
||||
Path string `json:"path"`
|
||||
Operation string `json:"op"`
|
||||
Value string `json:"value"`
|
||||
Path string `json:"path"`
|
||||
Operation string `json:"op"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// PolicyConfigGenerator is TODO
|
||||
// The declaration for a Secret or a ConfigMap, which will be created in the new namespace.
|
||||
// Can be applied only when PolicyRule.Resource.Kind is "Namespace".
|
||||
type PolicyConfigGenerator struct {
|
||||
Name string `json:"name"`
|
||||
CopyFrom *PolicyCopyFrom `json:"copyFrom"`
|
||||
Data map[string]string `json:"data"`
|
||||
Name string `json:"name"`
|
||||
CopyFrom *PolicyCopyFrom `json:"copyFrom"`
|
||||
Data map[string]string `json:"data"`
|
||||
}
|
||||
|
||||
// PolicyCopyFrom is TODO
|
||||
// Location of a Secret or a ConfigMap which will be used as source when applying PolicyConfigGenerator
|
||||
type PolicyCopyFrom struct {
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
Namespace string `json:"namespace"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// PolicyStatus is the status for a Policy resource
|
||||
// Contains logs about policy application
|
||||
type PolicyStatus struct {
|
||||
Logs []string `json:"log"`
|
||||
Logs []string `json:"log"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// PolicyList is a list of Policy resources
|
||||
// List of Policy resources
|
||||
type PolicyList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
Items []Policy `json:"items"`
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
Items []Policy `json:"items"`
|
||||
}
|
||||
|
|
253
server/server.go
253
server/server.go
|
@ -1,158 +1,173 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
controller "github.com/nirmata/kube-policy/controller"
|
||||
webhooks "github.com/nirmata/kube-policy/webhooks"
|
||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||
controller "github.com/nirmata/kube-policy/controller"
|
||||
kubeclient "github.com/nirmata/kube-policy/kubeclient"
|
||||
webhooks "github.com/nirmata/kube-policy/webhooks"
|
||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||
)
|
||||
|
||||
// WebhookServer is a struct that describes
|
||||
// TLS server with mutation webhook
|
||||
// WebhookServer contains configured TLS server with MutationWebhook.
|
||||
// MutationWebhook gets policies from policyController and takes control of the cluster with kubeclient.
|
||||
type WebhookServer struct {
|
||||
server http.Server
|
||||
logger *log.Logger
|
||||
policyController *controller.PolicyController
|
||||
mutationWebhook *webhooks.MutationWebhook
|
||||
server http.Server
|
||||
policyController *controller.PolicyController
|
||||
kubeclient *kubeclient.KubeClient
|
||||
mutationWebhook *webhooks.MutationWebhook
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
// NewWebhookServer creates new instance of WebhookServer and configures it
|
||||
func NewWebhookServer(certFile string, keyFile string, controller *controller.PolicyController, logger *log.Logger) *WebhookServer {
|
||||
if logger == nil {
|
||||
logger = log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile)
|
||||
}
|
||||
if controller == nil {
|
||||
logger.Fatal("Controller is not specified for webhook server")
|
||||
}
|
||||
// Configuration struct for WebhookServer used in NewWebhookServer
|
||||
// Controller and Kubeclient should be initialized and valid
|
||||
type WebhookServerConfig struct {
|
||||
CertFile string
|
||||
KeyFile string
|
||||
Controller *controller.PolicyController
|
||||
Kubeclient *kubeclient.KubeClient
|
||||
}
|
||||
|
||||
var config tls.Config
|
||||
pair, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
logger.Fatal("Unable to load certificate and key: ", err)
|
||||
}
|
||||
config.Certificates = []tls.Certificate{pair}
|
||||
// NewWebhookServer creates new instance of WebhookServer accordingly to given configuration
|
||||
// Policy Controller and Kubernetes Client should be initialized in configuration
|
||||
func NewWebhookServer(config WebhookServerConfig, logger *log.Logger) (*WebhookServer, error) {
|
||||
if logger == nil {
|
||||
logger = log.New(os.Stdout, "HTTPS Server: ", log.LstdFlags|log.Lshortfile)
|
||||
}
|
||||
|
||||
mw, err := webhooks.NewMutationWebhook(logger)
|
||||
if err != nil {
|
||||
logger.Fatal("Unable to create mutation webhook: ", err)
|
||||
}
|
||||
if config.Controller == nil || config.Kubeclient == nil {
|
||||
return nil, errors.New("WebHook server requires initialized Policy Controller and Kubernetes Client")
|
||||
}
|
||||
|
||||
ws := &WebhookServer{
|
||||
logger: logger,
|
||||
policyController: controller,
|
||||
mutationWebhook: mw,
|
||||
}
|
||||
var tlsConfig tls.Config
|
||||
pair, err := tls.LoadX509KeyPair(config.CertFile, config.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{pair}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/mutate", ws.serve)
|
||||
mw, err := webhooks.NewMutationWebhook(logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ws.server = http.Server{
|
||||
Addr: ":443", // Listen on port for HTTPS requests
|
||||
TLSConfig: &config,
|
||||
Handler: mux,
|
||||
ErrorLog: logger,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
}
|
||||
ws := &WebhookServer{
|
||||
logger: logger,
|
||||
policyController: config.Controller,
|
||||
kubeclient: config.Kubeclient,
|
||||
mutationWebhook: mw,
|
||||
}
|
||||
|
||||
return ws
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/mutate", ws.serve)
|
||||
|
||||
ws.server = http.Server{
|
||||
Addr: ":443", // Listen on port for HTTPS requests
|
||||
TLSConfig: &tlsConfig,
|
||||
Handler: mux,
|
||||
ErrorLog: logger,
|
||||
ReadTimeout: 15 * time.Second,
|
||||
WriteTimeout: 15 * time.Second,
|
||||
}
|
||||
|
||||
return ws, nil
|
||||
}
|
||||
|
||||
func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/mutate" {
|
||||
admissionReview := ws.parseAdmissionReview(r, w)
|
||||
if admissionReview == nil {
|
||||
return
|
||||
}
|
||||
if r.URL.Path == "/mutate" {
|
||||
admissionReview := ws.parseAdmissionReview(r, w)
|
||||
if admissionReview == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var admissionResponse *v1beta1.AdmissionResponse
|
||||
if webhooks.AdmissionIsRequired(admissionReview.Request) {
|
||||
admissionResponse = ws.mutationWebhook.Mutate(admissionReview.Request, ws.policyController.GetPolicies())
|
||||
}
|
||||
var admissionResponse *v1beta1.AdmissionResponse
|
||||
if webhooks.AdmissionIsRequired(admissionReview.Request) {
|
||||
admissionResponse = ws.mutationWebhook.Mutate(admissionReview.Request, ws.policyController.GetPolicies())
|
||||
}
|
||||
|
||||
if admissionResponse == nil {
|
||||
admissionResponse = &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
}
|
||||
}
|
||||
if admissionResponse == nil {
|
||||
admissionResponse = &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
}
|
||||
}
|
||||
|
||||
admissionReview.Response = admissionResponse
|
||||
admissionReview.Response.UID = admissionReview.Request.UID
|
||||
admissionReview.Response = admissionResponse
|
||||
admissionReview.Response.UID = admissionReview.Request.UID
|
||||
|
||||
responseJson, err := json.Marshal(admissionReview)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
responseJson, err := json.Marshal(admissionReview)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
ws.logger.Printf("Response body\n:%v", string(responseJson))
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if _, err := w.Write(responseJson); err != nil {
|
||||
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
http.Error(w, fmt.Sprintf("Unexpected method path: %v", r.URL.Path), http.StatusNotFound)
|
||||
}
|
||||
ws.logger.Printf("Response body\n:%v", string(responseJson))
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if _, err := w.Write(responseJson); err != nil {
|
||||
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
|
||||
}
|
||||
} else {
|
||||
http.Error(w, fmt.Sprintf("Unexpected method path: %v", r.URL.Path), http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
// Answers to the http.ResponseWriter if request is not valid
|
||||
func (ws *WebhookServer) parseAdmissionReview(request *http.Request, writer http.ResponseWriter) *v1beta1.AdmissionReview {
|
||||
var body []byte
|
||||
if request.Body != nil {
|
||||
if data, err := ioutil.ReadAll(request.Body); err == nil {
|
||||
body = data
|
||||
}
|
||||
}
|
||||
if len(body) == 0 {
|
||||
ws.logger.Print("Error: empty body")
|
||||
http.Error(writer, "empty body", http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
var body []byte
|
||||
if request.Body != nil {
|
||||
if data, err := ioutil.ReadAll(request.Body); err == nil {
|
||||
body = data
|
||||
}
|
||||
}
|
||||
if len(body) == 0 {
|
||||
ws.logger.Print("Error: empty body")
|
||||
http.Error(writer, "empty body", http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
|
||||
contentType := request.Header.Get("Content-Type")
|
||||
if contentType != "application/json" {
|
||||
ws.logger.Printf("Error: invalid Content-Type: %v", contentType)
|
||||
http.Error(writer, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType)
|
||||
return nil
|
||||
}
|
||||
contentType := request.Header.Get("Content-Type")
|
||||
if contentType != "application/json" {
|
||||
ws.logger.Printf("Error: invalid Content-Type: %v", contentType)
|
||||
http.Error(writer, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType)
|
||||
return nil
|
||||
}
|
||||
|
||||
admissionReview := &v1beta1.AdmissionReview{}
|
||||
if err := json.Unmarshal(body, &admissionReview); err != nil {
|
||||
ws.logger.Printf("Error: Can't decode body as AdmissionReview: %v", err)
|
||||
http.Error(writer, "Can't decode body as AdmissionReview", http.StatusExpectationFailed)
|
||||
return nil
|
||||
} else {
|
||||
ws.logger.Printf("Request body:\n%v", string(body))
|
||||
return admissionReview
|
||||
}
|
||||
admissionReview := &v1beta1.AdmissionReview{}
|
||||
if err := json.Unmarshal(body, &admissionReview); err != nil {
|
||||
ws.logger.Printf("Error: Can't decode body as AdmissionReview: %v", err)
|
||||
http.Error(writer, "Can't decode body as AdmissionReview", http.StatusExpectationFailed)
|
||||
return nil
|
||||
} else {
|
||||
ws.logger.Printf("Request body:\n%v", string(body))
|
||||
return admissionReview
|
||||
}
|
||||
}
|
||||
|
||||
// RunAsync runs TLS server in separate
|
||||
// thread and returns control immediately
|
||||
func (ws *WebhookServer) RunAsync() {
|
||||
go func(ws *WebhookServer) {
|
||||
err := ws.server.ListenAndServeTLS("", "")
|
||||
if err != nil {
|
||||
ws.logger.Fatal(err)
|
||||
}
|
||||
}(ws)
|
||||
go func(ws *WebhookServer) {
|
||||
err := ws.server.ListenAndServeTLS("", "")
|
||||
if err != nil {
|
||||
ws.logger.Fatal(err)
|
||||
}
|
||||
}(ws)
|
||||
}
|
||||
|
||||
// Stop stops TLS server
|
||||
func (ws *WebhookServer) Stop() {
|
||||
err := ws.server.Shutdown(context.Background())
|
||||
if err != nil {
|
||||
// Error from closing listeners, or context timeout:
|
||||
ws.logger.Printf("Server Shutdown error: %v", err)
|
||||
ws.server.Close()
|
||||
}
|
||||
err := ws.server.Shutdown(context.Background())
|
||||
if err != nil {
|
||||
// Error from closing listeners, or context timeout:
|
||||
ws.logger.Printf("Server Shutdown error: %v", err)
|
||||
ws.server.Close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,110 +1,110 @@
|
|||
package webhooks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"encoding/json"
|
||||
|
||||
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1"
|
||||
"k8s.io/api/admission/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
)
|
||||
|
||||
var supportedKinds = [...]string{
|
||||
"ConfigMap",
|
||||
"CronJob",
|
||||
"DaemonSet",
|
||||
"Deployment",
|
||||
"Endpoint",
|
||||
"HorizontalPodAutoscaler",
|
||||
"Ingress",
|
||||
"Job",
|
||||
"LimitRange",
|
||||
"Namespace",
|
||||
"NetworkPolicy",
|
||||
"PersistentVolumeClaim",
|
||||
"PodDisruptionBudget",
|
||||
"PodTemplate",
|
||||
"ResourceQuota",
|
||||
"Secret",
|
||||
"Service",
|
||||
"StatefulSet",
|
||||
"ConfigMap",
|
||||
"CronJob",
|
||||
"DaemonSet",
|
||||
"Deployment",
|
||||
"Endpoint",
|
||||
"HorizontalPodAutoscaler",
|
||||
"Ingress",
|
||||
"Job",
|
||||
"LimitRange",
|
||||
"Namespace",
|
||||
"NetworkPolicy",
|
||||
"PersistentVolumeClaim",
|
||||
"PodDisruptionBudget",
|
||||
"PodTemplate",
|
||||
"ResourceQuota",
|
||||
"Secret",
|
||||
"Service",
|
||||
"StatefulSet",
|
||||
}
|
||||
|
||||
func kindIsSupported(kind string) bool {
|
||||
for _, k := range supportedKinds {
|
||||
if k == kind {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
for _, k := range supportedKinds {
|
||||
if k == kind {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AdmissionIsRequired checks for admission if kind is supported
|
||||
func AdmissionIsRequired(request *v1beta1.AdmissionRequest) bool {
|
||||
// Here you can make additional hardcoded checks
|
||||
return kindIsSupported(request.Kind.Kind)
|
||||
// Here you can make additional hardcoded checks
|
||||
return kindIsSupported(request.Kind.Kind)
|
||||
}
|
||||
|
||||
// IsRuleApplicableToRequest checks requests kind, name and labels to fit the policy
|
||||
func IsRuleApplicableToRequest(policyResource types.PolicyResource, request *v1beta1.AdmissionRequest) bool {
|
||||
if policyResource.Selector == nil && policyResource.Name == nil {
|
||||
// TODO: selector or name MUST be specified
|
||||
return false
|
||||
}
|
||||
if policyResource.Selector == nil && policyResource.Name == nil {
|
||||
// TBD: selector or name MUST be specified
|
||||
return false
|
||||
}
|
||||
|
||||
if policyResource.Kind != request.Kind.Kind {
|
||||
return false
|
||||
}
|
||||
if policyResource.Kind != request.Kind.Kind {
|
||||
return false
|
||||
}
|
||||
|
||||
if request.Object.Raw != nil {
|
||||
meta := parseMetadataFromObject(request.Object.Raw)
|
||||
name := parseNameFromMetadata(meta)
|
||||
if request.Object.Raw != nil {
|
||||
meta := parseMetadataFromObject(request.Object.Raw)
|
||||
name := parseNameFromMetadata(meta)
|
||||
|
||||
if policyResource.Name != nil && *policyResource.Name != name {
|
||||
return false
|
||||
}
|
||||
if policyResource.Name != nil && *policyResource.Name != name {
|
||||
return false
|
||||
}
|
||||
|
||||
if policyResource.Selector != nil {
|
||||
selector, err := metav1.LabelSelectorAsSelector(policyResource.Selector)
|
||||
if policyResource.Selector != nil {
|
||||
selector, err := metav1.LabelSelectorAsSelector(policyResource.Selector)
|
||||
|
||||
if err != nil {
|
||||
// TODO: log that selector is invalid
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
// TODO: log that selector is invalid
|
||||
return false
|
||||
}
|
||||
|
||||
labelMap := parseLabelsFromMetadata(meta)
|
||||
labelMap := parseLabelsFromMetadata(meta)
|
||||
|
||||
if !selector.Matches(labelMap) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if !selector.Matches(labelMap) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
return true
|
||||
}
|
||||
|
||||
func parseMetadataFromObject(bytes []byte) map[string]interface{} {
|
||||
var objectJSON map[string]interface{}
|
||||
json.Unmarshal(bytes, &objectJSON)
|
||||
var objectJSON map[string]interface{}
|
||||
json.Unmarshal(bytes, &objectJSON)
|
||||
|
||||
return objectJSON["metadata"].(map[string]interface{})
|
||||
return objectJSON["metadata"].(map[string]interface{})
|
||||
}
|
||||
|
||||
func parseLabelsFromMetadata(meta map[string]interface{}) labels.Set {
|
||||
if interfaceMap, ok := meta["labels"].(map[string]interface{}); ok {
|
||||
labelMap := make(labels.Set, len(interfaceMap))
|
||||
if interfaceMap, ok := meta["labels"].(map[string]interface{}); ok {
|
||||
labelMap := make(labels.Set, len(interfaceMap))
|
||||
|
||||
for key, value := range interfaceMap {
|
||||
labelMap[key] = value.(string)
|
||||
}
|
||||
return labelMap
|
||||
}
|
||||
return nil
|
||||
for key, value := range interfaceMap {
|
||||
labelMap[key] = value.(string)
|
||||
}
|
||||
return labelMap
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseNameFromMetadata(meta map[string]interface{}) string {
|
||||
if name, ok := meta["name"].(string); ok {
|
||||
return name
|
||||
}
|
||||
return ""
|
||||
if name, ok := meta["name"].(string); ok {
|
||||
return name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue