Skip to content

Commit

Permalink
Add first version of IMDS tester
Browse files Browse the repository at this point in the history
  • Loading branch information
christophetd committed Apr 5, 2023
1 parent b6337e3 commit 353efa1
Show file tree
Hide file tree
Showing 5 changed files with 160 additions and 8 deletions.
4 changes: 3 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@

* IMDS
* ALB exposed
* VALIDATE
* VALIDATE CREDS
* LOOK FOR NOT ONLY AWS CREDS
* * INITI CONTAINERS AND EPHEMERAL CONTAINERS
7 changes: 0 additions & 7 deletions cmd/managed-kubernetes-auditing-toolkit/eks/find_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,6 @@ import (
"log"
)

var availableSearchLocations = []string{
"configmaps",
"secrets",
"pod-definitions",
"deployments",
}

func buildEksFindSecretsCommand() *cobra.Command {
eksFindSecretsCommand := &cobra.Command{
Use: "find-secrets",
Expand Down
34 changes: 34 additions & 0 deletions cmd/managed-kubernetes-auditing-toolkit/eks/imds.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package eks

import (
"github.com/datadog/managed-k8s-auditing-toolkit/internal/utils"
"github.com/datadog/managed-k8s-auditing-toolkit/pkg/managed-kubernetes-auditing-toolkit/eks/imds"
"github.com/spf13/cobra"
)

func buildTestImdsAccessCommand() *cobra.Command {
eksFindSecretsCommand := &cobra.Command{
Use: "test-imds-access",
Example: "mkat eks test-imds-access",
DisableFlagsInUseLine: true,
RunE: func(cmd *cobra.Command, args []string) error {
return doTestImdsAccessCommand()
},
}

return eksFindSecretsCommand
}

func doTestImdsAccessCommand() error {
tester := imds.ImdsTester{K8sClient: utils.K8sClient(), Namespace: "default"}
result, err := tester.TestImdsAccessible()
if err != nil {
return err
}
if result.IsImdsAccessible {
println("IMDS is accessible and allows any pod to retrieve credentials for the AWS role " + result.NodeRoleName)
} else {
println("IMDS is not accessible in your cluster")
}
return nil
}
1 change: 1 addition & 0 deletions cmd/managed-kubernetes-auditing-toolkit/eks/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ func BuildEksSubcommand() *cobra.Command {

eksCommand.AddCommand(buildEksRoleRelationshipsCommand())
eksCommand.AddCommand(buildEksFindSecretsCommand())
eksCommand.AddCommand(buildTestImdsAccessCommand())

return eksCommand
}
122 changes: 122 additions & 0 deletions pkg/managed-kubernetes-auditing-toolkit/eks/imds/imds_tester.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package imds

import (
"bytes"
"context"
"fmt"
"io"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
typedv1 "k8s.io/client-go/kubernetes/typed/core/v1"
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"
)

type ImdsTester struct {
K8sClient *kubernetes.Clientset
Namespace string
}

type ImdsTestResult struct {
IsImdsAccessible bool
NodeRoleName string
}

const ImdsTesterPodName = "mkat-imds-tester"

func (m *ImdsTester) TestImdsAccessible() (*ImdsTestResult, error) {
podDefinition := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: ImdsTesterPodName, Namespace: m.Namespace},
Spec: v1.PodSpec{
Containers: []v1.Container{{
Name: ImdsTesterPodName,
Image: "curlimages/curl:8.00.1",
Command: []string{"sh", "-c", "(curl --silent --show-error --connect-timeout 2 169.254.169.254/latest/meta-data/iam/security-credentials/ || true)"},
}},
RestartPolicy: v1.RestartPolicyNever, // don't restart the pod once the command has been executed
},
}
podsClient := m.K8sClient.CoreV1().Pods(m.Namespace)

log.Println("Testing if IMDS is accessible to pods by creating a pod that attempts to access it")
_, err := podsClient.Create(context.Background(), podDefinition, metav1.CreateOptions{})
if err != nil {
return nil, fmt.Errorf("unable to create IMDS tester pod: %v", err)
}
m.handleCtrlC()
defer removePod(podsClient, ImdsTesterPodName)

err = wait.PollImmediate(1*time.Second, 120*time.Second, func() (bool, error) {
return podHasSuccessfullyCompleted(podsClient, ImdsTesterPodName)
})

if err != nil {
return nil, fmt.Errorf("unable to wait for IMDS tester pod to complete: %v", err)
}

// Retrieve command output
podLogs, err := getPodLogs(podsClient, ImdsTesterPodName)
if err != nil {
return nil, fmt.Errorf("unable to retrieve logs from IMDS tester pod: %v", err)
}
if strings.Contains(podLogs, "Failed to connect") {
return &ImdsTestResult{IsImdsAccessible: false}, nil
} else {
return &ImdsTestResult{IsImdsAccessible: true, NodeRoleName: podLogs}, nil
}
}

func (m *ImdsTester) handleCtrlC() {
// If the user interactively cancels the test, clean up the pod
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
println("Received SIGINT, cleaning up IMDS tester pod")
removePod(m.K8sClient.CoreV1().Pods(m.Namespace), ImdsTesterPodName)
os.Exit(1)
}()
}

func podHasSuccessfullyCompleted(podsClient typedv1.PodInterface, podName string) (bool, error) {
pod, err := podsClient.Get(context.Background(), podName, metav1.GetOptions{})
if err != nil {
return false, err
}

if pod.Status.Phase == v1.PodSucceeded {
return true, nil
} else if pod.Status.Phase != v1.PodPending && pod.Status.Phase != v1.PodRunning {
return false, fmt.Errorf("pod %s errored and is in status %s", podName, pod.Status.Phase)
}

return false, nil
}

func getPodLogs(podsClient typedv1.PodInterface, podName string) (string, error) {
podLogsRequest := podsClient.GetLogs(ImdsTesterPodName, &v1.PodLogOptions{})
podLogs, err := podLogsRequest.Stream(context.Background())
if err != nil {
return "", fmt.Errorf("unable to get logs for pod %s: %v", podName, err)
}
defer podLogs.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, podLogs)
if err != nil {
return "", fmt.Errorf("unable to copy logs for pod %s: %v", podName, err)
}
return buf.String(), nil
}

func removePod(podsClient typedv1.PodInterface, podName string) {
var gracePeriod int64 = 0
_ = podsClient.Delete(context.Background(), podName, metav1.DeleteOptions{
GracePeriodSeconds: &gracePeriod,
})
}

0 comments on commit 353efa1

Please sign in to comment.