Skip to content

Commit

Permalink
Add a NodeSnifferService to perform tcpdump in default netnamespace
Browse files Browse the repository at this point in the history
Add NodeSnifferService to collect tcpdump on a Node
Add PrivilegedPodConfig to define values for CreatePrivilegedPod()
  • Loading branch information
MichaelWasher committed Sep 22, 2021
1 parent 083d346 commit b9d83f4
Show file tree
Hide file tree
Showing 6 changed files with 373 additions and 53 deletions.
99 changes: 61 additions & 38 deletions kube/kubernetes_api_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,24 @@ import (
"k8s.io/client-go/rest"
)

type PrivilegedPodConfig struct {
NodeName string
ContainerName string
Image string
SocketPath string
Timeout time.Duration
}

func NewCreatePodConfig() *PrivilegedPodConfig {
return &PrivilegedPodConfig{Timeout: 10 * time.Minute}
}

type KubernetesApiService interface {
ExecuteCommand(podName string, containerName string, command []string, stdOut io.Writer) (int, error)

DeletePod(podName string) error

CreatePrivilegedPod(nodeName string, containerName string, image string, socketPath string, timeout time.Duration) (*corev1.Pod, error)
CreatePrivilegedPod(config *PrivilegedPodConfig) (*corev1.Pod, error)

UploadFile(localPath string, remotePath string, podName string, containerName string) error
}
Expand Down Expand Up @@ -104,16 +116,19 @@ func (k *KubernetesApiServiceImpl) DeletePod(podName string) error {
return err
}

func (k *KubernetesApiServiceImpl) CreatePrivilegedPod(nodeName string, containerName string, image string, socketPath string, timeout time.Duration) (*corev1.Pod, error) {
log.Debugf("creating privileged pod on remote node")
func (k *KubernetesApiServiceImpl) CreatePrivilegedPod(config *PrivilegedPodConfig) (*corev1.Pod, error) {
log.Info("creating privileged pod on remote node")
log.Debugf("creating privileged pod with the following options: { %v }", config)

isSupported, err := k.IsSupportedContainerRuntime(nodeName)
hostNetwork := true

isSupported, err := k.IsSupportedContainerRuntime(config.NodeName)
if err != nil {
return nil, err
}

if !isSupported {
return nil, errors.Errorf("Container runtime on node %s isn't supported. Supported container runtimes are: %v", nodeName, runtime.SupportedContainerRuntimes)
return nil, errors.Errorf("Container runtime on node %s isn't supported. Supported container runtimes are: %v", config.NodeName, runtime.SupportedContainerRuntimes)
}

typeMetadata := v1.TypeMeta{
Expand All @@ -129,23 +144,52 @@ func (k *KubernetesApiServiceImpl) CreatePrivilegedPod(nodeName string, containe
},
}

// Add Storage / Mounts
hostPathType := corev1.HostPathSocket
directoryType := corev1.HostPathDirectory

volumeMounts := []corev1.VolumeMount{
{
Name: "container-socket",
ReadOnly: true,
MountPath: socketPath,
},
{
Name: "host",
ReadOnly: false,
MountPath: "/host",
},
}

volumes := []corev1.Volume{
{
Name: "host",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: "/",
Type: &directoryType,
},
},
},
}

if config.SocketPath != "" {
volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: "container-socket",
ReadOnly: true,
MountPath: config.SocketPath,
})
volumes = append(volumes, corev1.Volume{
Name: "container-socket",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: config.SocketPath,
Type: &hostPathType,
},
},
})
}

// Create Privileged container
privileged := true
privilegedContainer := corev1.Container{
Name: containerName,
Image: image,
Name: config.ContainerName,
Image: config.Image,

SecurityContext: &corev1.SecurityContext{
Privileged: &privileged,
Expand All @@ -155,34 +199,13 @@ func (k *KubernetesApiServiceImpl) CreatePrivilegedPod(nodeName string, containe
VolumeMounts: volumeMounts,
}

hostPathType := corev1.HostPathSocket
directoryType := corev1.HostPathDirectory

podSpecs := corev1.PodSpec{
NodeName: nodeName,
NodeName: config.NodeName,
RestartPolicy: corev1.RestartPolicyNever,
HostPID: true,
Containers: []corev1.Container{privilegedContainer},
Volumes: []corev1.Volume{
{
Name: "host",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: "/",
Type: &directoryType,
},
},
},
{
Name: "container-socket",
VolumeSource: corev1.VolumeSource{
HostPath: &corev1.HostPathVolumeSource{
Path: socketPath,
Type: &hostPathType,
},
},
},
},
Volumes: volumes,
HostNetwork: hostNetwork,
}

