From 799f0c3bdcfc18fcadf3b5e0c1c9ebaa543e18a8 Mon Sep 17 00:00:00 2001 From: Musa Date: Tue, 11 Feb 2025 16:01:24 -0500 Subject: [PATCH] Fix EKS cluster detection (#1532) --- translator/util/eksdetector/eksdetector.go | 47 ++++++++++++++++++- .../util/eksdetector/eksdetector_test.go | 26 ++++++++++ .../util/eksdetector/eksdetectortestutil.go | 7 ++- 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/translator/util/eksdetector/eksdetector.go b/translator/util/eksdetector/eksdetector.go index 58830883d6..a8677b98ed 100644 --- a/translator/util/eksdetector/eksdetector.go +++ b/translator/util/eksdetector/eksdetector.go @@ -5,7 +5,10 @@ package eksdetector import ( "context" + "encoding/base64" + "encoding/json" "fmt" + "strings" "sync" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -15,6 +18,7 @@ import ( type Detector interface { getConfigMap(namespace string, name string) (map[string]string, error) + getIssuer() (string, error) } type EksDetector struct { @@ -55,7 +59,8 @@ var ( } // IsEKS checks if the agent is running on EKS. This is done by using the kubernetes API to determine if the aws-auth - // configmap exists in the kube-system namespace + // configmap exists in the kube-system namespace or by extracting the "iss" field from the service account token and + // checking if it contains "eks" as a fall-back IsEKS = func() IsEKSCache { once.Do(func() { var errors error @@ -71,6 +76,11 @@ var ( awsAuth, err := eksDetector.getConfigMap(authConfigNamespace, authConfigConfigMap) if err == nil { value = awsAuth != nil + } else { + issuer, err := eksDetector.getIssuer() + if err == nil { + value = strings.Contains(strings.ToLower(issuer), "eks") + } } } isEKSCacheSingleton = IsEKSCache{Value: value, Err: errors} @@ -90,6 +100,41 @@ func (d *EksDetector) getConfigMap(namespace string, name string) (map[string]st return configMap.Data, nil } +// getIssuer retrieves the issuer ("iss") from the service account token +func (d *EksDetector) getIssuer() (string, error) { + conf, err := getInClusterConfig() + if err != nil { + return "", fmt.Errorf("failed to get in-cluster config: %w", err) + } + + token := conf.BearerToken + if token == "" { + return "", fmt.Errorf("empty token in config") + } + + parts := strings.Split(token, ".") + if len(parts) < 2 { + return "", fmt.Errorf("missing payload") + } + + decoded, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return "", fmt.Errorf("failed to decode token payload: %w", err) + } + + var claims map[string]interface{} + if err = json.Unmarshal(decoded, &claims); err != nil { + return "", fmt.Errorf("failed to unmarshal token payload: %w", err) + } + + iss, ok := claims["iss"].(string) + if !ok { + return "", fmt.Errorf("issuer field not found in token") + } + + return iss, nil +} + func getClient() (kubernetes.Interface, error) { //Get cluster config confs, err := getInClusterConfig() diff --git a/translator/util/eksdetector/eksdetector_test.go b/translator/util/eksdetector/eksdetector_test.go index a5dafaf446..7a569e3963 100644 --- a/translator/util/eksdetector/eksdetector_test.go +++ b/translator/util/eksdetector/eksdetector_test.go @@ -4,6 +4,7 @@ package eksdetector import ( + "encoding/base64" "fmt" "testing" @@ -54,9 +55,17 @@ func TestEKS(t *testing.T) { } testDetector.On("getConfigMap", authConfigNamespace, authConfigConfigMap).Return(map[string]string{conventions.AttributeK8SClusterName: "my-cluster"}, nil) + isEks := IsEKS() assert.True(t, isEks.Value) assert.NoError(t, isEks.Err) + + testDetector.On("getConfigMap", authConfigNamespace, authConfigConfigMap).Return(nil, fmt.Errorf("configmap not found")) + testDetector.On("getIssuer").Return("https://oidc.eks.us-west-2.amazonaws.com/id/someid", nil) + + isEks = IsEKS() + assert.True(t, isEks.Value) + assert.NoError(t, isEks.Err) } func Test_getConfigMap(t *testing.T) { @@ -82,6 +91,23 @@ func Test_getConfigMap(t *testing.T) { assert.NotNil(t, res) } +func Test_getIssuer(t *testing.T) { + client := fake.NewSimpleClientset() + testDetector := &EksDetector{Clientset: client} + + payload := `{"iss":"https://oidc.eks.us-west-2.amazonaws.com/id/someid"}` + encodedPayload := base64.RawURLEncoding.EncodeToString([]byte(payload)) + dummyToken := "header." + encodedPayload + ".signature" + + getInClusterConfig = func() (*rest.Config, error) { + return &rest.Config{BearerToken: dummyToken}, nil + } + + issuer, err := testDetector.getIssuer() + assert.NoError(t, err) + assert.Equal(t, "https://oidc.eks.us-west-2.amazonaws.com/id/someid", issuer) +} + func Test_getClientError(t *testing.T) { //InClusterConfig error getInClusterConfig = func() (*rest.Config, error) { diff --git a/translator/util/eksdetector/eksdetectortestutil.go b/translator/util/eksdetector/eksdetectortestutil.go index c3b14a48c5..dc449d9482 100644 --- a/translator/util/eksdetector/eksdetectortestutil.go +++ b/translator/util/eksdetector/eksdetectortestutil.go @@ -25,7 +25,7 @@ var ( return &EksDetector{Clientset: fake.NewSimpleClientset()}, nil } - // TestIsEKSCacheEKS os used for unit testing EKS route + // TestIsEKSCacheEKS is used for unit testing EKS route TestIsEKSCacheEKS = func() IsEKSCache { return IsEKSCache{Value: true, Err: nil} } @@ -44,3 +44,8 @@ func (detector *MockDetector) getConfigMap(namespace string, name string) (map[s args := detector.Called(namespace, name) return args.Get(0).(map[string]string), args.Error(1) } + +func (detector *MockDetector) getIssuer() (string, error) { + args := detector.Called() + return args.Get(0).(string), args.Error(1) +}