Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a configuration file #56

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions audit/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package audit

import (
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/record"

"github.com/jlevesy/kudo/controller"
"github.com/jlevesy/kudo/pkg/apis/k8s.kudo.dev/v1alpha1"
"github.com/jlevesy/kudo/pkg/generated/clientset/versioned/scheme"
)

const (
K8sEventsSink = "K8sEvents"
)

func BuildSinkFromConfig(cfg controller.AuditConfig, kubeClient kubernetes.Interface) (Sink, error) {
var sinks multiAsyncSink

for _, sinkCfg := range cfg.Sinks {
switch sinkCfg.Kind {
case K8sEventsSink:
k8sCfg, err := v1alpha1.DecodeValueWithKind[controller.K8sEventsConfig](sinkCfg)
if err != nil {
return nil, err
}

eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartStructuredLogging(0)
eventBroadcaster.StartRecordingToSink(
&typedcorev1.EventSinkImpl{
Interface: kubeClient.CoreV1().Events(k8sCfg.Namespace),
},
)

sinks = append(
sinks,
NewK8sEventSink(
eventBroadcaster.NewRecorder(
scheme.Scheme,
corev1.EventSource{Component: "kudo-controller"},
),
),
)
default:
return nil, fmt.Errorf("unsupported sink kind %q", sinkCfg.Kind)
}

}

return sinks, nil
}
71 changes: 32 additions & 39 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,50 @@ import (
"os"
"os/signal"
"syscall"
"time"

"golang.org/x/sync/errgroup"
corev1 "k8s.io/api/core/v1"
kubeinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/record"
"k8s.io/klog/v2"

"github.com/jlevesy/kudo/audit"
"github.com/jlevesy/kudo/controller"
"github.com/jlevesy/kudo/escalation"
"github.com/jlevesy/kudo/escalationpolicy"
"github.com/jlevesy/kudo/grant"
kudov1alpha1 "github.com/jlevesy/kudo/pkg/apis/k8s.kudo.dev/v1alpha1"
"github.com/jlevesy/kudo/pkg/controllersupport"
clientset "github.com/jlevesy/kudo/pkg/generated/clientset/versioned"
"github.com/jlevesy/kudo/pkg/generated/clientset/versioned/scheme"
kudoinformers "github.com/jlevesy/kudo/pkg/generated/informers/externalversions"
"github.com/jlevesy/kudo/pkg/webhooksupport"
)

var (
masterURL string
kubeconfig string
threadiness int
resyncInterval time.Duration
retryInterval time.Duration

webhookConfig webhooksupport.ServerConfig
configPath string
kubeConfig string
masterURL string
)

const defaultInformerResyncInterval = time.Hour