pod := corev1.Pod{
Expand Down Expand Up @@ -214,8 +237,8 @@ func (k *KubernetesApiServiceImpl) CreatePrivilegedPod(nodeName string, containe

log.Info("waiting for pod successful startup")

if !utils.RunWhileFalse(verifyPodState, timeout, 1*time.Second) {
return nil, errors.Errorf("failed to create pod within timeout (%s)", timeout)
if !utils.RunWhileFalse(verifyPodState, config.Timeout, 1*time.Second) {
return nil, errors.Errorf("failed to create pod within timeout (%s)", config.Timeout)
}

return createdPod, nil
Expand Down
17 changes: 9 additions & 8 deletions pkg/cmd/sniff.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,15 @@ import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"

_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
)

var (
Expand Down Expand Up @@ -172,7 +171,7 @@ func (o *Ksniff) Complete(cmd *cobra.Command, args []string) error {
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

if o.settings.UserSpecifiedVerboseMode {
Expand Down Expand Up @@ -267,7 +266,7 @@ func (o *Ksniff) Validate() error {
log.Infof("using tcpdump path at: '%s'", o.settings.UserSpecifiedLocalTcpdumpPath)
}

pod, err := o.clientset.CoreV1().Pods(o.resultingContext.Namespace).Get(context.TODO(), o.settings.UserSpecifiedPodName, v1.GetOptions{})
pod, err := o.clientset.CoreV1().Pods(o.resultingContext.Namespace).Get(context.TODO(), o.settings.UserSpecifiedPodName, metav1.GetOptions{})
if err != nil {
return err
}
Expand Down Expand Up @@ -312,7 +311,9 @@ func (o *Ksniff) getPodSnifferService(pod *corev1.Pod) (sniffer.SnifferService,
if o.settings.UserSpecifiedPrivilegedMode {
log.Info("sniffing method: privileged pod")
snifferVar, err = sniffer.NewPrivilegedPodRemoteSniffingService(o.settings, pod, kubernetesApiService)

} else if o.settings.UserSpecifiedNodeMode {
log.Info("sniffing method: node")
snifferVar = sniffer.NewNodeSnifferService(o.settings, kubernetesApiService)
} else {
log.Info("sniffing method: upload static tcpdump")
snifferVar = sniffer.NewUploadTcpdumpRemoteSniffingService(o.settings, kubernetesApiService)
Expand Down
10 changes: 10 additions & 0 deletions pkg/config/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ type KsniffSettings struct {
UserSpecifiedRemoteTcpdumpPath string
UserSpecifiedVerboseMode bool
UserSpecifiedPrivilegedMode bool
UserSpecifiedNodeMode bool
UserSpecifiedNodeName string
UserSpecifiedImage string
DetectedPodNodeName string
DetectedContainerId string
Expand Down Expand Up @@ -52,6 +54,14 @@ type StaticTCPSnifferServiceConfig struct {
UserSpecifiedFilter string
}

type NodeSnifferServiceConfig struct {
Image string
UserSpecifiedInterface string
UserSpecifiedFilter string
NodeName string
UserSpecifiedPodCreateTimeout time.Duration
}

func NewKsniffSettings(streams genericclioptions.IOStreams) *KsniffSettings {
return &KsniffSettings{}
}
104 changes: 104 additions & 0 deletions pkg/service/sniffer/node_sniffer_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package sniffer

import (
"io"

"ksniff/kube"
"ksniff/pkg/config"

log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
)

var defaultInterface = "any"

type NodeSnifferService struct {
*config.NodeSnifferServiceConfig
privilegedPod *v1.Pod
privilegedContainerName string
targetInterface string
// TODO Replace Node Name
nodeName string
kubernetesApiService kube.KubernetesApiService
}

func NewNodeSnifferService(options *config.KsniffSettings, service kube.KubernetesApiService) SnifferService {
nodeSnifferService := &NodeSnifferService{
NodeSnifferServiceConfig: &config.NodeSnifferServiceConfig{
Image: options.UserSpecifiedImage,
UserSpecifiedInterface: options.UserSpecifiedInterface,
UserSpecifiedFilter: options.UserSpecifiedFilter,
NodeName: options.UserSpecifiedNodeName,
UserSpecifiedPodCreateTimeout: options.UserSpecifiedPodCreateTimeout,
},
privilegedContainerName: "node-sniff",
kubernetesApiService: service,
nodeName: options.DetectedPodNodeName,
targetInterface: defaultInterface,
}

if options.UseDefaultImage {
nodeSnifferService.Image = "maintained/tcpdump"
}

return nodeSnifferService
}

func (nss *NodeSnifferService) Setup() error {
var err error
// TODO Create a Nodesniffer Object
log.Infof("creating privileged pod on node: '%s'", nss.nodeName)
log.Debugf("initiating sniff on node with option: '%v'", nss)

podConfig := kube.PrivilegedPodConfig{
// TODO Replace DetectedPodNodeName with PodName
NodeName: nss.nodeName,
ContainerName: nss.privilegedContainerName,
Image: nss.Image,
Timeout: nss.UserSpecifiedPodCreateTimeout,
}

nss.privilegedPod, err = nss.kubernetesApiService.CreatePrivilegedPod(&podConfig)
if err != nil {
log.WithError(err).Errorf("failed to create privileged pod on node: '%s'", nss.nodeName)
return err
}

log.Infof("pod: '%s' created successfully on node: '%s'", nss.privilegedPod.Name, nss.nodeName)

return nil
}

func (nss *NodeSnifferService) Cleanup() error {
log.Infof("removing pod: '%s'", nss.privilegedPod.Name)

err := nss.kubernetesApiService.DeletePod(nss.privilegedPod.Name)
if err != nil {
log.WithError(err).Errorf("failed to remove pod: '%s", nss.privilegedPod.Name)
return err
}

log.Infof("pod: '%s' removed successfully", nss.privilegedPod.Name)

return nil
}

func buildTcpdumpCommand(netInterface string, filter string, tcpdumpImage string) []string {
return []string{"tcpdump", "-i", netInterface, "-U", "-w", "-", filter}
}

func (nss *NodeSnifferService) Start(stdOut io.Writer) error {
log.Info("starting remote sniffing using privileged pod")

command := buildTcpdumpCommand(nss.targetInterface, nss.UserSpecifiedFilter, nss.Image)

exitCode, err := nss.kubernetesApiService.ExecuteCommand(nss.privilegedPod.Name, nss.privilegedContainerName, command, stdOut)
if err != nil {
log.WithError(err).Errorf("failed to start sniffing using privileged pod, exit code: '%d'", exitCode)
return err
}

log.Info("remote sniffing using privileged pod completed")

return nil
}
18 changes: 11 additions & 7 deletions pkg/service/sniffer/privileged_pod_sniffer_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,17 @@ func (p *PrivilegedPodSnifferService) Setup() error {
var err error

log.Infof("creating privileged pod on node: '%s'", p.DetectedPodNodeName)
p.privilegedPod, err = p.kubernetesApiService.CreatePrivilegedPod(
p.DetectedPodNodeName,
p.privilegedContainerName,
p.Image,
p.SocketPath,
p.UserSpecifiedPodCreateTimeout,
)

podConfig := kube.PrivilegedPodConfig{
NodeName: p.DetectedPodNodeName,
ContainerName: p.privilegedContainerName,
Image: p.Image,
SocketPath: p.SocketPath,
Timeout: p.UserSpecifiedPodCreateTimeout,
}

p.privilegedPod, err = p.kubernetesApiService.CreatePrivilegedPod(&podConfig)

if err != nil {
log.WithError(err).Errorf("failed to create privileged pod on node: '%s'", p.DetectedPodNodeName)
return err
Expand Down
Loading

0 comments on commit b9d83f4

Please sign in to comment.