diff --git a/docs/attack-techniques/EKS/eks.lateral-movement.create-access-entry.md b/docs/attack-techniques/EKS/eks.lateral-movement.create-access-entry.md new file mode 100755 index 000000000..d50c5baa6 --- /dev/null +++ b/docs/attack-techniques/EKS/eks.lateral-movement.create-access-entry.md @@ -0,0 +1,85 @@ +--- +title: Create Admin EKS Access Entry +--- + +# Create Admin EKS Access Entry + + + + +Platform: EKS + +## MITRE ATT&CK Tactics + + +- Lateral Movement + +## Description + + +Uses the EKS Cluster Access Management to assign cluster administrator privileges to an IAM role. This allows the role to perform any action inside the Kubernetes cluster. + +Warm-up: + +- Create an IAM role + +Detonation: + +- Create an access entry for the IAM role +- Associate the access entry with the AmazonEKSClusterAdminPolicy access policy + +References: + +- https://securitylabs.datadoghq.com/articles/eks-cluster-access-management-deep-dive/ +- https://docs.aws.amazon.com/eks/latest/userguide/access-entries.html + + +## Instructions + +```bash title="Detonate with Stratus Red Team" +stratus detonate eks.lateral-movement.create-access-entry +``` +## Detection + + +You can use the following CloudTrail events to identify when someone grants access to your EKS cluster: + +- **CreateAccessEntry**, when someone creates an access entry for a principal (meaning it's the first this principal is granted privileges in the cluster)': + +```json +{ + "eventSource": "eks.amazonaws.com", + "eventName": "CreateAccessEntry", + "requestParameters": { + "name": "eks-cluster", + "principalArn": "arn:aws:iam::012345678901:role/stratus-red-team-eks-create-access-entry-role" + }, + "responseElements": { + "accessEntry": { + "clusterName": "eks-cluster", + "type": "STANDARD", + "principalArn": "arn:aws:iam::012345678901:role/stratus-red-team-eks-create-access-entry-role", + } + } +} +``` + + +- **AssociateAccessPolicy**: when someone assigns an access policy to a principal + +```json +{ + "eventSource": "eks.amazonaws.com", + "eventName": "AssociateAccessPolicy", + "requestParameters": { + "policyArn": "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy", + "accessScope": { + "type": "cluster" + }, + "name": "eks-cluster", + "principalArn": "arn%3Aaws%3Aiam%3A%3A012345678901%3Arole%2Fstratus-red-team-eks-create-access-entry-role" + } +} +``` + + diff --git a/docs/attack-techniques/EKS/index.md b/docs/attack-techniques/EKS/index.md new file mode 100755 index 000000000..a4c64f9e4 --- /dev/null +++ b/docs/attack-techniques/EKS/index.md @@ -0,0 +1,10 @@ +# EKS + +This page contains the Stratus attack techniques for EKS, grouped by MITRE ATT&CK Tactic. +Note that some Stratus attack techniques may correspond to more than a single ATT&CK Tactic. + + +## Lateral Movement + +- [Create Admin EKS Access Entry](./eks.lateral-movement.create-access-entry.md) + diff --git a/docs/attack-techniques/list.md b/docs/attack-techniques/list.md index 0da70d670..38168d115 100755 --- a/docs/attack-techniques/list.md +++ b/docs/attack-techniques/list.md @@ -50,6 +50,7 @@ This page contains the list of all Stratus Attack Techniques. | [Execute Command on Virtual Machine using Custom Script Extension](./azure/azure.execution.vm-custom-script-extension.md) | [Azure](./azure/index.md) | Execution | | [Execute Commands on Virtual Machine using Run Command](./azure/azure.execution.vm-run-command.md) | [Azure](./azure/index.md) | Execution | | [Export Disk Through SAS URL](./azure/azure.exfiltration.disk-export.md) | [Azure](./azure/index.md) | Exfiltration | +| [Create Admin EKS Access Entry](./EKS/eks.lateral-movement.create-access-entry.md) | [EKS](./EKS/index.md) | Lateral Movement | | [Exfiltrate Compute Disk by sharing it](./GCP/gcp.exfiltration.share-compute-disk.md) | [GCP](./GCP/index.md) | Exfiltration | | [Exfiltrate Compute Image by sharing it](./GCP/gcp.exfiltration.share-compute-image.md) | [GCP](./GCP/index.md) | Exfiltration | | [Exfiltrate Compute Disk by sharing a snapshot](./GCP/gcp.exfiltration.share-compute-snapshot.md) | [GCP](./GCP/index.md) | Exfiltration | diff --git a/docs/index.yaml b/docs/index.yaml index cdb2d5a34..1dcf4fc2d 100644 --- a/docs/index.yaml +++ b/docs/index.yaml @@ -329,6 +329,15 @@ AWS: - Privilege Escalation platform: AWS isIdempotent: false +EKS: + Lateral Movement: + - id: eks.lateral-movement.create-access-entry + name: Create Admin EKS Access Entry + isSlow: false + mitreAttackTactics: + - Lateral Movement + platform: EKS + isIdempotent: false GCP: Exfiltration: - id: gcp.exfiltration.share-compute-disk diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index e77a5f6e3..627127dd2 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -94,7 +94,7 @@ For more information, see [Usage](./usage.md), [Examples](./examples.md) and the ## Connecting to your cloud account -Stratus Red Team currently supports AWS and Kubernetes. +Stratus Red Team currently supports AWS, Azure, Kubernetes, and Amazon EKS. !!! warning @@ -108,6 +108,15 @@ In order to use Stratus attack techniques against AWS, you need to be authentica - Using static credentials in `~/.aws/config`, and setting your desired AWS profile using `export AWS_PROFILE=my-profile` +### EKS + +Stratus Red Team does **not** create an EKS cluster for you. It assumes you're already authenticated to an EKS cluster. + +To use Stratus attack techniques against Amazon EKS, you need to be authenticated against AWS, as described above. Stratus Red Team will use the current AWS credentials and Kubernetes context to interact with the EKS cluster. It will check that the Kubernetes cluster you're connected to is an EKS cluster, and refuse to run otherwise. + +- Authenticate to AWS (for instance, using [`aws-vault`](https://github.com/99designs/aws-vault)) +- Run `aws eks update-kubeconfig --name your-cluster-name --region your-region` to update your `~/.kube/config` file with the EKS cluster configuration + ### Azure - Use the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) to authenticate against your Azure tenant: diff --git a/v2/go.mod b/v2/go.mod index 4097ce91f..1ede2b541 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -8,7 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute v1.0.0 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 - github.com/aws/aws-sdk-go-v2 v1.26.1 + github.com/aws/aws-sdk-go-v2 v1.30.1 github.com/aws/aws-sdk-go-v2/config v1.25.11 github.com/aws/aws-sdk-go-v2/credentials v1.16.9 github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.4 @@ -27,7 +27,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sesv2 v1.27.3 github.com/aws/aws-sdk-go-v2/service/ssm v1.44.2 github.com/aws/aws-sdk-go-v2/service/sts v1.26.2 - github.com/aws/smithy-go v1.20.2 + github.com/aws/smithy-go v1.20.3 github.com/cenkalti/backoff/v4 v4.2.1 github.com/fatih/color v1.13.0 github.com/golang-jwt/jwt v3.2.2+incompatible @@ -51,10 +51,11 @@ require ( github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.9 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.8 // indirect + github.com/aws/aws-sdk-go-v2/service/eks v1.46.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.8 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.8 // indirect diff --git a/v2/go.sum b/v2/go.sum index 49b86f7e0..915196e85 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -36,6 +36,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA= github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM= +github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o= +github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3 h1:Zx9+31KyB8wQna6SXFWOewlgoY5uGdDAu6PTOEU3OQI= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.3/go.mod h1:zxbEJhRdKTH1nqS2qu6UJ7zGe25xaHxZXaC2CvuQFnA= github.com/aws/aws-sdk-go-v2/config v1.25.11 h1:RWzp7jhPRliIcACefGkKp03L0Yofmd2p8M25kbiyvno= @@ -48,8 +50,12 @@ github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.4 h1:TUCNKBd4/JEefsZDxo5de github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.15.4/go.mod h1:egDkcl+zsgFqS6VO142bKboip5Pe1sNMwN55Xy38QsM= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg= github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0= github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw= github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY= github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.8 h1:abKT+RuM1sdCNZIGIfZpLkvxEX3Rpsto019XG/rkYG8= @@ -60,6 +66,8 @@ github.com/aws/aws-sdk-go-v2/service/ec2 v1.138.2 h1:e3Imv1oXz+W3Tfclflkh72t5TUP github.com/aws/aws-sdk-go-v2/service/ec2 v1.138.2/go.mod h1:d1hAqgLDOPaSO1Piy/0bBmj6oAplFwv6p0cquHntNHM= github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect v1.20.6 h1:Y0pqdpafA8TdG6AalCMFbbQ5SlO99MAybU0BDPLHbwo= github.com/aws/aws-sdk-go-v2/service/ec2instanceconnect v1.20.6/go.mod h1:y6fUhf01cjz+VUz+zrmJh3KfIXhefV7dS4STCxgHx7g= +github.com/aws/aws-sdk-go-v2/service/eks v1.46.0 h1:ZPhHHZtAjVohIGIVjXECPfljcPOQ+hjZ1IpgvjPTJ50= +github.com/aws/aws-sdk-go-v2/service/eks v1.46.0/go.mod h1:p4Yk0zfWEoLvvQ4V6XZrTmAAPzcevNnEsbUR82NAY0w= github.com/aws/aws-sdk-go-v2/service/iam v1.28.2 h1:lax3msAOJ99KL6k9YHehZPZqfxJ7+uFXvtpFWNBgPEo= github.com/aws/aws-sdk-go-v2/service/iam v1.28.2/go.mod h1:KJbw+8r7gZfjF+OewOVhyEQKiJXJ/OM1F1r3aAvKS9M= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.3 h1:e3PCNeEaev/ZF01cQyNZgmYE9oYYePIMJs2mWSKG514= @@ -98,6 +106,8 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.26.2 h1:fFrLsy08wEbAisqW3KDl/cPHrF43 github.com/aws/aws-sdk-go-v2/service/sts v1.26.2/go.mod h1:7Ld9eTqocTvJqqJ5K/orbSDwmGcpRdlDiLjz2DO+SL8= github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q= github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry/main.go b/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry/main.go new file mode 100644 index 000000000..6f26a6a36 --- /dev/null +++ b/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry/main.go @@ -0,0 +1,141 @@ +package eks + +import ( + "context" + _ "embed" + "fmt" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/eks" + "github.com/aws/aws-sdk-go-v2/service/eks/types" + "github.com/datadog/stratus-red-team/v2/pkg/stratus" + "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" + "log" +) + +//go:embed main.tf +var tf []byte + +// https://docs.aws.amazon.com/eks/latest/userguide/access-policies.html +const ClusterAccessPolicyName = "AmazonEKSClusterAdminPolicy" +const ClusterAccessPolicyARN = "arn:aws:eks::aws:cluster-access-policy/" + ClusterAccessPolicyName + +func init() { + const codeBlock = "```" + stratus.GetRegistry().RegisterAttackTechnique(&stratus.AttackTechnique{ + ID: "eks.lateral-movement.create-access-entry", + FriendlyName: "Create Admin EKS Access Entry", + + Description: ` +Uses the EKS Cluster Access Management to assign cluster administrator privileges to an IAM role. This allows the role to perform any action inside the Kubernetes cluster. + +Warm-up: + +- Create an IAM role + +Detonation: + +- Create an access entry for the IAM role +- Associate the access entry with the ` + ClusterAccessPolicyName + ` access policy + +References: + +- https://securitylabs.datadoghq.com/articles/eks-cluster-access-management-deep-dive/ +- https://docs.aws.amazon.com/eks/latest/userguide/access-entries.html +`, + Detection: ` +You can use the following CloudTrail events to identify when someone grants access to your EKS cluster: + +- **CreateAccessEntry**, when someone creates an access entry for a principal (meaning it's the first this principal is granted privileges in the cluster)': + +` + codeBlock + `json +{ + "eventSource": "eks.amazonaws.com", + "eventName": "CreateAccessEntry", + "requestParameters": { + "name": "eks-cluster", + "principalArn": "arn:aws:iam::012345678901:role/stratus-red-team-eks-create-access-entry-role" + }, + "responseElements": { + "accessEntry": { + "clusterName": "eks-cluster", + "type": "STANDARD", + "principalArn": "arn:aws:iam::012345678901:role/stratus-red-team-eks-create-access-entry-role", + } + } +} +` + codeBlock + ` + + +- **AssociateAccessPolicy**: when someone assigns an access policy to a principal + +` + codeBlock + `json +{ + "eventSource": "eks.amazonaws.com", + "eventName": "AssociateAccessPolicy", + "requestParameters": { + "policyArn": "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy", + "accessScope": { + "type": "cluster" + }, + "name": "eks-cluster", + "principalArn": "arn%3Aaws%3Aiam%3A%3A012345678901%3Arole%2Fstratus-red-team-eks-create-access-entry-role" + } +} +` + codeBlock + ` +`, + Platform: stratus.EKS, + PrerequisitesTerraformCode: tf, + IsIdempotent: false, + MitreAttackTactics: []mitreattack.Tactic{mitreattack.LateralMovement}, + Detonate: detonate, + Revert: revert, + }) +} + +func detonate(params map[string]string, providers stratus.CloudProviders) error { + //ec2instanceconnectClient := ec2instanceconnect.NewFromConfig(providers.AWS().GetConnection()) + eksProvider := providers.EKS() + eksClient := eks.NewFromConfig(eksProvider.GetAWSConnection()) + roleArn := params["role_arn"] + + log.Println("Using EKS cluster management API to assign administrator privileges to " + roleArn) + + _, err := eksClient.CreateAccessEntry(context.Background(), &eks.CreateAccessEntryInput{ + ClusterName: aws.String(eksProvider.GetEKSClusterName()), + PrincipalArn: &roleArn, + }) + if err != nil { + return fmt.Errorf("failed to create EKS access entry: %w", err) + } + log.Println("Successfully created EKS access entry for role", roleArn) + log.Println("This role is now full EKS cluster admin") + + _, err = eksClient.AssociateAccessPolicy(context.Background(), &eks.AssociateAccessPolicyInput{ + AccessScope: &types.AccessScope{Type: types.AccessScopeTypeCluster}, + ClusterName: aws.String(eksProvider.GetEKSClusterName()), + PolicyArn: aws.String(ClusterAccessPolicyARN), + PrincipalArn: &roleArn, + }) + if err != nil { + return fmt.Errorf("failed to associate EKS access policy to role: %w", err) + } + log.Println("Successfully associated EKS access policy", ClusterAccessPolicyName, "to role", roleArn) + return nil +} + +func revert(params map[string]string, providers stratus.CloudProviders) error { + eksProvider := providers.EKS() + eksClient := eks.NewFromConfig(eksProvider.GetAWSConnection()) + roleArn := params["role_arn"] + + _, err := eksClient.DeleteAccessEntry(context.Background(), &eks.DeleteAccessEntryInput{ + ClusterName: aws.String(eksProvider.GetEKSClusterName()), + PrincipalArn: &roleArn, + }) + if err != nil { + return fmt.Errorf("failed to delete EKS access entry: %w", err) + } + log.Println("Successfully deleted EKS access entry for role ", roleArn) + + return nil +} diff --git a/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry/main.tf b/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry/main.tf new file mode 100644 index 000000000..e73ccec24 --- /dev/null +++ b/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry/main.tf @@ -0,0 +1,48 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.0" + } + } +} +provider "aws" { + skip_region_validation = true + skip_credentials_validation = true + skip_get_ec2_platforms = true +} + +locals { + resource_prefix = "stratus-red-team-eks-create-access-entry" +} + +data "aws_caller_identity" "current" {} + +resource "aws_iam_role" "role" { + name = "${local.resource_prefix}-role" + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + AWS = data.aws_caller_identity.current.account_id + } + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "role-policy" { + role = aws_iam_role.role.name + policy_arn = "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess" +} + +output "role_arn" { + value = aws_iam_role.role.arn +} + +output "display" { + value = format("IAM role %s ready", aws_iam_role.role.arn) +} \ No newline at end of file diff --git a/v2/internal/attacktechniques/main.go b/v2/internal/attacktechniques/main.go index ff5a64246..e2a9e57cc 100644 --- a/v2/internal/attacktechniques/main.go +++ b/v2/internal/attacktechniques/main.go @@ -42,6 +42,7 @@ import ( _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-custom-script-extension" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/execution/vm-run-command" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/azure/exfiltration/disk-export" + _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/eks/lateral-movement/create-access-entry" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/share-compute-disk" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/share-compute-image" _ "github.com/datadog/stratus-red-team/v2/internal/attacktechniques/gcp/exfiltration/share-compute-snapshot" diff --git a/v2/internal/providers/eks.go b/v2/internal/providers/eks.go new file mode 100644 index 000000000..17fcd174c --- /dev/null +++ b/v2/internal/providers/eks.go @@ -0,0 +1,67 @@ +package providers + +import ( + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/google/uuid" + "log" + "net/url" + "os" + "strings" +) + +const EnvVarSkipEKSHostnameCheck = "STRATUS_SKIP_EKS_HOSTNAME_CHECK" + +type EKSProvider struct { + awsProvider *AWSProvider + K8sProvider *K8sProvider + UniqueCorrelationId uuid.UUID // unique value injected in the user-agent, to differentiate Stratus Red Team executions +} + +func NewEKSProvider(uuid uuid.UUID) *EKSProvider { + return &EKSProvider{ + awsProvider: NewAWSProvider(uuid), + K8sProvider: NewK8sProvider(uuid), + UniqueCorrelationId: uuid, + } +} + +func (m *EKSProvider) GetAWSConnection() aws.Config { + return m.awsProvider.GetConnection() +} + +func (m *EKSProvider) IsAuthenticatedAgainstEKS() bool { + // Check if we're properly authenticated against AWS + if !m.awsProvider.IsAuthenticatedAgainstAWS() { + return false + } + + // Check if our current K8s context is a valid EKS cluster + if os.Getenv(EnvVarSkipEKSHostnameCheck) == "1" { + return true + } + apiServerUrl := m.K8sProvider.GetRestConfig().Host + parsedAPIServerUrl, err := url.Parse(apiServerUrl) + if err != nil { + log.Fatalf("unable to parse API server URL %s: %v", apiServerUrl, err) + } + + return strings.HasSuffix(parsedAPIServerUrl.Host, ".eks.amazonaws.com") +} + +// taken from https://github.com/DataDog/managed-kubernetes-auditing-toolkit/blob/main/internal/utils/kubernetes.go#L59C1-L74C2 +func (m *EKSProvider) GetEKSClusterName() string { + // Most of (if not all) the time, the KubeConfig file generated by "aws eks update-kubeconfig" will have an + // ExecProvider section that runs "aws eks get-token <...> --cluster-name foo" + // We parse it and extract the cluster name from there + + execProvider := m.K8sProvider.GetRestConfig().ExecProvider + if execProvider == nil || execProvider.Command != "aws" { + return "" + } + for i, arg := range execProvider.Args { + if arg == "--cluster-name" && i+1 < len(execProvider.Args) { + return execProvider.Args[i+1] + } + } + return "" +} diff --git a/v2/pkg/stratus/platform.go b/v2/pkg/stratus/platform.go index 2ca1fa8b3..7d0ed2dad 100644 --- a/v2/pkg/stratus/platform.go +++ b/v2/pkg/stratus/platform.go @@ -11,6 +11,7 @@ type Platform string const ( AWS = "AWS" + EKS = "EKS" Kubernetes = "kubernetes" Azure = "azure" GCP = "GCP" @@ -26,6 +27,8 @@ func PlatformFromString(name string) (Platform, error) { return Azure, nil case strings.ToLower(GCP): return GCP, nil + case strings.ToLower(EKS): + return EKS, nil default: return "", errors.New("unknown platform: " + name) } @@ -41,6 +44,8 @@ func (p Platform) FormatName() (string, error) { return "GCP", nil case Kubernetes: return "Kubernetes", nil + case EKS: + return "EKS", nil default: return "", errors.New("platform name not formatted") } diff --git a/v2/pkg/stratus/providers.go b/v2/pkg/stratus/providers.go index d3b81fb2d..58a1801b4 100644 --- a/v2/pkg/stratus/providers.go +++ b/v2/pkg/stratus/providers.go @@ -13,6 +13,7 @@ type CloudProviders interface { K8s() *providers.K8sProvider Azure() *providers.AzureProvider GCP() *providers.GCPProvider + EKS() *providers.EKSProvider } type CloudProvidersImpl struct { @@ -21,6 +22,7 @@ type CloudProvidersImpl struct { K8sProvider *providers.K8sProvider AzureProvider *providers.AzureProvider GCPProvider *providers.GCPProvider + EKSProvider *providers.EKSProvider } func (m CloudProvidersImpl) AWS() *providers.AWSProvider { @@ -51,6 +53,13 @@ func (m CloudProvidersImpl) GCP() *providers.GCPProvider { return m.GCPProvider } +func (m CloudProvidersImpl) EKS() *providers.EKSProvider { + if m.EKSProvider == nil { + m.EKSProvider = providers.NewEKSProvider(m.UniqueCorrelationID) + } + return m.EKSProvider +} + // EnsureAuthenticated ensures that the current user is properly authenticated against a specific platform func EnsureAuthenticated(platform Platform) error { providerFactory := CloudProvidersImpl{UniqueCorrelationID: uuid.New()} @@ -79,6 +88,15 @@ func EnsureAuthenticated(platform Platform) error { "Make sure you are authenticated against GCP and you have set your GCP Project ID in your environment variables" + " (export GOOGLE_PROJECT=xxx)") } + case EKS: + if !providerFactory.EKS().IsAuthenticatedAgainstEKS() { + return errors.New("you are not authenticated against AWS or an EKS cluster.\n" + + "To use Stratus Red Team's EKS modules, you need to be authenticated both to an AWS account through the AWS CLI, and to an EKS cluster.\n\n" + + "Troubleshooting:\n" + + "1. Are you authenticated against AWS?\n" + + "2. Do you have a region or default region set (whether in your AWS configuration file or in your environment)? If not, run 'export AWS_REGION=xxx'\n" + + "3. Are you authenticated against an EKS cluster? If not, run 'aws eks update-kubeconfig --name '\n") + } default: return errors.New("unhandled platform " + string(platform)) }