Skip to content

Commit

Permalink
Add a pod template generation command (#5)
Browse files Browse the repository at this point in the history
* Add a template generator command to create bad pods

* Add tests for the kgen package

* Add documentation about the new gen feature in README
  • Loading branch information
mtardy authored Jul 8, 2022
1 parent c61ab3a commit 3359013
Show file tree
Hide file tree
Showing 4 changed files with 348 additions and 0 deletions.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Usage:
Available Commands:
completion Generate the autocompletion script for the specified shell
dig Use all buckets or specific ones
gen Generate template for pod with security features disabled
help Help about any command
ls List available buckets or describe specific ones
version Print the version information
Expand All @@ -130,6 +131,7 @@ Flags:
-w, --width int Width for the human output (default 140)

Use "kdigger [command] --help" for more information about a command.

```

Make sure to check out the help on the ``dig`` command to see all the available
Expand Down Expand Up @@ -161,6 +163,50 @@ Global Flags:
-w, --width int Width for the human output (default 140)
```

You can also generate useful templates for pods with security features disabled
to escalate privileges when you can create such a pod. See the help for this
specific command for more information.

```console
$ kdigger help gen
This command generates templates for pod with security features disabled.
You can customize the pods with some of the string flags and activate
boolean flags to disabled security features. Examples:

# Generate a very simple template in json
kdigger gen -o json

# Create a very simple pod
kdigger gen | kubectl apply -f -

# Create a pod named mypod with most security features disabled
kdigger gen -all mypod | kubectl apply -f -

# Create a custom privileged pod
kdigger gen --privileged --image bash --command watch --command date | kubectl apply -f -

Usage:
kdigger gen [name] [flags]

Aliases:
gen, generate

Flags:
--all Enable everything
--command stringArray Container command used (default [sleep,infinitely])
-h, --help help for gen
--hostnetwork Add the hostNetwork flag on the whole pod
--hostpath Add a hostPath volume to the container
--hostpid Add the hostPid flag on the whole pod
--image string Container image used (default "busybox")
--privileged Add the security flag to the security context of the pod
--tolerations Add tolerations to be schedulable on most nodes

Global Flags:
-o, --output string Output format. One of: human|json. (default "human")
-w, --width int Width for the human output (default 140)
```

## Details

### Updates
Expand Down
77 changes: 77 additions & 0 deletions commands/gen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package commands

import (
"os"

"github.com/quarkslab/kdigger/pkg/kgen"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/printers"
)

var opts kgen.GenerateOpts

var genAll bool

var genCmd = &cobra.Command{
Use: "gen [name] [flags]",
Aliases: []string{"generate"},
Short: "Generate template for pod with security features disabled",
Long: `This command generates templates for pod with security features disabled.
You can customize the pods with some of the string flags and activate
boolean flags to disabled security features. Examples:
# Generate a very simple template in json
kdigger gen -o json
# Create a very simple pod
kdigger gen | kubectl apply -f -
# Create a pod named mypod with most security features disabled
kdigger gen -all mypod | kubectl apply -f -
# Create a custom privileged pod
kdigger gen --privileged --image bash --command watch --command date | kubectl apply -f -`,
RunE: func(cmd *cobra.Command, args []string) error {
// all puts all the boolean flags to true
if genAll {
opts.HostNetwork = true
opts.Privileged = true
opts.HostPath = true
opts.HostPid = true
opts.Tolerations = true
}
if len(args) > 0 {
opts.Name = args[0]
}

pod := kgen.Generate(opts)

var p printers.ResourcePrinter
if output == "json" {
p = &printers.JSONPrinter{}
} else {
p = &printers.YAMLPrinter{}
}
err := p.PrintObj(pod, os.Stdout)
if err != nil {
return err
}

return nil
},
}

func init() {
rootCmd.AddCommand(genCmd)

genCmd.Flags().StringVar(&opts.Image, "image", "busybox", "Container image used")
genCmd.Flags().StringArrayVar(&opts.Command, "command", []string{"sleep", "infinitely"}, "Container command used")

genCmd.Flags().BoolVar(&opts.Privileged, "privileged", false, "Add the security flag to the security context of the pod")
genCmd.Flags().BoolVar(&opts.HostPath, "hostpath", false, "Add a hostPath volume to the container")
genCmd.Flags().BoolVar(&opts.Tolerations, "tolerations", false, "Add tolerations to be schedulable on most nodes")
genCmd.Flags().BoolVar(&opts.HostPid, "hostpid", false, "Add the hostPid flag on the whole pod")
genCmd.Flags().BoolVar(&opts.HostNetwork, "hostnetwork", false, "Add the hostNetwork flag on the whole pod")

genCmd.Flags().BoolVar(&genAll, "all", false, "Enable everything")
}
97 changes: 97 additions & 0 deletions pkg/kgen/kgen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package kgen

import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"k8s.io/apimachinery/pkg/util/rand"
)

type GenerateOpts struct {
Name string
Image string
Command []string
Privileged bool
HostPath bool
HostPid bool
HostNetwork bool
Tolerations bool
}

func Generate(opts GenerateOpts) *v1.Pod {
pod := &v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Command: []string{"sleep", "infinitely"},
Name: "digger",
Image: "busybox",
},
},
},
}

