From 39ed562c1ba8a96c59975cf9dcf3c092d241967e Mon Sep 17 00:00:00 2001 From: mtardy Date: Thu, 13 Oct 2022 17:35:47 +0200 Subject: [PATCH] Use server side dry run on admission control scan Added a flag --admission-create to replicate the old behavior but the scan now by default run with --dry-run=server so no cleaning is needed. Shoutout to @smarticu5 for the idea at KubeHuddle 2022! --- README.md | 127 +++++++++++++++-------------- commands/dig.go | 30 +++---- pkg/bucket/bucket.go | 3 + pkg/plugins/admission/admission.go | 27 ++++-- 4 files changed, 103 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index fc99bed..e53b1b3 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,7 @@ Aliases: dig, d Flags: + --admission-create Actually create pods to scan admission instead of using server dry run. (this flag is specific to the admission bucket) --admission-force Force creation of pods to scan admission even without cleaning rights. (this flag is specific to the admission bucket) -c, --color Enable color in output. (default true if output is human) -h, --help help for dig @@ -244,6 +245,9 @@ instance, if the `hostname` syscall is successful, it will replace the hostname with the empty string. So please, **NEVER** run with sufficient permissions (as root for example) directly on your machine. +The admission scan will by default run as server dry-run but will generate API +server logs. + ### Results warning Some tests are based on details of implementation or side effects on the @@ -345,69 +349,71 @@ You can list and describe the available buckets (or plugins) with `kdigger list` or `kdigger ls`: ```console $ kdigger ls -+---------------+----------------------------+--------------------------------------+-------------+---------------+ -| NAME | ALIASES | DESCRIPTION | SIDEEFFECTS | REQUIRECLIENT | -+---------------+----------------------------+--------------------------------------+-------------+---------------+ -| admission | [admissions adm] | Admission scans the admission | true | true | -| | | controller chain by creating | | | -| | | specific pods to find what is | | | -| | | prevented or not. | | | -| apiresources | [api apiresource] | APIResources discovers the available | false | true | -| | | APIs of the cluster. | | | -| authorization | [authorizations auth] | Authorization checks your API | false | true | -| | | permissions with the current context | | | -| | | or the available token. | | | -| capabilities | [capability cap] | Capabilities lists all capabilities | false | false | -| | | in all sets and displays dangerous | | | -| | | capabilities in red. | | | -| cgroups | [cgroup cg] | Cgroups reads the /proc/self/cgroup | false | false | -| | | files that can leak information | | | -| | | under cgroups v1. | | | -| cloudmetadata | [cloud meta] | Cloudmetadata scans the usual | false | false | -| | | metadata endpoints in public clouds. | | | -| devices | [device dev] | Devices shows the list of devices | false | false | -| | | available in the container. | | | -| environment | [environments environ env] | Environment checks the presence of | false | false | -| | | kubernetes related environment | | | -| | | variables and shows them. | | | -| mount | [mounts mn] | Mount shows all mounted devices in | false | false | -| | | the container. | | | -| node | [nodes n] | Node retrieves various information | false | false | -| | | in /proc about the current host. | | | -| pidnamespace | [pidnamespaces pidns] | PIDnamespace analyses the PID | false | false | -| | | namespace of the container in the | | | -| | | context of Kubernetes. | | | -| processes | [process ps] | Processes analyses the running | false | false | -| | | processes in your PID namespace | | | -| runtime | [runtimes rt] | Runtime finds clues to identify | false | false | -| | | which container runtime is running | | | -| | | the container. | | | -| services | [service svc] | Services uses CoreDNS wildcards | false | false | -| | | feature to discover every service | | | -| | | available in the cluster. | | | -| syscalls | [syscall sys] | Syscalls scans most of the syscalls | true | false | -| | | to detect which are blocked and | | | -| | | allowed. | | | -| token | [tokens tk] | Token checks for the presence of a | false | false | -| | | service account token in the | | | -| | | filesystem. | | | -| userid | [userids id] | UserID retrieves UID, GID and their | false | false | -| | | corresponding names. | | | -| usernamespace | [usernamespaces userns] | UserNamespace analyses the user | false | false | -| | | namespace configuration. | | | -| version | [versions v] | Version dumps the API server version | false | true | -| | | informations. | | | -+---------------+----------------------------+--------------------------------------+-------------+---------------+ ++-----------------+----------------------------+----------------------------------------+-------------+---------------+ +| NAME | ALIASES | DESCRIPTION | SIDEEFFECTS | REQUIRECLIENT | ++-----------------+----------------------------+----------------------------------------+-------------+---------------+ +| admission | [admissions adm] | Admission scans the admission | true | true | +| | | controller chain by creating (by | | | +| | | default with dry run) specific pods to | | | +| | | find what is prevented or not. | | | +| apiresources | [api apiresource] | APIResources discovers the available | false | true | +| | | APIs of the cluster. | | | +| authorization | [authorizations auth] | Authorization checks your API | false | true | +| | | permissions with the current context | | | +| | | or the available token. | | | +| capabilities | [capability cap] | Capabilities lists all capabilities in | false | false | +| | | all sets and displays dangerous | | | +| | | capabilities in red. | | | +| cgroups | [cgroup cg] | Cgroups reads the /proc/self/cgroup | false | false | +| | | files that can leak information under | | | +| | | cgroups v1. | | | +| cloudmetadata | [cloud meta] | Cloudmetadata scans the usual metadata | false | false | +| | | endpoints in public clouds. | | | +| containerdetect | [container cdetect] | ContainerDetect retrieves hints that | false | false | +| | | the process is running inside a | | | +| | | typical container. | | | +| devices | [device dev] | Devices shows the list of devices | false | false | +| | | available in the container. | | | +| environment | [environments environ env] | Environment checks the presence of | false | false | +| | | kubernetes related environment | | | +| | | variables and shows them. | | | +| mount | [mounts mn] | Mount shows all mounted devices in the | false | false | +| | | container. | | | +| node | [nodes n] | Node retrieves various information in | false | false | +| | | /proc about the current host. | | | +| pidnamespace | [pidnamespaces pidns] | PIDnamespace analyses the PID | false | false | +| | | namespace of the container in the | | | +| | | context of Kubernetes. | | | +| processes | [process ps] | Processes analyses the running | false | false | +| | | processes in your PID namespace | | | +| runtime | [runtimes rt] | Runtime finds clues to identify which | false | false | +| | | container runtime is running the | | | +| | | container. | | | +| services | [service svc] | Services uses CoreDNS wildcards | false | false | +| | | feature to discover every service | | | +| | | available in the cluster. | | | +| syscalls | [syscall sys] | Syscalls scans most of the syscalls to | true | false | +| | | detect which are blocked and allowed. | | | +| token | [tokens tk] | Token checks for the presence of a | false | false | +| | | service account token in the | | | +| | | filesystem. | | | +| userid | [userids id] | UserID retrieves UID, GID and their | false | false | +| | | corresponding names. | | | +| usernamespace | [usernamespaces userns] | UserNamespace analyses the user | false | false | +| | | namespace configuration. | | | +| version | [versions v] | Version dumps the API server version | false | true | +| | | informations. | | | ++-----------------+----------------------------+----------------------------------------+-------------+---------------+ ``` ### Admission -Admission scans the admission controller chain by creating specific pods to -find what is prevented or not. The idea behind this bucket is to check, after -you learned that you have `create pods` ability, if no admission controller -like a PodSecurityPolicy or another is blocking you to create node privilege -escalation pods. Like mounting the host filesystem, or the host PID namespace, -or just a privileged container, for example. +Admission scans the admission controller chain by creating (by default with dry +run) specific pods to find what is prevented or not. The idea behind this bucket +is to check, after you learned that you have `create pods` ability, if no +admission controller like a PodSecurityPolicy or another is blocking you to +create node privilege escalation pods. Like mounting the host filesystem, or the +host PID namespace, or just a privileged container, for example. This bucket currently automatically tries to create: - a privileged pod @@ -420,6 +426,9 @@ This bucket currently automatically tries to create: So, if you are granted rights to `create pods`, you can check the presence of any admission controller that might restrict you. +Note that it uses `--dry-run=server` by default but you can really create the +pods with the `--admission-create` admission plugin specific flag. + ### API Resources APIResources discovers the available APIs of the cluster. These endpoints are diff --git a/commands/dig.go b/commands/dig.go index 1f41fad..ec70a50 100644 --- a/commands/dig.go +++ b/commands/dig.go @@ -19,19 +19,16 @@ var kubeconfig string // flag for the namespace var namespace string -// flag for the color -var color bool - // flag to activate side effects buckets var sideEffects bool -// flag to force admission creation -var admForce bool - // output formats const outputHuman = "human" const outputJSON = "json" +// config that will carry parameters and client for plugin init +var pluginConfig bucket.Config + // digCmd represents the dig command var digCmd = &cobra.Command{ Use: "dig [buckets]", @@ -53,7 +50,7 @@ arguments.`, // apply default colored human only if the color flag was not set if !cmd.Flags().Changed("color") && output == outputHuman { - color = true + pluginConfig.Color = true } // check if any called buckets have side effects without the flag activated @@ -74,13 +71,6 @@ arguments.`, return nil }, RunE: func(cmd *cobra.Command, args []string) error { - // create the config that will be passed to every plugins - config := &bucket.Config{ - Color: color, - OutputWidth: outputWidth, - AdmForce: admForce, - } - // handles the "all" or "a" and erase the args with the bucket list // PreRun should guarantee that len(args) != 0 but in case if len(args) != 0 { @@ -107,7 +97,7 @@ arguments.`, for _, name := range args { // initialize the bucket if buckets.RequiresClient(name) { - err := loadContext(config) + err := loadContext(&pluginConfig) if err != nil { // loading the context failed and is required so skip this // execution after printing the error with the name @@ -118,7 +108,7 @@ arguments.`, continue } } - b, err := buckets.InitBucket(name, *config) + b, err := buckets.InitBucket(name, pluginConfig) if err != nil { return err } @@ -191,7 +181,11 @@ func init() { } digCmd.Flags().StringVarP(&namespace, "namespace", "n", "", "Kubernetes namespace to use. (default to the namespace in the context)") - digCmd.Flags().BoolVarP(&color, "color", "c", false, "Enable color in output. (default true if output is human)") digCmd.Flags().BoolVarP(&sideEffects, "side-effects", "s", false, "Enable all buckets that might have side effect on environment.") - digCmd.Flags().BoolVarP(&admForce, "admission-force", "", false, "Force creation of pods to scan admission even without cleaning rights. (this flag is specific to the admission bucket)") + + digCmd.Flags().BoolVarP(&pluginConfig.Color, "color", "c", false, "Enable color in output. (default true if output is human)") + digCmd.Flags().BoolVarP(&pluginConfig.AdmForce, "admission-force", "", false, "Force creation of pods to scan admission even without cleaning rights. (this flag is specific to the admission bucket)") + digCmd.Flags().BoolVarP(&pluginConfig.AdmCreate, "admission-create", "", false, "Actually create pods to scan admission instead of using server dry run. (this flag is specific to the admission bucket)") + // this one is retrieved from the root cmd because applicable to many cmds + pluginConfig.OutputWidth = outputWidth } diff --git a/pkg/bucket/bucket.go b/pkg/bucket/bucket.go index 2b140f2..2a9a529 100644 --- a/pkg/bucket/bucket.go +++ b/pkg/bucket/bucket.go @@ -55,6 +55,9 @@ type Config struct { // This options is specific to the admission plugin, is it to force creation // even if we can't cleanup the mess with delete AdmForce bool + // This options is specific to the admission plugin, is it to actually create + // pod instead of use the dry run + AdmCreate bool } func NewBuckets() *Buckets { diff --git a/pkg/plugins/admission/admission.go b/pkg/plugins/admission/admission.go index 6129921..9bdfba6 100644 --- a/pkg/plugins/admission/admission.go +++ b/pkg/plugins/admission/admission.go @@ -15,7 +15,7 @@ import ( const ( bucketName = "admission" - bucketDescription = "Admission scans the admission controller chain by creating specific pods to find what is prevented or not." + bucketDescription = "Admission scans the admission controller chain by creating (by default with dry run) specific pods to find what is prevented or not." ) var bucketAliases = []string{"admissions", "adm"} @@ -55,8 +55,8 @@ func Register(b *bucket.Buckets) { // Run runs the admission test. func (a *Bucket) Run() (bucket.Results, error) { res := bucket.NewResults(bucketName) - if !a.config.AdmForce && !a.CanIDelete() { - return *res, errors.New("cannot delete pod, will not be able to clean the scan artifacts, force creation --admission-force") + if a.config.AdmCreate && !a.config.AdmForce && !a.CanIDelete() { + return *res, errors.New("cannot delete pod, will not be able to clean the scan artifacts, force creation with --admission-force") } a.initialize() c := make(chan admissionResult, len(a.podFactoryChain)) @@ -105,13 +105,26 @@ func (a *Bucket) Run() (bucket.Results, error) { func (a *Bucket) use(f podFactory) error { pod := f.NewPod() - pod, err := a.client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{}) + + // activate server dry run by default + var createOptions metav1.CreateOptions + if !a.config.AdmCreate { + createOptions = metav1.CreateOptions{ + DryRun: []string{"All"}, + } + } + + pod, err := a.client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, createOptions) if err != nil { return err } - a.cleaningLock.Lock() - a.podsToClean = append(a.podsToClean, pod) - a.cleaningLock.Unlock() + + // clean only if we actually created the pod + if a.config.AdmCreate { + a.cleaningLock.Lock() + a.podsToClean = append(a.podsToClean, pod) + a.cleaningLock.Unlock() + } return nil }