Skip to content

Commit 841e6ca

Browse files
committed
Add a NodeSnifferService to perform tcpdump in default netnamespace
Add NodeSnifferService to collect tcpdump on a Node Add PrivilegedPodConfig to define values for CreatePrivilegedPod()
1 parent 083d346 commit 841e6ca

File tree

6 files changed

+373
-53
lines changed

6 files changed

+373
-53
lines changed

kube/kubernetes_api_service.go

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,24 @@ import (
1818
"k8s.io/client-go/rest"
1919
)
2020

21+
type PrivilegedPodConfig struct {
22+
NodeName string
23+
ContainerName string
24+
Image string
25+
SocketPath string
26+
Timeout time.Duration
27+
}
28+
29+
func NewCreatePodConfig() *PrivilegedPodConfig {
30+
return &PrivilegedPodConfig{Timeout: 10 * time.Minute}
31+
}
32+
2133
type KubernetesApiService interface {
2234
ExecuteCommand(podName string, containerName string, command []string, stdOut io.Writer) (int, error)
2335

2436
DeletePod(podName string) error
2537

26-
CreatePrivilegedPod(nodeName string, containerName string, image string, socketPath string, timeout time.Duration) (*corev1.Pod, error)
38+
CreatePrivilegedPod(config *PrivilegedPodConfig) (*corev1.Pod, error)
2739

2840
UploadFile(localPath string, remotePath string, podName string, containerName string) error
2941
}
@@ -104,16 +116,19 @@ func (k *KubernetesApiServiceImpl) DeletePod(podName string) error {
104116
return err
105117
}
106118

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

110-
isSupported, err := k.IsSupportedContainerRuntime(nodeName)
123+
hostNetwork := true
124+
125+
isSupported, err := k.IsSupportedContainerRuntime(config.NodeName)
111126
if err != nil {
112127
return nil, err
113128
}
114129

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

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

147+
// Add Storage / Mounts
148+
hostPathType := corev1.HostPathSocket
149+
directoryType := corev1.HostPathDirectory
150+
132151
volumeMounts := []corev1.VolumeMount{
133-
{
134-
Name: "container-socket",
135-
ReadOnly: true,
136-
MountPath: socketPath,
137-
},
138152
{
139153
Name: "host",
140154
ReadOnly: false,
141155
MountPath: "/host",
142156
},
143157
}
144158

