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

Feature/admission controller #189

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
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
61 changes: 61 additions & 0 deletions chart/kubecop/templates/admission-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{{- $svcName := (printf "armo-admission-webhook.%s.svc" .Release.Namespace) -}}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "..fullname" . }}-admission
namespace: {{ .Release.Namespace }}
labels:
app: {{ include "..name" . }}-admission
{{- include "..labels" . | nindent 4 }}
spec:
replicas: 1 # Set as needed
selector:
matchLabels:
app: {{ include "..name" . }}-admission
{{- include "..selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
app: {{ include "..name" . }}-admission
{{- include "..labels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "..serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}-admission
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- --mode-admission-controller
securityContext:
{{- toYaml .Values.securityContextNormal | nindent 12 }}
resources:
{{- toYaml .Values.kubecop.resources | nindent 12 }}
env:
{{- if .Values.isNamespaced }}
- name: STORE_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
{{- end }}
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
ports:
- containerPort: 8443
name: https
protocol: TCP
volumeMounts:
- name: tls-certs
mountPath: /etc/certs
readOnly: true
volumes:
- name: tls-certs
secret:
secretName: {{ $svcName }}-armo-tls-pair
tolerations:
{{- toYaml .Values.tolerations | nindent 8 }}
16 changes: 16 additions & 0 deletions chart/kubecop/templates/admission-service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: "armo-admission-webhook"
namespace: {{ .Release.Namespace }}
labels:
app: {{ include "..name" . }}-admission
{{ include "..labels" . | nindent 4 }}
spec:
ports:
- port: 443
targetPort: 8443
selector:
app: {{ include "..name" . }}-admission
{{ include "..selectorLabels" . | nindent 4 }}
type: ClusterIP # Or use LoadBalancer or NodePort if needed
37 changes: 37 additions & 0 deletions chart/kubecop/templates/admission-webhook.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{{- $ca := genCA (printf "*.%s.svc" .Release.Namespace) 1024 -}}
{{- $svcName := (printf "armo-admission-webhook.%s.svc" .Release.Namespace) -}}
{{- $cert := genSignedCert $svcName nil (list $svcName) 1024 $ca -}}
---
apiVersion: v1
kind: Secret
metadata:
name: {{ $svcName }}-armo-tls-pair
namespace: {{ .Release.Namespace }}
type: kubernetes.io/tls
data:
tls.key: {{ $cert.Key | b64enc }}
tls.crt: {{ $cert.Cert | b64enc }}
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: validation
webhooks:
- name: validation.armo.admission
clientConfig:
service:
name: armo-admission-webhook
namespace: {{ .Release.Namespace }}
path: /validate
port: 443
caBundle: {{ $ca.Cert | b64enc }}
admissionReviewVersions: ["v1"]
sideEffects: None
rules:
- operations: ["CREATE", "UPDATE", "DELETE", "CONNECT"]
apiGroups: ["*"]
apiVersions: ["v1"]
resources: ["pods", "pods/exec", "pods/portforward", "pods/attach", "clusterrolebindings", "rolebindings"]
scope: "*"
failurePolicy: Ignore

4 changes: 2 additions & 2 deletions chart/kubecop/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ metadata:
name: {{ include "..fullname" . }}
rules:
- apiGroups: [""]
resources: ["namespaces", "pods", "serviceaccounts", "services"]
resources: ["namespaces", "pods", "serviceaccounts", "services", "clusterrolebindings", "rolebindings", "roles", "clusterroles"]
verbs: ["list", "get", "watch"]
- apiGroups: ["apps","batch","extensions"]
- apiGroups: ["apps","batch","extensions", "rbac.authorization.k8s.io"]
resources: ["*"]
verbs: ["get"]
- apiGroups: ["kubescape.io"]
Expand Down
63 changes: 49 additions & 14 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"

Expand All @@ -14,6 +15,7 @@ import (
"net/http"
_ "net/http/pprof"

"github.com/armosec/kubecop/pkg/admission/webhook"
"github.com/armosec/kubecop/pkg/approfilecache"
"github.com/armosec/kubecop/pkg/engine"
"github.com/armosec/kubecop/pkg/exporters"
Expand All @@ -26,6 +28,7 @@ import (
"github.com/kubescape/kapprofiler/pkg/tracing"
"github.com/prometheus/client_golang/prometheus/promhttp"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
Expand Down Expand Up @@ -147,6 +150,7 @@ func main() {
// Parse command line arguments to check which mode to run in
controllerMode := false
nodeAgentMode := false
admissionControllerMode := false
storeNamespace := ""

for _, arg := range os.Args[1:] {
Expand All @@ -159,6 +163,10 @@ func main() {
// Run in node agent mode
nodeAgentMode = true
log.Printf("Running in node agent mode")
case "--mode-admission-controller":
// Run in admission controller mode
admissionControllerMode = true
log.Printf("Running in admission controller mode")
default:
log.Fatalf("Unknown command line argument: %s", arg)
}
Expand All @@ -168,10 +176,11 @@ func main() {
storeNamespace = ns
}

if !controllerMode && !nodeAgentMode {
if !controllerMode && !nodeAgentMode && !admissionControllerMode {
controllerMode = true
nodeAgentMode = true
log.Printf("Running in both controller and node agent mode")
admissionControllerMode = true
log.Printf("Running in all modes (controller, node agent, admission controller)")
}

// Start the pprof server if _PPROF_SERVER environment variable is set
Expand All @@ -186,9 +195,20 @@ func main() {
log.Fatalf("Failed to initialize service: %v\n", err)
}

// TODO: support exporters config from file/crd
exporterBus := exporters.InitExporters(exporters.ExportersConfig{})

// Create Kubernetes clientset from the configuration
clientset, err := kubernetes.NewForConfig(k8sConfig)
if err != nil {
log.Fatalf("Failed to create Kubernetes client: %v\n", err)
}
dynamicClient, err := dynamic.NewForConfig(k8sConfig)
if err != nil {
log.Fatalf("Failed to create Kubernetes dynamic client: %v\n", err)
}

if nodeAgentMode {
// TODO: support exporters config from file/crd
exporterBus := exporters.InitExporters(exporters.ExportersConfig{})
// Create tracer (without sink for now)
tracer := tracing.NewTracer(NodeName, k8sConfig, []tracing.EventSink{}, false)
// Create application profile cache
Expand Down Expand Up @@ -234,16 +254,6 @@ func main() {
//////////////////////////////////////////////////////////////////////////////
// Recording subsystem is ready, start the rule engine

// Create Kubernetes clientset from the configuration
clientset, err := kubernetes.NewForConfig(k8sConfig)
if err != nil {
log.Fatalf("Failed to create Kubernetes client: %v\n", err)
}
dynamicClient, err := dynamic.NewForConfig(k8sConfig)
if err != nil {
log.Fatalf("Failed to create Kubernetes dynamic client: %v\n", err)
}

// Create the "Rule Engine" and start it
engine := engine.NewEngine(clientset, appProfileCache, tracer, &exporterBus, 4, NodeName)

Expand Down Expand Up @@ -335,6 +345,31 @@ func main() {
defer appProfileReconcilerController.StopController()
}

if admissionControllerMode {
// Handle SIGINT and SIGTERM by cancelling the root context
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()

waitGroup := sync.WaitGroup{}
serverContext, serverCancel := context.WithCancel(ctx)

addr := ":8443"

admissionController := webhook.New(addr, "/etc/certs/tls.crt", "/etc/certs/tls.key", exporterBus, runtime.NewScheme(), webhook.NewAdmissionValidator(clientset))
// Start HTTP REST server for webhook
waitGroup.Add(1)
go func() {
defer func() {
// Cancel the server context to stop other workers
serverCancel()
waitGroup.Done()
}()

cancellationReason := admissionController.Run(serverContext)
log.Printf("Server stopped: %v", cancellationReason)
}()
}

// Start prometheus metrics server
go func() {
http.Handle("/metrics", promhttp.Handler())
Expand Down
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@ require (
k8s.io/api v0.29.2
k8s.io/apiextensions-apiserver v0.29.2
k8s.io/apimachinery v0.29.2
k8s.io/apiserver v0.29.2
k8s.io/client-go v0.29.2
sigs.k8s.io/yaml v1.4.0
)

require (
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/moby/sys/user v0.1.0 // indirect
github.com/vishvananda/netlink v1.2.1-beta.2 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0 // indirect
k8s.io/component-base v0.29.2 // indirect
)

require (
Expand Down
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
Expand Down Expand Up @@ -563,8 +565,12 @@ k8s.io/apiextensions-apiserver v0.29.2 h1:UK3xB5lOWSnhaCk0RFZ0LUacPZz9RY4wi/yt2I
k8s.io/apiextensions-apiserver v0.29.2/go.mod h1:aLfYjpA5p3OwtqNXQFkhJ56TB+spV8Gc4wfMhUA3/b8=
k8s.io/apimachinery v0.29.2 h1:EWGpfJ856oj11C52NRCHuU7rFDwxev48z+6DSlGNsV8=
k8s.io/apimachinery v0.29.2/go.mod h1:6HVkd1FwxIagpYrHSwJlQqZI3G9LfYWRPAkUvLnXTKU=
k8s.io/apiserver v0.29.2 h1:+Z9S0dSNr+CjnVXQePG8TcBWHr3Q7BmAr7NraHvsMiQ=
k8s.io/apiserver v0.29.2/go.mod h1:B0LieKVoyU7ykQvPFm7XSdIHaCHSzCzQWPFa5bqbeMQ=
k8s.io/client-go v0.29.2 h1:FEg85el1TeZp+/vYJM7hkDlSTFZ+c5nnK44DJ4FyoRg=
k8s.io/client-go v0.29.2/go.mod h1:knlvFZE58VpqbQpJNbCbctTVXcd35mMyAAwBdpt4jrA=
k8s.io/component-base v0.29.2 h1:lpiLyuvPA9yV1aQwGLENYyK7n/8t6l3nn3zAtFTJYe8=
k8s.io/component-base v0.29.2/go.mod h1:BfB3SLrefbZXiBfbM+2H1dlat21Uewg/5qtKOl8degM=
k8s.io/cri-api v0.29.2 h1:LLSeWVC3h1nVMpV9vHiE+mO3spDYmz/C0GvxH6p6tkg=
k8s.io/cri-api v0.29.2/go.mod h1:9fQTFm+wi4FLyqrkVUoMJiUB3mE74XrVvHz8uFY/sSw=
k8s.io/klog/v2 v2.110.1 h1:U/Af64HJf7FcwMcXyKm2RPM22WZzyR7OSpYj5tg3cL0=
Expand Down
25 changes: 25 additions & 0 deletions pkg/admission/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package admission

// Contains the data on the al
type AdmissionControlData struct {
// The user who sent the request
User string `json:"user"`
// The user groups
Groups []string `json:"groups"`
// The user UID
UID string `json:"uid"`
// The namespace of the request
Namespace string `json:"namespace"`
// The name of the request
Name string `json:"name"`
// The operation of the request
Operation string `json:"operation"`
// The kind of the request
Kind string `json:"kind"`
// The request resource
Resource string `json:"resource"`
// The request subresource
Subresource string `json:"subresource"`
// The request response message
ResponseMessage string `json:"responseMessage"`
}
Loading
Loading