Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pod identity support #14

Merged
merged 6 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 33 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ analysis](https://github.com/DataDog/managed-kubernetes-auditing-toolkit/actions
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.

Features:
- 🔎 [Identify trust relationships between K8s service accounts and AWS IAM roles](#identify-trust-relationships-between-k8s-service-accounts-and-aws-iam-roles)
- 🔑 [Find hardcoded AWS credentials in K8s resources](#find-hardcoded-aws-credentials-in-k8s-resources)
- 💀 [Test if pods can access the AWS Instance Metadata Service (IMDS)](#test-if-pods-can-access-the-aws-instance-metadata-service-imds)

_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._
- 🔎 [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.
- 🔑 [Find hardcoded AWS credentials in K8s resources](#find-hardcoded-aws-credentials-in-k8s-resources).
- 💀 [Test if pods can access the AWS Instance Metadata Service (IMDS)](#test-if-pods-can-access-the-aws-instance-metadata-service-imds).

## Installation

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

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

[IAM Roles for Service Accounts](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) is
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`).
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:

- **[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`).

- **[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.

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 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.
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.

```bash
$ mkat eks find-role-relationships
_ _
_ __ ___ | | __ __ _ | |_
_ __ ___ | | __ __ _ | |_
| '_ ` _ \ | |/ / / _` | | __|
| | | | | | | < | (_| | | |_
|_| |_| |_| |_|\_\ \__,_| \__|

2023/04/12 00:25:15 Connected to EKS cluster mkat-cluster
2023/04/12 00:25:15 Retrieving cluster OIDC issuer
2023/04/12 00:25:16 Listing roles in the AWS account
2023/04/12 00:25:18 Listing K8s service accounts in all namespaces
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
+-----------+----------------------+-------------------+-------------------------------------------------------+
| NAMESPACE | SERVICE ACCOUNT | POD | ASSUMABLE ROLE ARN |
+-----------+----------------------+-------------------+-------------------------------------------------------+
| default | apigw-sa | apigw | arn:aws:iam::677301038893:role/apigw-role |
| | | | arn:aws:iam::677301038893:role/s3-reader |
| | inventory-service-sa | inventory-service | arn:aws:iam::677301038893:role/inventory-service-role |
| | | | arn:aws:iam::677301038893:role/s3-reader |
| | kafka-proxy-sa | kafka-proxy | arn:aws:iam::677301038893:role/kafka-proxy-role |
| | rate-limiter-sa | rate-limiter | arn:aws:iam::677301038893:role/rate-limiter-role |
+-----------+----------------------+-------------------+-------------------------------------------------------+
2023/11/28 21:05:59 Connected to EKS cluster mkat-cluster
2023/11/28 21:05:59 Retrieving cluster information
2023/11/28 21:06:00 Listing K8s service accounts in all namespaces
2023/11/28 21:06:02 Listing roles in the AWS account
2023/11/28 21:06:03 Found 286 IAM roles in the AWS account
2023/11/28 21:06:03 Analyzing IAM Roles For Service Accounts (IRSA) configuration
2023/11/28 21:06:03 Analyzing Pod Identity configuration of your cluster
2023/11/28 21:06:04 Analyzing namespace microservices which has 1 Pod Identity associations
+------------------+---------------------------+-----------------------------------+-----------------------------+--------------------------------+
| NAMESPACE | SERVICE ACCOUNT | POD | ASSUMABLE ROLE | MECHANISM |
+------------------+---------------------------+-----------------------------------+-----------------------------+--------------------------------+
| microservices | inventory-service-sa | inventory-service | inventory-service-role | IAM Roles for Service Accounts |
| | | | s3-backup-role | IAM Roles for Service Accounts |
| | rate-limiter-sa | rate-limiter-1 | rate-limiter-role | IAM Roles for Service Accounts |
| | | | webserver-role | Pod Identity |
| | | rate-limiter-2 | rate-limiter-role | IAM Roles for Service Accounts |
| | | | webserver-role | Pod Identity |
+------------------+---------------------------+-----------------------------------+-----------------------------+--------------------------------+
| default | vulnerable-application-sa | vulnerable-application | vulnerable-application-role | IAM Roles for Service Accounts |
| | webserver-sa | webserver | webserver-role | IAM Roles for Service Accounts |
+------------------+---------------------------+-----------------------------------+-----------------------------+--------------------------------+
| external-secrets | external-secrets-sa | external-secrets-66cfb84c9b-kldt9 | ExternalSecretsRole | IAM Roles for Service Accounts |
+------------------+---------------------------+-----------------------------------+-----------------------------+--------------------------------+
```

It can also generate a `dot` output for graphic visualization:
Expand Down
30 changes: 22 additions & 8 deletions cmd/managed-kubernetes-auditing-toolkit/eks/role_relationships.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
var outputFormat string
var outputFile string
var eksClusterName string
var showFullRoleArns bool

// Output formats
const (
Expand Down Expand Up @@ -63,7 +64,7 @@ func buildEksRoleRelationshipsCommand() *cobra.Command {
eksRoleRelationshipsCommand.Flags().StringVarP(&outputFormat, "output-format", "f", DefaultOutputFormat, "Output format. Supported formats: "+strings.Join(availableOutputFormats, ", "))
eksRoleRelationshipsCommand.Flags().StringVarP(&outputFile, "output-file", "o", "", "Output file. If not specified, output will be printed to stdout.")
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")

eksRoleRelationshipsCommand.Flags().BoolVarP(&showFullRoleArns, "show-full-role-arns", "", false, "Show full ARNs of roles instead of just the role name")
return eksRoleRelationshipsCommand
}

Expand Down Expand Up @@ -118,15 +119,15 @@ func getTextOutput(resolver *role_relationships.EKSCluster) (string, error) {
{Number: 2, AutoMerge: true, VAlign: text.VAlignMiddle},
{Number: 3, AutoMerge: true, VAlign: text.VAlignMiddle},
})
t.AppendHeader(table.Row{"Namespace", "Service Account", "Pod", "Assumable Role ARN"})
t.AppendHeader(table.Row{"Namespace", "Service Account", "Pod", "Assumable Role", "Mechanism"})
var found = false
for namespace, pods := range resolver.PodsByNamespace {
for _, pod := range pods {
if pod.ServiceAccount == nil || len(pod.ServiceAccount.AssumableRoles) == 0 {
continue
}
for _, role := range pod.ServiceAccount.AssumableRoles {
t.AppendRow([]interface{}{namespace, pod.ServiceAccount.Name, pod.Name, role.Arn})
t.AppendRow([]interface{}{namespace, pod.ServiceAccount.Name, pod.Name, getRoleDisplayName(role.IAMRole), role.Reason})
found = true
}
}
Expand All @@ -146,6 +147,7 @@ type Vertex struct {
func (v *Vertex) GetID() int {
return v.ID
}

func getDotOutput(resolver *role_relationships.EKSCluster) (string, error) {
graphAst, _ := gographviz.ParseString(`digraph G { }`)
graphViz := gographviz.NewGraph()
Expand Down Expand Up @@ -179,8 +181,7 @@ func getDotOutput(resolver *role_relationships.EKSCluster) (string, error) {
"fontsize": "12",
})
for _, role := range pod.ServiceAccount.AssumableRoles {
parsedArn, _ := arn.Parse(role.Arn)
roleLabel := fmt.Sprintf(`"IAM role %s"`, strings.Split(parsedArn.Resource, "/")[1])
roleLabel := fmt.Sprintf(`"IAM role %s"`, getRoleName(role.IAMRole))
graphViz.AddNode("G", roleLabel, map[string]string{
"fontname": "Helvetica",
"shape": "box",
Expand All @@ -204,19 +205,20 @@ func getDotOutput(resolver *role_relationships.EKSCluster) (string, error) {

func getCsvOutput(resolver *role_relationships.EKSCluster) (string, error) {
sb := new(strings.Builder)
sb.WriteString("namespace,pod,service_account,role_arn")
sb.WriteString("namespace,pod,service_account,role_arn,reason")
for namespace, pods := range resolver.PodsByNamespace {
for _, pod := range pods {
if pod.ServiceAccount == nil || len(pod.ServiceAccount.AssumableRoles) == 0 {
continue
}
for _, role := range pod.ServiceAccount.AssumableRoles {
sb.WriteString(fmt.Sprintf(
"%s,%s,%s,%s",
"%s,%s,%s,%s,%s",
namespace,
pod.Name,
pod.ServiceAccount.Name,
role.Arn,
getRoleDisplayName(role.IAMRole),
role.Reason,
))
sb.WriteRune('\n')
}
Expand All @@ -225,3 +227,15 @@ func getCsvOutput(resolver *role_relationships.EKSCluster) (string, error) {

return sb.String(), nil
}

func getRoleDisplayName(role *role_relationships.IAMRole) string {
if showFullRoleArns {
return role.Arn
}
return getRoleName(role)
}

func getRoleName(role *role_relationships.IAMRole) string {
parsedArn, _ := arn.Parse(role.Arn)
return strings.Split(parsedArn.Resource, "/")[1]
}
48 changes: 16 additions & 32 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ go 1.19

require (
github.com/awalterschulze/gographviz v2.0.3+incompatible
github.com/aws/aws-sdk-go-v2 v1.17.6
github.com/aws/aws-sdk-go-v2/config v1.18.16
github.com/aws/aws-sdk-go-v2/service/eks v1.27.6
github.com/aws/aws-sdk-go-v2/service/iam v1.19.5
github.com/aws/aws-sdk-go-v2 v1.23.1
github.com/aws/aws-sdk-go-v2/config v1.25.6
github.com/aws/aws-sdk-go-v2/service/eks v1.34.1
github.com/aws/aws-sdk-go-v2/service/iam v1.27.4
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be
github.com/fatih/color v1.15.0
github.com/hashicorp/go-version v1.6.0
github.com/jedib0t/go-pretty/v6 v6.4.6
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.0
Expand All @@ -21,68 +22,51 @@ require (
)

require (
github.com/aws/aws-sdk-go-v2/credentials v1.13.16 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.24 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.31 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.24 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.5 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.5 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.18.6 // indirect
github.com/aws/smithy-go v1.13.5 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.5 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.17.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.25.5 // indirect
github.com/aws/smithy-go v1.17.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/emirpasic/gods v1.12.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-licenses v1.6.0 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/licenseclassifier v0.0.0-20210722185704-3043a050f148 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/otiai10/copy v1.6.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/src-d/gcfg v1.4.0 // indirect
github.com/xanzy/ssh-agent v0.2.1 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
golang.org/x/tools v0.5.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
gopkg.in/src-d/go-git.v4 v4.13.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
Expand Down
Loading
Loading