func main() {
flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.")
flag.StringVar(&configPath, "config", "", "Path to Kudo Configuration")
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(&webhookConfig.CertPath, "webhook_cert", "", "Path to webhook TLS cert")
flag.StringVar(&webhookConfig.KeyPath, "webhook_key", "", "Path to webhook TLS key")
flag.StringVar(&webhookConfig.Addr, "webhook_addr", ":8080", "Webhook listening address")
flag.IntVar(&threadiness, "threadiness", 10, "Amount of events processed in paralled")
flag.DurationVar(&resyncInterval, "resync_interval", 30*time.Second, "Maximum period to resync an active escalation")
flag.DurationVar(&retryInterval, "retry_interval", 10*time.Second, "Maximum period retry an escalation not fully granted/reclaimed")
klog.InitFlags(nil)
defer klog.Flush()

flag.Parse()

klog.Info("Loading Kudo controller configuration", "path", configPath)

kudoCfg, err := controller.LoadConfigurationFromFile(configPath)
if err != nil {
klog.Fatalf("Unable to load kudo configuration: %s", err.Error())
}

klog.Info("Starting kudo controller")

cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfig)
cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeConfig)
if err != nil {
klog.Fatalf("Unable to build kube client configuration: %s", err.Error())
}
Expand All @@ -75,15 +68,22 @@ func main() {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()

eventBroadcaster := record.NewBroadcaster()
eventBroadcaster.StartStructuredLogging(0)
eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")})
auditSink, err := audit.BuildSinkFromConfig(kudoCfg.Audit, kubeClient)
if err != nil {
klog.Fatalf("Unable to build audit sink: %s", err.Error())
}

var (
serveMux = http.NewServeMux()

kubeInformerFactory = kubeinformers.NewSharedInformerFactory(kubeClient, defaultInformerResyncInterval)
kudoInformerFactory = kudoinformers.NewSharedInformerFactory(kudoClientSet, defaultInformerResyncInterval)
kubeInformerFactory = kubeinformers.NewSharedInformerFactory(
kubeClient,
kudoCfg.Controller.InformerResyncInterval.Duration,
)
kudoInformerFactory = kudoinformers.NewSharedInformerFactory(
kudoClientSet,
kudoCfg.Controller.InformerResyncInterval.Duration,
)
escalationsInformer = kudoInformerFactory.K8s().V1alpha1().Escalations().Informer()
escalationsClient = kudoClientSet.K8sV1alpha1().Escalations()
policiesLister = kudoInformerFactory.K8s().V1alpha1().EscalationPolicies().Lister()
Expand All @@ -95,19 +95,12 @@ func main() {
policiesLister,
escalationsClient,
granterFactory,
audit.MutliAsyncSink(
audit.NewK8sEventSink(
eventBroadcaster.NewRecorder(
scheme.Scheme,
corev1.EventSource{Component: "kudo-controller"},
),
),
),
escalation.WithResyncInterval(resyncInterval),
escalation.WithRetryInterval(retryInterval),
auditSink,
escalation.WithResyncInterval(kudoCfg.Controller.ResyncInterval.Duration),
escalation.WithRetryInterval(kudoCfg.Controller.RetryInterval.Duration),
),
kudov1alpha1.KindEscalation,
threadiness,
kudoCfg.Controller.Threadiness,
)
)

