Skip to content

Commit

Permalink
Allow air gapped envs to use ksniff (#113)
Browse files Browse the repository at this point in the history
* add tcpdump image selection for air gapped envs

Co-authored-by: Kostas Dichalas <[email protected]>
  • Loading branch information
kdihalas and Kostas Dichalas authored Jun 25, 2021
1 parent 13d278f commit b882817
Show file tree
Hide file tree
Showing 9 changed files with 42 additions and 9 deletions.
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ To compile a static tcpdump binary:
LOCAL_TCPDUMP_FILE: Optional. if specified, ksniff will use this path as the local path of the static tcpdump binary.
REMOTE_TCPDUMP_FILE: Optional. if specified, ksniff will use the specified path as the remote path to upload static tcpdump to.

#### Air gapped environments
Use `--image` and `--tcpdump-image` flags to override the default container images and use your own e.g (docker):

kubectl plugin sniff <POD_NAME> [-n <NAMESPACE_NAME>] [-c <CONTAINER_NAME>] --image <PRIVATE_REPO>/docker --tcpdump-image <PRIVATE_REPO>/tcpdump


#### Non-Privileged and Scratch Pods
To reduce attack surface and have small and lean containers, many production-ready containers runs as non-privileged user
or even as a scratch container.
Expand Down
6 changes: 6 additions & 0 deletions pkg/cmd/sniff.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ func NewCmdSniff(streams genericclioptions.IOStreams) *cobra.Command {
_ = viper.BindEnv("image", "KUBECTL_PLUGINS_LOCAL_FLAG_IMAGE")
_ = viper.BindPFlag("image", cmd.Flags().Lookup("image"))

cmd.Flags().StringVarP(&ksniffSettings.TCPDumpImage, "tcpdump-image", "", "",
"the tcpdump container image (optional)")
_ = viper.BindEnv("tcpdump-image", "KUBECTL_PLUGINS_LOCAL_FLAG_TCPDUMP_IMAGE")
_ = viper.BindPFlag("tcpdump-image", cmd.Flags().Lookup("tcpdump-image"))

cmd.Flags().StringVarP(&ksniffSettings.UserSpecifiedKubeContext, "context", "x", "",
"kubectl context to work on (optional)")
_ = viper.BindEnv("context", "KUBECTL_PLUGINS_CURRENT_CONTEXT")
Expand Down Expand Up @@ -167,6 +172,7 @@ func (o *Ksniff) Complete(cmd *cobra.Command, args []string) error {
o.settings.UserSpecifiedPrivilegedMode = viper.GetBool("privileged")
o.settings.UserSpecifiedKubeContext = viper.GetString("context")
o.settings.UseDefaultImage = !cmd.Flag("image").Changed
o.settings.UseDefaultTCPDumpImage = !cmd.Flag("tcpdump-image").Changed
o.settings.UseDefaultSocketPath = !cmd.Flag("socket").Changed

var err error
Expand Down
2 changes: 2 additions & 0 deletions pkg/config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ type KsniffSettings struct {
DetectedContainerId string
DetectedContainerRuntime string
Image string
TCPDumpImage string
UseDefaultImage bool
UseDefaultTCPDumpImage bool
UserSpecifiedKubeContext string
SocketPath string
UseDefaultSocketPath bool
Expand Down
5 changes: 5 additions & 0 deletions pkg/service/sniffer/privileged_pod_sniffer_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ func (p *PrivilegedPodSnifferService) Setup() error {
p.settings.Image = p.runtimeBridge.GetDefaultImage()
}

if p.settings.UseDefaultTCPDumpImage {
p.settings.TCPDumpImage = p.runtimeBridge.GetDefaultTCPImage()
}

if p.settings.UseDefaultSocketPath {
p.settings.SocketPath = p.runtimeBridge.GetDefaultSocketPath()
}
Expand Down Expand Up @@ -102,6 +106,7 @@ func (p *PrivilegedPodSnifferService) Start(stdOut io.Writer) error {
p.settings.UserSpecifiedFilter,
p.targetProcessId,
p.settings.SocketPath,
p.settings.TCPDumpImage,
)

exitCode, err := p.kubernetesApiService.ExecuteCommand(p.privilegedPod.Name, p.privilegedContainerName, command, stdOut)
Expand Down
9 changes: 6 additions & 3 deletions pkg/service/sniffer/runtime/containerd.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ func (d ContainerdBridge) GetDefaultSocketPath() string {
return "/run/containerd/containerd.sock"
}

func (d *ContainerdBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string) []string {
func (d *ContainerdBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string {
d.tcpdumpContainerName = "ksniff-container-" + utils.GenerateRandomString(8)
d.socketPath = socketPath
imageName := "docker.io/maintained/tcpdump:latest"
tcpdumpCommand := fmt.Sprintf("tcpdump -i %s -U -w - %s", netInterface, filter)
shellScript := fmt.Sprintf(`
set -ex
Expand All @@ -44,7 +43,7 @@ func (d *ContainerdBridge) BuildTcpdumpCommand(containerId *string, netInterface
crictl pull %s >/dev/null
netns=$(crictl inspect %s | jq '.info.runtimeSpec.linux.namespaces[] | select(.type == "network") | .path' | tr -d '"')
exec chroot /host ctr -a ${CONTAINERD_SOCKET} run --rm --with-ns "network:${netns}" %s %s %s
`, d.socketPath, imageName, *containerId, imageName, d.tcpdumpContainerName, tcpdumpCommand)
`, d.socketPath, tcpdumpImage, *containerId, tcpdumpImage, d.tcpdumpContainerName, tcpdumpCommand)
command := []string{"/bin/sh", "-c", shellScript}
return command
}
Expand All @@ -64,3 +63,7 @@ func (d *ContainerdBridge) BuildCleanupCommand() []string {
func (d ContainerdBridge) GetDefaultImage() string {
return "docker.io/hamravesh/ksniff-helper:v3"
}

func (d *ContainerdBridge) GetDefaultTCPImage() string {
return "docker.io/maintained/tcpdump:latest"
}
6 changes: 5 additions & 1 deletion pkg/service/sniffer/runtime/crio.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (c *CrioBridge) ExtractPid(inspection string) (*string, error) {
return &ret, nil
}

func (c *CrioBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string) []string {
func (c *CrioBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string {
return []string{"nsenter", "-n", "-t", *pid, "--", "tcpdump", "-i", netInterface, "-U", "-w", "-", filter}
}

Expand Down Expand Up @@ -87,3 +87,7 @@ func extractPidCrio118(partial map[string]json.RawMessage) (float64, error) {
}
return result["pid"].(float64), nil
}

func (d *CrioBridge) GetDefaultTCPImage() string {
return ""
}
8 changes: 6 additions & 2 deletions pkg/service/sniffer/runtime/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ func (d *DockerBridge) ExtractPid(inspection string) (*string, error) {
panic("Docker doesn't need this implemented")
}

func (d *DockerBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string) []string {
func (d *DockerBridge) BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string {
d.tcpdumpContainerName = "ksniff-container-" + utils.GenerateRandomString(8)
containerNameFlag := fmt.Sprintf("--name=%s", d.tcpdumpContainerName)

command := []string{"docker", "--host", "unix://" + socketPath,
"run", "--rm", containerNameFlag,
fmt.Sprintf("--net=container:%s", *containerId), "maintained/tcpdump", "-i",
fmt.Sprintf("--net=container:%s", *containerId), tcpdumpImage, "-i",
netInterface, "-U", "-w", "-", filter}

d.cleanupCommand = []string{"docker", "--host", "unix://" + socketPath,
Expand All @@ -50,6 +50,10 @@ func (d *DockerBridge) GetDefaultImage() string {
return "docker"
}

func (d *DockerBridge) GetDefaultTCPImage() string {
return "maintained/tcpdump"
}

func (d *DockerBridge) GetDefaultSocketPath() string {
return "/var/run/docker.sock"
}
6 changes: 4 additions & 2 deletions pkg/service/sniffer/runtime/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ func TestPrivilegedPodName(t *testing.T) {
var filter = "filter"
var pid = "pid"
var path = "/path"
bridge.BuildTcpdumpCommand(&containerId, netInterface, filter, &pid, path)
var tcpdumpImage = bridge.GetDefaultTCPImage()
bridge.BuildTcpdumpCommand(&containerId, netInterface, filter, &pid, path, tcpdumpImage)
assert.NotEqual(t, "", bridge.tcpdumpContainerName, "tcpdumpContainerName should have been set")
}

Expand All @@ -34,7 +35,8 @@ func TestCleanupCommand(t *testing.T) {
var filter = "filter"
var pid = "pid"
var socketPath = "/path"
bridge.BuildTcpdumpCommand(&containerId, netInterface, filter, &pid, socketPath)
var tcpdumpImage = bridge.GetDefaultTCPImage()
bridge.BuildTcpdumpCommand(&containerId, netInterface, filter, &pid, socketPath, tcpdumpImage)
assert.Equal(t,
[]string{"docker", "--host", "unix://" + socketPath, "rm", "-f", bridge.tcpdumpContainerName},
bridge.BuildCleanupCommand(),
Expand Down
3 changes: 2 additions & 1 deletion pkg/service/sniffer/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ type ContainerRuntimeBridge interface {
NeedsPid() bool
BuildInspectCommand(containerId string) []string
ExtractPid(inspection string) (*string, error)
BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string) []string
BuildTcpdumpCommand(containerId *string, netInterface string, filter string, pid *string, socketPath string, tcpdumpImage string) []string
BuildCleanupCommand() []string
GetDefaultImage() string
GetDefaultTCPImage() string
GetDefaultSocketPath() string
}

Expand Down

0 comments on commit b882817

Please sign in to comment.