159+
volumes := []corev1.Volume{
160+
{
161+
Name: "host",
162+
VolumeSource: corev1.VolumeSource{
163+
HostPath: &corev1.HostPathVolumeSource{
164+
Path: "/",
165+
Type: &directoryType,
166+
},
167+
},
168+
},
169+
}
170+
171+
if config.SocketPath != "" {
172+
volumeMounts = append(volumeMounts, corev1.VolumeMount{
173+
Name: "container-socket",
174+
ReadOnly: true,
175+
MountPath: config.SocketPath,
176+
})
177+
volumes = append(volumes, corev1.Volume{
178+
Name: "container-socket",
179+
VolumeSource: corev1.VolumeSource{
180+
HostPath: &corev1.HostPathVolumeSource{
181+
Path: config.SocketPath,
182+
Type: &hostPathType,
183+
},
184+
},
185+
})
186+
}
187+
188+
// Create Privileged container
145189
privileged := true
146190
privilegedContainer := corev1.Container{
147-
Name: containerName,
148-
Image: image,
191+
Name: config.ContainerName,
192+
Image: config.Image,
149193

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

158-
hostPathType := corev1.HostPathSocket
159-
directoryType := corev1.HostPathDirectory
160-
161202
podSpecs := corev1.PodSpec{
162-
NodeName: nodeName,
203+
NodeName: config.NodeName,
163204
RestartPolicy: corev1.RestartPolicyNever,
164205
HostPID: true,
165206
Containers: []corev1.Container{privilegedContainer},
166-
Volumes: []corev1.Volume{
167-
{
168-
Name: "host",
169-
VolumeSource: corev1.VolumeSource{
170-
HostPath: &corev1.HostPathVolumeSource{
171-
Path: "/",
172-
Type: &directoryType,
173-
},
174-
},
175-
},
176-
{
177-
Name: "container-socket",
178-
VolumeSource: corev1.VolumeSource{
179-
HostPath: &corev1.HostPathVolumeSource{
180-
Path: socketPath,
181-
Type: &hostPathType,
182-
},
183-
},
184-
},
185-
},
207+
Volumes: volumes,
208+
HostNetwork: hostNetwork,
186209
}
187210

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

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

217-
if !utils.RunWhileFalse(verifyPodState, timeout, 1*time.Second) {
218-
return nil, errors.Errorf("failed to create pod within timeout (%s)", timeout)
240+
if !utils.RunWhileFalse(verifyPodState, config.Timeout, 1*time.Second) {
241+
return nil, errors.Errorf("failed to create pod within timeout (%s)", config.Timeout)
219242
}
220243

221244
return createdPod, nil

pkg/cmd/sniff.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,15 @@ import (
1818
"github.com/spf13/cobra"
1919
"github.com/spf13/viper"
2020
corev1 "k8s.io/api/core/v1"
21-
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2222
"k8s.io/cli-runtime/pkg/genericclioptions"
2323
"k8s.io/client-go/kubernetes"
24-
"k8s.io/client-go/rest"
25-
"k8s.io/client-go/tools/clientcmd"
26-
"k8s.io/client-go/tools/clientcmd/api"
27-
2824
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
2925
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
3026
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
27+
"k8s.io/client-go/rest"
28+
"k8s.io/client-go/tools/clientcmd"
29+
"k8s.io/client-go/tools/clientcmd/api"
3130
)
3231

3332
var (
@@ -172,7 +171,7 @@ func (o *Ksniff) Complete(cmd *cobra.Command, args []string) error {
172171
o.settings.UseDefaultImage = !cmd.Flag("image").Changed
173172
o.settings.UseDefaultTCPDumpImage = !cmd.Flag("tcpdump-image").Changed
174173
o.settings.UseDefaultSocketPath = !cmd.Flag("socket").Changed
175-
174+
176175
var err error
177176

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

270-
pod, err := o.clientset.CoreV1().Pods(o.resultingContext.Namespace).Get(context.TODO(), o.settings.UserSpecifiedPodName, v1.GetOptions{})
269+
pod, err := o.clientset.CoreV1().Pods(o.resultingContext.Namespace).Get(context.TODO(), o.settings.UserSpecifiedPodName, metav1.GetOptions{})
271270
if err != nil {
272271
return err
273272
}
@@ -312,7 +311,9 @@ func (o *Ksniff) getPodSnifferService(pod *corev1.Pod) (sniffer.SnifferService,
312311
if o.settings.UserSpecifiedPrivilegedMode {
313312
log.Info("sniffing method: privileged pod")
314313
snifferVar, err = sniffer.NewPrivilegedPodRemoteSniffingService(o.settings, pod, kubernetesApiService)
315-
314+
} else if o.settings.UserSpecifiedNodeMode {
315+
log.Info("sniffing method: node")
316+
snifferVar = sniffer.NewNodeSnifferService(o.settings, kubernetesApiService)
316317
} else {
317318
log.Info("sniffing method: upload static tcpdump")
318319
snifferVar = sniffer.NewUploadTcpdumpRemoteSniffingService(o.settings, kubernetesApiService)

pkg/config/settings.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ type KsniffSettings struct {
1818
UserSpecifiedRemoteTcpdumpPath string
1919
UserSpecifiedVerboseMode bool
2020
UserSpecifiedPrivilegedMode bool
21+
UserSpecifiedNodeMode bool
22+
UserSpecifiedNodeName string
2123
UserSpecifiedImage string
2224
DetectedPodNodeName string
2325
DetectedContainerId string
@@ -52,6 +54,14 @@ type StaticTCPSnifferServiceConfig struct {
5254
UserSpecifiedFilter string
5355
}
5456

57+
type NodeSnifferServiceConfig struct {
58+
Image string
59+
UserSpecifiedInterface string
60+
UserSpecifiedFilter string
61+
NodeName string
62+
UserSpecifiedPodCreateTimeout time.Duration
63+
}
64+
5565
func NewKsniffSettings(streams genericclioptions.IOStreams) *KsniffSettings {
5666
return &KsniffSettings{}
5767
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package sniffer
2+
3+
import (
4+
"io"
5+
6+
"ksniff/kube"
7+
"ksniff/pkg/config"
8+
9+
log "github.com/sirupsen/logrus"
10+
v1 "k8s.io/api/core/v1"
11+
)
12+
13+
var defaultInterface = "any"
14+
15+
type NodeSnifferService struct {
16+
*config.NodeSnifferServiceConfig
17+
privilegedPod *v1.Pod
18+
privilegedContainerName string
19+
targetInterface string
20+
// TODO Replace Node Name
21+
nodeName string
22+
kubernetesApiService kube.KubernetesApiService
23+
}
24+
25+
func NewNodeSnifferService(options *config.KsniffSettings, service kube.KubernetesApiService) SnifferService {
26+
nodeSnifferService := &NodeSnifferService{
27+
NodeSnifferServiceConfig: &config.NodeSnifferServiceConfig{
28+
Image: options.UserSpecifiedImage,
29+
UserSpecifiedInterface: options.UserSpecifiedInterface,
30+
UserSpecifiedFilter: options.UserSpecifiedFilter,
31+
NodeName: options.UserSpecifiedNodeName,
32+
UserSpecifiedPodCreateTimeout: options.UserSpecifiedPodCreateTimeout,
33+
},
34+
privilegedContainerName: "node-sniff",
35+
kubernetesApiService: service,
36+
nodeName: options.DetectedPodNodeName,
37+
targetInterface: defaultInterface,
38+
}
39+
40+
if options.UseDefaultImage {
41+
nodeSnifferService.Image = "maintained/tcpdump"
42+
}
43+
44+
return nodeSnifferService
45+
}
46+
47+
func (nss *NodeSnifferService) Setup() error {
48+
var err error
49+
// TODO Create a Nodesniffer Object
50+
log.Infof("creating privileged pod on node: '%s'", nss.nodeName)
51+
log.Debugf("initiating sniff on node with option: '%v'", nss)
52+
53+
podConfig := kube.PrivilegedPodConfig{
54+
// TODO Replace DetectedPodNodeName with PodName
55+
NodeName: nss.nodeName,
56+
ContainerName: nss.privilegedContainerName,
57+
Image: nss.Image,
58+
Timeout: nss.UserSpecifiedPodCreateTimeout,
59+
}
60+
61+
nss.privilegedPod, err = nss.kubernetesApiService.CreatePrivilegedPod(&podConfig)
62+
if err != nil {
63+
log.WithError(err).Errorf("failed to create privileged pod on node: '%s'", nss.nodeName)
64+
return err
65+
}
66+
67+
log.Infof("pod: '%s' created successfully on node: '%s'", nss.privilegedPod.Name, nss.nodeName)
68+
69+
return nil
70+
}
71+
72+
func (nss *NodeSnifferService) Cleanup() error {
73+
log.Infof("removing pod: '%s'", nss.privilegedPod.Name)
74+
75+
err := nss.kubernetesApiService.DeletePod(nss.privilegedPod.Name)
76+
if err != nil {
77+
log.WithError(err).Errorf("failed to remove pod: '%s", nss.privilegedPod.Name)
78+
return err
79+
}
80+
81+
log.Infof("pod: '%s' removed successfully", nss.privilegedPod.Name)
82+
83+
return nil
84+
}
85+
86+
func buildTcpdumpCommand(netInterface string, filter string, tcpdumpImage string) []string {
87+
return []string{"tcpdump", "-i", netInterface, "-U", "-w", "-", filter}
88+
}
89+
90+
func (nss *NodeSnifferService) Start(stdOut io.Writer) error {
91+
log.Info("starting remote sniffing using privileged pod")
92+
93+
command := buildTcpdumpCommand(nss.targetInterface, nss.UserSpecifiedFilter, nss.Image)
94+
95+
exitCode, err := nss.kubernetesApiService.ExecuteCommand(nss.privilegedPod.Name, nss.privilegedContainerName, command, stdOut)
96+
if err != nil {
97+
log.WithError(err).Errorf("failed to start sniffing using privileged pod, exit code: '%d'", exitCode)
98+
return err
99+
}
100+
101+
log.Info("remote sniffing using privileged pod completed")
102+
103+
return nil
104+
}

pkg/service/sniffer/privileged_pod_sniffer_service.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,17 @@ func (p *PrivilegedPodSnifferService) Setup() error {
7272
var err error
7373

7474
log.Infof("creating privileged pod on node: '%s'", p.DetectedPodNodeName)
75-
p.privilegedPod, err = p.kubernetesApiService.CreatePrivilegedPod(
76-
p.DetectedPodNodeName,
77-
p.privilegedContainerName,
78-
p.Image,
79-
p.SocketPath,
80-
p.UserSpecifiedPodCreateTimeout,
81-
)
75+
76+
podConfig := kube.PrivilegedPodConfig{
77+
NodeName: p.DetectedPodNodeName,
78+
ContainerName: p.privilegedContainerName,
79+
Image: p.Image,
80+
SocketPath: p.SocketPath,
81+
Timeout: p.UserSpecifiedPodCreateTimeout,
82+
}
83+
84+
p.privilegedPod, err = p.kubernetesApiService.CreatePrivilegedPod(&podConfig)
85+
8286
if err != nil {
8387
log.WithError(err).Errorf("failed to create privileged pod on node: '%s'", p.DetectedPodNodeName)
8488
return err

0 commit comments

Comments
 (0)