Expand Down Expand Up @@ -135,7 +128,7 @@ func main() {
klog.Info("Informers warmed up, starting controller...")

group.Go(func() error {
return webhooksupport.Serve(ctx, webhookConfig, serveMux)
return webhooksupport.Serve(ctx, kudoCfg.Webhook, serveMux)
})

group.Go(func() error {
Expand Down
81 changes: 81 additions & 0 deletions controller/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package controller

import (
"os"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/yaml"

"github.com/jlevesy/kudo/pkg/apis/k8s.kudo.dev/v1alpha1"
)

var defaultConfig = Config{
Audit: AuditConfig{
Sinks: []v1alpha1.ValueWithKind{
v1alpha1.MustEncodeValueWithKind(
"K8sEvents",
K8sEventsConfig{
Namespace: "",
},
),
},
},
Controller: ControllerConfig{
ResyncInterval: metav1.Duration{Duration: 30 * time.Second},
RetryInterval: metav1.Duration{Duration: 10 * time.Second},
InformerResyncInterval: metav1.Duration{Duration: 1 * time.Hour},
Threadiness: 10,
},
Webhook: WebhookConfig{
CertPath: "/var/run/certs/tls.crt",
KeyPath: "/var/run/certs/tls.key",
Addr: ":8443",
ReadTimeout: metav1.Duration{
Duration: 20 * time.Second,
},
WriteTimeout: metav1.Duration{
Duration: 20 * time.Second,
},
},
}

type Config struct {
Audit AuditConfig `json:"audit"`
Controller ControllerConfig `json:"controller"`
Webhook WebhookConfig `json:"webhook"`
}

type AuditConfig struct {
Sinks []v1alpha1.ValueWithKind `json:"sinks"`
}

type ControllerConfig struct {
ResyncInterval metav1.Duration `json:"resyncInterval"`
RetryInterval metav1.Duration `json:"retryInterval"`
InformerResyncInterval metav1.Duration `json:"informerResyncInterval"`
Threadiness int `json:"threadiness"`
}

type WebhookConfig struct {
Addr string `json:"addr"`
CertPath string `json:"certPath"`
KeyPath string `json:"keyPath"`
ReadTimeout metav1.Duration `json:"readTimeout"`
WriteTimeout metav1.Duration `json:"writeTimeout"`
}

type K8sEventsConfig struct {
Namespace string `json:"namespace"`
}

func LoadConfigurationFromFile(path string) (Config, error) {
bytes, err := os.ReadFile(path)
if err != nil {
return Config{}, err
}

var cfg = defaultConfig

return cfg, yaml.Unmarshal(bytes, &cfg)
}
79 changes: 79 additions & 0 deletions controller/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package controller_test

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/jlevesy/kudo/controller"
"github.com/jlevesy/kudo/pkg/apis/k8s.kudo.dev/v1alpha1"
)

func TestLoadConfig(t *testing.T) {
cfg, err := controller.LoadConfigurationFromFile("./testdata/config.yaml")
require.NoError(t, err)

wantConfig := controller.Config{
Audit: controller.AuditConfig{
Sinks: []v1alpha1.ValueWithKind{
v1alpha1.MustEncodeValueWithKind(
"K8sEvents",
controller.K8sEventsConfig{
Namespace: "some-namespace",
},
),
},
},
Controller: controller.ControllerConfig{
ResyncInterval: metav1.Duration{Duration: 50 * time.Second},
RetryInterval: metav1.Duration{Duration: 10 * time.Second},
InformerResyncInterval: metav1.Duration{Duration: 30 * time.Minute},
Threadiness: 50,
},
Webhook: controller.WebhookConfig{
CertPath: "/some/path/cert.pem",
KeyPath: "/some/path/key.pem",
Addr: ":8444",
ReadTimeout: metav1.Duration{Duration: 50 * time.Second},
WriteTimeout: metav1.Duration{Duration: 20 * time.Second},
},
}

assert.Equal(t, wantConfig, cfg)
}

func TestLoadConfig_AppliesDefaultConfig(t *testing.T) {
cfg, err := controller.LoadConfigurationFromFile("./testdata/partial_config.yaml")
require.NoError(t, err)

wantConfig := controller.Config{
Audit: controller.AuditConfig{
Sinks: []v1alpha1.ValueWithKind{
v1alpha1.MustEncodeValueWithKind(
"K8sEvents",
controller.K8sEventsConfig{
Namespace: "some-namespace",
},
),
},
},
Controller: controller.ControllerConfig{
ResyncInterval: metav1.Duration{Duration: 30 * time.Second},
RetryInterval: metav1.Duration{Duration: 10 * time.Second},
InformerResyncInterval: metav1.Duration{Duration: time.Hour},
Threadiness: 10,
},
Webhook: controller.WebhookConfig{
CertPath: "/var/run/certs/tls.crt",
KeyPath: "/var/run/certs/tls.key",
Addr: ":8443",
ReadTimeout: metav1.Duration{Duration: 20 * time.Second},
WriteTimeout: metav1.Duration{Duration: 20 * time.Second},
},
}

assert.Equal(t, wantConfig, cfg)
}
17 changes: 17 additions & 0 deletions controller/testdata/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
audit:
sinks:
- kind: K8sEvents
namespace: "some-namespace"

controller:
resyncInterval: 50s
retryInterval: 10s
informerResyncInterval: 30m
threadiness: 50

webhook:
certPath: "/some/path/cert.pem"
keyPath: "/some/path/key.pem"
addr: ":8444"
readTimeout: 50s
writeTimeout: 20s
4 changes: 4 additions & 0 deletions controller/testdata/partial_config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
audit:
sinks:
- kind: K8sEvents
namespace: "some-namespace"
4 changes: 2 additions & 2 deletions e2e/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,8 @@ func installKudo(ctx context.Context, kubeConfigPath string) error {
"--create-namespace",
"--namespace=" + kudoInstallNamespace,
"--set=image.devRef=" + imageRef,
"--set=controller.resyncInterval=5s",
"--set=controller.retryInterval=1s",
"--set=config.controller.resyncInterval=5s",
"--set=config.controller.retryInterval=1s",
"--set=resources.limits.cpu=1",
"--set=resources.requests.cpu=1",
"--wait",
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
k8s.io/cli-runtime v0.25.1
k8s.io/client-go v0.25.1
k8s.io/klog/v2 v2.80.1
sigs.k8s.io/yaml v1.2.0
)

require (
Expand Down Expand Up @@ -67,5 +68,4 @@ require (
sigs.k8s.io/kustomize/api v0.12.1 // indirect
sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)
Loading