Skip to content

Commit 6da7e30

Browse files
authored
Support EKS Pod Identity to identify relationships between pods and IAM roles
Pod identity support closes #13
2 parents 654e898 + 4012946 commit 6da7e30

File tree

5 files changed

+288
-474
lines changed

5 files changed

+288
-474
lines changed

README.md

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,9 @@ analysis](https://github.com/DataDog/managed-kubernetes-auditing-toolkit/actions
77
MKAT is an all-in-one auditing toolkit for identifying common security issues within managed Kubernetes environments. It is focused on Amazon EKS at the moment, and will be extended to other managed Kubernetes environments in the future.
88

99
Features:
10-
- 🔎 [Identify trust relationships between K8s service accounts and AWS IAM roles](#identify-trust-relationships-between-k8s-service-accounts-and-aws-iam-roles)
11-
- 🔑 [Find hardcoded AWS credentials in K8s resources](#find-hardcoded-aws-credentials-in-k8s-resources)
12-
- 💀 [Test if pods can access the AWS Instance Metadata Service (IMDS)](#test-if-pods-can-access-the-aws-instance-metadata-service-imds)
13-
14-
_Note: At the time, MKAT doesn't support EKS Pod Identity, [released](https://aws.amazon.com/blogs/aws/amazon-eks-pod-identity-simplifies-iam-permissions-for-applications-on-amazon-eks-clusters/) on November 26th 2023. Watch [#13] for updates._
10+
- 🔎 [Identify trust relationships between K8s service accounts and AWS IAM roles](#identify-trust-relationships-between-k8s-service-accounts-and-aws-iam-roles) - supports both IAM Roles for Service Accounts (IRSA), and [Pod Identity](https://aws.amazon.com/blogs/aws/amazon-eks-pod-identity-simplifies-iam-permissions-for-applications-on-amazon-eks-clusters/), released on November 26 2023.
11+
- 🔑 [Find hardcoded AWS credentials in K8s resources](#find-hardcoded-aws-credentials-in-k8s-resources).
12+
- 💀 [Test if pods can access the AWS Instance Metadata Service (IMDS)](#test-if-pods-can-access-the-aws-instance-metadata-service-imds).
1513

1614
## Installation
1715

@@ -33,35 +31,44 @@ aws eks update-kubeconfig --name <cluster-name>
3331

3432
### Identify trust relationships between K8s service accounts and AWS IAM roles
3533

36-
[IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) is
37-
a popular mechanism to allow pods to assume AWS IAM roles, by exchanging a Kubernetes service account token for AWS credentials through the AWS STS API (`AssumeRoleWithWebIdentity`).
34+
MKAT can identify the trust relationships between K8s service accounts and AWS IAM roles, and display them in a table or as a graph. It currently supports:
35+
36+
- **[IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)**, a popular mechanism to allow pods to assume AWS IAM roles by exchanging a Kubernetes service account token for AWS credentials through the AWS STS API (`AssumeRoleWithWebIdentity`).
37+
38+
- **[EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html)**, another newer mechanism that works in a similar way, but is easier to set up.
3839

39-
MKAT can identify the trust relationships between K8s service accounts and AWS IAM roles, and display them in a table or as a graph.
40-
It works by looking both at the trust policy of the IAM roles, and at the service accounts that are associated with the pods running in the cluster.
40+
MKAT works by analyzing both the IAM roles in the AWS account, and the K8s service accounts in the cluster, and then matching them together based on these two mechanisms.
4141

4242
```bash
4343
$ mkat eks find-role-relationships
44-
_ _
45-
_ __ ___ | | __ __ _ | |_
44+
_ __ ___ | | __ __ _ | |_
4645
| '_ ` _ \ | |/ / / _` | | __|
4746
| | | | | | | < | (_| | | |_
4847
|_| |_| |_| |_|\_\ \__,_| \__|
4948
50-
2023/04/12 00:25:15 Connected to EKS cluster mkat-cluster
51-
2023/04/12 00:25:15 Retrieving cluster OIDC issuer
52-
2023/04/12 00:25:16 Listing roles in the AWS account
53-
2023/04/12 00:25:18 Listing K8s service accounts in all namespaces
54-
2023/04/12 00:25:19 Analyzing the trust policy of 5 IAM roles that have the cluster's OIDC provider in their trust policy
55-
+-----------+----------------------+-------------------+-------------------------------------------------------+
56-
| NAMESPACE | SERVICE ACCOUNT | POD | ASSUMABLE ROLE ARN |
57-
+-----------+----------------------+-------------------+-------------------------------------------------------+
58-
| default | apigw-sa | apigw | arn:aws:iam::677301038893:role/apigw-role |
59-
| | | | arn:aws:iam::677301038893:role/s3-reader |
60-
| | inventory-service-sa | inventory-service | arn:aws:iam::677301038893:role/inventory-service-role |
61-
| | | | arn:aws:iam::677301038893:role/s3-reader |
62-
| | kafka-proxy-sa | kafka-proxy | arn:aws:iam::677301038893:role/kafka-proxy-role |
63-
| | rate-limiter-sa | rate-limiter | arn:aws:iam::677301038893:role/rate-limiter-role |
64-
+-----------+----------------------+-------------------+-------------------------------------------------------+
49+
2023/11/28 21:05:59 Connected to EKS cluster mkat-cluster
50+
2023/11/28 21:05:59 Retrieving cluster information
51+
2023/11/28 21:06:00 Listing K8s service accounts in all namespaces
52+
2023/11/28 21:06:02 Listing roles in the AWS account
53+
2023/11/28 21:06:03 Found 286 IAM roles in the AWS account
54+
2023/11/28 21:06:03 Analyzing IAM Roles For Service Accounts (IRSA) configuration
55+
2023/11/28 21:06:03 Analyzing Pod Identity configuration of your cluster
56+
2023/11/28 21:06:04 Analyzing namespace microservices which has 1 Pod Identity associations
57+
+------------------+---------------------------+-----------------------------------+-----------------------------+--------------------------------+
58+
| NAMESPACE | SERVICE ACCOUNT | POD | ASSUMABLE ROLE | MECHANISM |
59+
+------------------+---------------------------+-----------------------------------+-----------------------------+--------------------------------+
60+
| microservices | inventory-service-sa | inventory-service | inventory-service-role | IAM Roles for Service Accounts |
61+
| | | | s3-backup-role | IAM Roles for Service Accounts |
62+
| | rate-limiter-sa | rate-limiter-1 | rate-limiter-role | IAM Roles for Service Accounts |
63+
| | | | webserver-role | Pod Identity |
64+
| | | rate-limiter-2 | rate-limiter-role | IAM Roles for Service Accounts |
65+
| | | | webserver-role | Pod Identity |
66+
+------------------+---------------------------+-----------------------------------+-----------------------------+--------------------------------+
67+
| default | vulnerable-application-sa | vulnerable-application | vulnerable-application-role | IAM Roles for Service Accounts |
68+
| | webserver-sa | webserver | webserver-role | IAM Roles for Service Accounts |
69+
+------------------+---------------------------+-----------------------------------+-----------------------------+--------------------------------+
70+
| external-secrets | external-secrets-sa | external-secrets-66cfb84c9b-kldt9 | ExternalSecretsRole | IAM Roles for Service Accounts |
71+
+------------------+---------------------------+-----------------------------------+-----------------------------+--------------------------------+
6572
```
6673
6774
It can also generate a `dot` output for graphic visualization:

cmd/managed-kubernetes-auditing-toolkit/eks/role_relationships.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
var outputFormat string
2323
var outputFile string
2424
var eksClusterName string
25+
var showFullRoleArns bool
2526

2627
// Output formats
2728
const (
@@ -63,7 +64,7 @@ func buildEksRoleRelationshipsCommand() *cobra.Command {
6364
eksRoleRelationshipsCommand.Flags().StringVarP(&outputFormat, "output-format", "f", DefaultOutputFormat, "Output format. Supported formats: "+strings.Join(availableOutputFormats, ", "))
6465
eksRoleRelationshipsCommand.Flags().StringVarP(&outputFile, "output-file", "o", "", "Output file. If not specified, output will be printed to stdout.")
6566
eksRoleRelationshipsCommand.Flags().StringVarP(&eksClusterName, "eks-cluster-name", "", "", "When the EKS cluster name cannot be automatically detected from your KubeConfig, specify this argument to pass the EKS cluster name of your current kubectl context")
66-
67+
eksRoleRelationshipsCommand.Flags().BoolVarP(&showFullRoleArns, "show-full-role-arns", "", false, "Show full ARNs of roles instead of just the role name")
6768
return eksRoleRelationshipsCommand
6869
}
6970

@@ -118,15 +119,15 @@ func getTextOutput(resolver *role_relationships.EKSCluster) (string, error) {
118119
{Number: 2, AutoMerge: true, VAlign: text.VAlignMiddle},
119120
{Number: 3, AutoMerge: true, VAlign: text.VAlignMiddle},
120121
})
121-
t.AppendHeader(table.Row{"Namespace", "Service Account", "Pod", "Assumable Role ARN"})
122+
t.AppendHeader(table.Row{"Namespace", "Service Account", "Pod", "Assumable Role", "Mechanism"})
122123
var found = false
123124
for namespace, pods := range resolver.PodsByNamespace {
124125
for _, pod := range pods {
125126
if pod.ServiceAccount == nil || len(pod.ServiceAccount.AssumableRoles) == 0 {
126127
continue
127128
}
128129
for _, role := range pod.ServiceAccount.AssumableRoles {
129-
t.AppendRow([]interface{}{namespace, pod.ServiceAccount.Name, pod.Name, role.Arn})
130+
t.AppendRow([]interface{}{namespace, pod.ServiceAccount.Name, pod.Name, getRoleDisplayName(role.IAMRole), role.Reason})
130131
found = true
131132
}
132133
}
@@ -146,6 +147,7 @@ type Vertex struct {
146147
func (v *Vertex) GetID() int {
147148
return v.ID
148149
}
150+
149151
func getDotOutput(resolver *role_relationships.EKSCluster) (string, error) {
150152
graphAst, _ := gographviz.ParseString(`digraph G { }`)
151153
graphViz := gographviz.NewGraph()
@@ -179,8 +181,7 @@ func getDotOutput(resolver *role_relationships.EKSCluster) (string, error) {
179181
"fontsize": "12",
180182
})
181183
for _, role := range pod.ServiceAccount.AssumableRoles {
182-
parsedArn, _ := arn.Parse(role.Arn)
183-
roleLabel := fmt.Sprintf(`"IAM role %s"`, strings.Split(parsedArn.Resource, "/")[1])
184+
roleLabel := fmt.Sprintf(`"IAM role %s"`, getRoleName(role.IAMRole))
184185
graphViz.AddNode("G", roleLabel, map[string]string{
185186
"fontname": "Helvetica",
186187
"shape": "box",
@@ -204,19 +205,20 @@ func getDotOutput(resolver *role_relationships.EKSCluster) (string, error) {
204205

205206
func getCsvOutput(resolver *role_relationships.EKSCluster) (string, error) {
206207
sb := new(strings.Builder)
207-
sb.WriteString("namespace,pod,service_account,role_arn")
208+
sb.WriteString("namespace,pod,service_account,role_arn,reason")
208209
for namespace, pods := range resolver.PodsByNamespace {
209210
for _, pod := range pods {
210211
if pod.ServiceAccount == nil || len(pod.ServiceAccount.AssumableRoles) == 0 {
211212
continue
212213
}
213214
for _, role := range pod.ServiceAccount.AssumableRoles {
214215
sb.WriteString(fmt.Sprintf(
215-
"%s,%s,%s,%s",
216+
"%s,%s,%s,%s,%s",
216217
namespace,
217218
pod.Name,
218219
pod.ServiceAccount.Name,
219-
role.Arn,
220+
getRoleDisplayName(role.IAMRole),
221+
role.Reason,
220222
))
221223
sb.WriteRune('\n')
222224
}
@@ -225,3 +227,15 @@ func getCsvOutput(resolver *role_relationships.EKSCluster) (string, error) {
225227

226228
return sb.String(), nil
227229
}
230+
231+
func getRoleDisplayName(role *role_relationships.IAMRole) string {
232+
if showFullRoleArns {
233+
return role.Arn
234+
}
235+
return getRoleName(role)
236+
}
237+
238+
func getRoleName(role *role_relationships.IAMRole) string {
239+
parsedArn, _ := arn.Parse(role.Arn)
240+
return strings.Split(parsedArn.Resource, "/")[1]
241+
}

go.mod

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,13 @@ go 1.19
44

55
require (
66
github.com/awalterschulze/gographviz v2.0.3+incompatible
7-
github.com/aws/aws-sdk-go-v2 v1.17.6
8-
github.com/aws/aws-sdk-go-v2/config v1.18.16
9-
github.com/aws/aws-sdk-go-v2/service/eks v1.27.6
10-
github.com/aws/aws-sdk-go-v2/service/iam v1.19.5
7+
github.com/aws/aws-sdk-go-v2 v1.23.1
8+
github.com/aws/aws-sdk-go-v2/config v1.25.6
9+
github.com/aws/aws-sdk-go-v2/service/eks v1.34.1
10+
github.com/aws/aws-sdk-go-v2/service/iam v1.27.4
1111
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be
1212
github.com/fatih/color v1.15.0
13+
github.com/hashicorp/go-version v1.6.0
1314
github.com/jedib0t/go-pretty/v6 v6.4.6
1415
github.com/spf13/cobra v1.6.1
1516
github.com/stretchr/testify v1.8.0
@@ -21,68 +22,51 @@ require (
2122
)
2223

2324
require (
24-
github.com/aws/aws-sdk-go-v2/credentials v1.13.16 // indirect
25-
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.24 // indirect
26-
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 // indirect
27-
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 // indirect
28-
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 // indirect
29-
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 // indirect
30-
github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 // indirect
31-
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 // indirect
32-
github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 // indirect
33-
github.com/aws/smithy-go v1.13.5 // indirect
25+
github.com/aws/aws-sdk-go-v2/credentials v1.16.5 // indirect
26+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 // indirect
27+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 // indirect
28+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 // indirect
29+
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect
30+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 // indirect
31+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 // indirect
32+
github.com/aws/aws-sdk-go-v2/service/sso v1.17.4 // indirect
33+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.2 // indirect
34+
github.com/aws/aws-sdk-go-v2/service/sts v1.25.5 // indirect
35+
github.com/aws/smithy-go v1.17.0 // indirect
3436
github.com/davecgh/go-spew v1.1.1 // indirect
3537
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
36-
github.com/emirpasic/gods v1.12.0 // indirect
3738
github.com/go-logr/logr v1.2.3 // indirect
3839
github.com/go-openapi/jsonpointer v0.19.5 // indirect
3940
github.com/go-openapi/jsonreference v0.20.0 // indirect
4041
github.com/go-openapi/swag v0.19.14 // indirect
4142
github.com/gogo/protobuf v1.3.2 // indirect
42-
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
4343
github.com/golang/protobuf v1.5.2 // indirect
4444
github.com/google/gnostic v0.5.7-v3refs // indirect
4545
github.com/google/go-cmp v0.5.9 // indirect
46-
github.com/google/go-licenses v1.6.0 // indirect
4746
github.com/google/gofuzz v1.1.0 // indirect
48-
github.com/google/licenseclassifier v0.0.0-20210722185704-3043a050f148 // indirect
4947
github.com/imdario/mergo v0.3.6 // indirect
5048
github.com/inconshreveable/mousetrap v1.0.1 // indirect
51-
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
5249
github.com/jmespath/go-jmespath v0.4.0 // indirect
5350
github.com/josharian/intern v1.0.0 // indirect
5451
github.com/json-iterator/go v1.1.12 // indirect
55-
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
5652
github.com/mailru/easyjson v0.7.6 // indirect
5753
github.com/mattn/go-colorable v0.1.13 // indirect
5854
github.com/mattn/go-isatty v0.0.17 // indirect
5955
github.com/mattn/go-runewidth v0.0.13 // indirect
60-
github.com/mitchellh/go-homedir v1.1.0 // indirect
6156
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
6257
github.com/modern-go/reflect2 v1.0.2 // indirect
6358
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
64-
github.com/otiai10/copy v1.6.0 // indirect
6559
github.com/pmezard/go-difflib v1.0.0 // indirect
6660
github.com/rivo/uniseg v0.2.0 // indirect
67-
github.com/sergi/go-diff v1.2.0 // indirect
6861
github.com/spf13/pflag v1.0.5 // indirect
69-
github.com/src-d/gcfg v1.4.0 // indirect
70-
github.com/xanzy/ssh-agent v0.2.1 // indirect
71-
go.opencensus.io v0.23.0 // indirect
72-
golang.org/x/crypto v0.1.0 // indirect
73-
golang.org/x/mod v0.7.0 // indirect
7462
golang.org/x/net v0.7.0 // indirect
7563
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect
7664
golang.org/x/sys v0.6.0 // indirect
7765
golang.org/x/text v0.7.0 // indirect
7866
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
79-
golang.org/x/tools v0.5.0 // indirect
8067
google.golang.org/appengine v1.6.7 // indirect
8168
google.golang.org/protobuf v1.28.1 // indirect
8269
gopkg.in/inf.v0 v0.9.1 // indirect
83-
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
84-
gopkg.in/src-d/go-git.v4 v4.13.1 // indirect
85-
gopkg.in/warnings.v0 v0.1.2 // indirect
8670
gopkg.in/yaml.v2 v2.4.0 // indirect
8771
gopkg.in/yaml.v3 v3.0.1 // indirect
8872
k8s.io/klog/v2 v2.80.1 // indirect

0 commit comments

Comments
 (0)