-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b6337e3
commit 353efa1
Showing
5 changed files
with
160 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
122 changes: 122 additions & 0 deletions
122
pkg/managed-kubernetes-auditing-toolkit/eks/imds/imds_tester.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}) | ||
} |