if opts.Name == "" {
pod.ObjectMeta.Name = "digger-" + rand.String(5)
} else {
pod.ObjectMeta.Name = opts.Name
pod.Spec.Containers[0].Name = opts.Name
}

if opts.Image != "" {
pod.Spec.Containers[0].Image = opts.Image
}

if len(opts.Command) != 0 {
pod.Spec.Containers[0].Command = opts.Command
}

pod.Spec.HostPID = opts.HostPid

pod.Spec.HostNetwork = opts.HostNetwork

if opts.Privileged {
pod.Spec.Containers[0].SecurityContext = &v1.SecurityContext{
Privileged: &opts.Privileged,
}
}

if opts.HostPath {
pod.Spec.Volumes = append(pod.Spec.Volumes, v1.Volume{
Name: "host",
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: "/",
},
},
})
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, v1.VolumeMount{
Name: "host",
MountPath: "/hostfs",
})
}

if opts.Tolerations {
pod.Spec.Tolerations = append(pod.Spec.Tolerations, []v1.Toleration{
{
Key: "NoExecute",
Operator: "Exists",
},
{
Key: "NoSchedule",
Operator: "Exists",
},
{
Key: "CriticalAddonsOnly",
Operator: "Exists",
},
}...)
}

return pod
}
128 changes: 128 additions & 0 deletions pkg/kgen/kgen_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package kgen

import (
"reflect"
"testing"

v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/rand"
)

func TestGenerateManualName(t *testing.T) {
args := GenerateOpts{
Name: "testname",
}
if got := Generate(args); got.ObjectMeta.Name != args.Name {
t.Errorf("Generate() = %v, want %v", got.ObjectMeta.Name, args.Name)
}
}

func TestGenerateAutoName(t *testing.T) {
args := GenerateOpts{}
// generate pseudo random
rand.Seed(0)
name := "digger-cbhtc"
if got := Generate(args); got.ObjectMeta.Name != name {
t.Errorf("Generate() = %v, want %v", got.ObjectMeta.Name, name)
}
}

func TestGenerateManualImage(t *testing.T) {
args := GenerateOpts{
Image: "testimage",
}
if got := Generate(args); got.Spec.Containers[0].Image != args.Image {
t.Errorf("Generate() = %v, want %v", got.Spec.Containers[0].Image, args.Image)
}
}

func TestGenerateManualCommand(t *testing.T) {
args := GenerateOpts{
Command: []string{"test", "command"},
}
if got := Generate(args); len(got.Spec.Containers[0].Command) != 2 && got.Spec.Containers[0].Command[0] != "test" && got.Spec.Containers[0].Command[1] != "command" {
t.Errorf("Generate() = %v, want %v", got.Spec.Containers[0].Command, args.Command)
}
}

func TestGenerateHostPID(t *testing.T) {
args := GenerateOpts{
HostPid: true,
}
if got := Generate(args); !got.Spec.HostPID {
t.Errorf("Generate() = %v, want %v", got.Spec.HostPID, true)
}
args = GenerateOpts{
HostPid: false,
}
if got := Generate(args); got.Spec.HostPID {
t.Errorf("Generate() = %v, want %v", got.Spec.HostPID, false)
}
}

func TestGenerateHostNetwork(t *testing.T) {
args := GenerateOpts{
HostNetwork: true,
}
if got := Generate(args); !got.Spec.HostNetwork {
t.Errorf("Generate() = %v, want %v", got.Spec.HostNetwork, true)
}
args = GenerateOpts{
HostNetwork: false,
}
if got := Generate(args); got.Spec.HostNetwork {
t.Errorf("Generate() = %v, want %v", got.Spec.HostNetwork, false)
}
}

func TestGeneratePrivileged(t *testing.T) {
args := []GenerateOpts{
{
Privileged: true,
},
{
Privileged: false,
},
}
for _, arg := range args {
if got := Generate(arg); got.Spec.Containers[0].SecurityContext != nil && got.Spec.Containers[0].SecurityContext.Privileged == &arg.Privileged {
t.Errorf("Generate() = %v, want %v", got.Spec.Containers[0].SecurityContext.Privileged, &arg.Privileged)
}
}
}

func TestGenerateHostPath(t *testing.T) {
args := GenerateOpts{
HostPath: true,
}
if got := Generate(args); len(got.Spec.Containers[0].VolumeMounts) != 1 &&
got.Spec.Containers[0].VolumeMounts[0].Name == "host" &&
got.Spec.Containers[0].VolumeMounts[0].MountPath == "hostfs" &&
got.Spec.Volumes[0].Name == "host" &&
got.Spec.Volumes[0].HostPath.Path == "/" {
t.Errorf("Generate() = %v and %v", got.Spec.Containers[0].VolumeMounts[0], got.Spec.Volumes[0])
}
}

func TestGenerateTolerations(t *testing.T) {
args := GenerateOpts{
Tolerations: true,
}
want := []v1.Toleration{
{
Key: "NoExecute",
Operator: "Exists",
},
{
Key: "NoSchedule",
Operator: "Exists",
},
{
Key: "CriticalAddonsOnly",
Operator: "Exists",
},
}
if got := Generate(args); reflect.DeepEqual(got, want) {
t.Errorf("Generate() = %v, want %v", got, want)
}
}

0 comments on commit 3359013

Please sign in to comment.