From 4f908424cd07cbcf62d0d2c806f47927f24e0bc0 Mon Sep 17 00:00:00 2001 From: Christophe Tafani-Dereeper Date: Thu, 26 Jan 2023 16:07:05 +0100 Subject: [PATCH] Properly encapsulate providers to allow for different detonation UUIDs --- examples/custom/detonate_custom_technique.go | 4 +- .../ec2-get-password-data/main.go | 4 +- .../ec2-steal-instance-credentials/main.go | 3 +- .../secretsmanager-retrieve-secrets/main.go | 3 +- .../main.go | 3 +- .../defense-evasion/cloudtrail-delete/main.go | 3 +- .../cloudtrail-event-selectors/main.go | 5 +- .../cloudtrail-lifecycle-rule/main.go | 5 +- .../defense-evasion/cloudtrail-stop/main.go | 5 +- .../organizations-leave/main.go | 3 +- .../vpc-remove-flow-logs/main.go | 3 +- .../ec2-enumerate-from-instance/main.go | 3 +- .../aws/discovery/ec2-get-user-data/main.go | 3 +- .../ec2-launch-unusual-instances/main.go | 3 +- .../aws/execution/ec2-user-data/main.go | 13 ++--- .../main.go | 5 +- .../aws/exfiltration/ec2-share-ami/main.go | 5 +- .../ec2-share-ebs-snapshot/main.go | 5 +- .../exfiltration/rds-share-snapshot/main.go | 5 +- .../s3-backdoor-bucket-policy/main.go | 5 +- .../console-login-without-mfa/main.go | 10 ++-- .../aws/persistence/iam-backdoor-role/main.go | 14 ++--- .../aws/persistence/iam-backdoor-user/main.go | 5 +- .../persistence/iam-create-admin-user/main.go | 5 +- .../iam-create-user-login-profile/main.go | 5 +- .../lambda-backdoor-function/main.go | 5 +- .../persistence/lambda-overwrite-code/main.go | 5 +- .../rolesanywhere-create-trust-anchor/main.go | 5 +- .../vm-custom-script-extension/main.go | 5 +- .../azure/execution/vm-run-command/main.go | 3 +- .../azure/exfiltration/disk-export/main.go | 15 +++--- .../create-admin-service-account/main.go | 28 +++++----- .../create-service-account-key/main.go | 5 +- .../impersonate-service-accounts/main.go | 3 +- .../credential-access/dump-secrets/main.go | 3 +- .../steal-serviceaccount-token/main.go | 3 +- .../create-admin-clusterrole/main.go | 11 ++-- .../create-client-certificate/main.go | 3 +- .../k8s/persistence/create-token/main.go | 3 +- .../hostpath-volume/main.go | 5 +- .../privilege-escalation/nodes-proxy/main.go | 14 ++--- .../privileged-pod/main.go | 5 +- v2/internal/providers/aws.go | 27 +++------- v2/internal/providers/azure.go | 44 +++++++-------- v2/internal/providers/gcp.go | 25 ++++----- v2/internal/providers/kubernetes.go | 38 ++++++------- v2/internal/providers/main.go | 8 ++- v2/internal/state/mocks/FileSystem.go | 2 +- v2/internal/state/mocks/StateManager.go | 2 +- v2/internal/state/state_test.go | 2 +- v2/pkg/stratus/attack_technique.go | 4 +- v2/pkg/stratus/providers.go | 54 ++++++++++++++++--- .../stratus/runner/mocks/TerraformManager.go | 2 +- v2/pkg/stratus/runner/runner.go | 36 ++++++++----- v2/pkg/stratus/runner/runner_test.go | 8 +-- v2/pkg/stratus/runner/terraform.go | 7 +-- 56 files changed, 247 insertions(+), 260 deletions(-) diff --git a/examples/custom/detonate_custom_technique.go b/examples/custom/detonate_custom_technique.go index 2b6bb650..839ff718 100644 --- a/examples/custom/detonate_custom_technique.go +++ b/examples/custom/detonate_custom_technique.go @@ -31,9 +31,9 @@ func buildCustomAttackTechnique() *stratus.AttackTechnique { } } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { iamUserName := params["iam_user_name"] - iamClient := iam.NewFromConfig(stratus.AWSProvider().GetConnection()) + iamClient := iam.NewFromConfig(providers.AWS().GetConnection()) userResponse, err := iamClient.GetUser(context.Background(), &iam.GetUserInput{ UserName: &iamUserName, diff --git a/v2/internal/attacktechniques/aws/credential-access/ec2-get-password-data/main.go b/v2/internal/attacktechniques/aws/credential-access/ec2-get-password-data/main.go index a2b14491..30e9db53 100644 --- a/v2/internal/attacktechniques/aws/credential-access/ec2-get-password-data/main.go +++ b/v2/internal/attacktechniques/aws/credential-access/ec2-get-password-data/main.go @@ -8,7 +8,6 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials/stscreds" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/sts" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/internal/utils" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" @@ -49,8 +48,9 @@ Detonation: const numCalls = 30 -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { roleArn := params["role_arn"] + providers.AWS().GetConnection() awsConnection := providers.AWS().GetConnection() stsClient := sts.NewFromConfig(awsConnection) diff --git a/v2/internal/attacktechniques/aws/credential-access/ec2-steal-instance-credentials/main.go b/v2/internal/attacktechniques/aws/credential-access/ec2-steal-instance-credentials/main.go index 834d7718..f0f7bc09 100644 --- a/v2/internal/attacktechniques/aws/credential-access/ec2-steal-instance-credentials/main.go +++ b/v2/internal/attacktechniques/aws/credential-access/ec2-steal-instance-credentials/main.go @@ -10,7 +10,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ssm" "github.com/aws/aws-sdk-go-v2/service/ssm/types" "github.com/aws/aws-sdk-go-v2/service/sts" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/internal/utils" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" @@ -57,7 +56,7 @@ See also: [Known detection bypasses](https://hackingthe.cloud/aws/avoiding-detec }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { ssmClient := ssm.NewFromConfig(providers.AWS().GetConnection()) instanceId := params["instance_id"] instanceRoleName := params["instance_role_name"] diff --git a/v2/internal/attacktechniques/aws/credential-access/secretsmanager-retrieve-secrets/main.go b/v2/internal/attacktechniques/aws/credential-access/secretsmanager-retrieve-secrets/main.go index 4df5ec06..f9eed702 100644 --- a/v2/internal/attacktechniques/aws/credential-access/secretsmanager-retrieve-secrets/main.go +++ b/v2/internal/attacktechniques/aws/credential-access/secretsmanager-retrieve-secrets/main.go @@ -7,7 +7,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/secretsmanager" "github.com/aws/aws-sdk-go-v2/service/secretsmanager/types" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -47,7 +46,7 @@ The following may be use to tune the detection, or validate findings: }) } -func detonate(map[string]string) error { +func detonate(_ map[string]string, providers stratus.CloudProviders) error { secretsManagerClient := secretsmanager.NewFromConfig(providers.AWS().GetConnection()) secretsResponse, err := secretsManagerClient.ListSecrets(context.Background(), &secretsmanager.ListSecretsInput{ diff --git a/v2/internal/attacktechniques/aws/credential-access/ssm-retrieve-securestring-parameters/main.go b/v2/internal/attacktechniques/aws/credential-access/ssm-retrieve-securestring-parameters/main.go index 72bcb79b..e6f29341 100644 --- a/v2/internal/attacktechniques/aws/credential-access/ssm-retrieve-securestring-parameters/main.go +++ b/v2/internal/attacktechniques/aws/credential-access/ssm-retrieve-securestring-parameters/main.go @@ -6,7 +6,6 @@ import ( "errors" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssm" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -52,7 +51,7 @@ The following may be use to tune the detection, or validate findings: }) } -func detonate(map[string]string) error { +func detonate(_ map[string]string, providers stratus.CloudProviders) error { ssmClient := ssm.NewFromConfig(providers.AWS().GetConnection()) log.Println("Running ssm:DescribeParameters and ssm:GetParameters by batch of 10 to find all SSM Parameters in the current region") diff --git a/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-delete/main.go b/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-delete/main.go index a8b385cc..e2a2e6ee 100644 --- a/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-delete/main.go +++ b/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-delete/main.go @@ -5,7 +5,6 @@ import ( _ "embed" "errors" "github.com/aws/aws-sdk-go-v2/service/cloudtrail" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -42,7 +41,7 @@ GuardDuty also provides a dedicated finding type, [Stealth:IAMUser/CloudTrailLog }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { cloudtrailClient := cloudtrail.NewFromConfig(providers.AWS().GetConnection()) trailName := params["cloudtrail_trail_name"] diff --git a/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-event-selectors/main.go b/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-event-selectors/main.go index 64bd1c36..5c9d8aac 100644 --- a/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-event-selectors/main.go +++ b/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-event-selectors/main.go @@ -7,7 +7,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/cloudtrail" "github.com/aws/aws-sdk-go-v2/service/cloudtrail/types" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -45,7 +44,7 @@ Identify when event selectors of a CloudTrail trail are updated, through CloudTr }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { cloudtrailClient := cloudtrail.NewFromConfig(providers.AWS().GetConnection()) trailName := params["cloudtrail_trail_name"] @@ -72,7 +71,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { cloudtrailClient := cloudtrail.NewFromConfig(providers.AWS().GetConnection()) trailName := params["cloudtrail_trail_name"] diff --git a/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-lifecycle-rule/main.go b/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-lifecycle-rule/main.go index befef53e..3911f21d 100644 --- a/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-lifecycle-rule/main.go +++ b/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-lifecycle-rule/main.go @@ -7,7 +7,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -48,7 +47,7 @@ The CloudTrail event PutBucketLifecycle and its attribute }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { s3Client := s3.NewFromConfig(providers.AWS().GetConnection()) bucketName := params["s3_bucket_name"] @@ -75,7 +74,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { s3Client := s3.NewFromConfig(providers.AWS().GetConnection()) bucketName := params["s3_bucket_name"] diff --git a/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-stop/main.go b/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-stop/main.go index 0f70ab32..efd1e273 100644 --- a/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-stop/main.go +++ b/v2/internal/attacktechniques/aws/defense-evasion/cloudtrail-stop/main.go @@ -5,7 +5,6 @@ import ( _ "embed" "errors" "github.com/aws/aws-sdk-go-v2/service/cloudtrail" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -43,7 +42,7 @@ GuardDuty also provides a dedicated finding type, [Stealth:IAMUser/CloudTrailLog }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { cloudtrailClient := cloudtrail.NewFromConfig(providers.AWS().GetConnection()) trailName := params["cloudtrail_trail_name"] @@ -60,7 +59,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { cloudtrailClient := cloudtrail.NewFromConfig(providers.AWS().GetConnection()) trailName := params["cloudtrail_trail_name"] diff --git a/v2/internal/attacktechniques/aws/defense-evasion/organizations-leave/main.go b/v2/internal/attacktechniques/aws/defense-evasion/organizations-leave/main.go index 9f4b1e04..63dc3688 100644 --- a/v2/internal/attacktechniques/aws/defense-evasion/organizations-leave/main.go +++ b/v2/internal/attacktechniques/aws/defense-evasion/organizations-leave/main.go @@ -8,7 +8,6 @@ import ( "github.com/aws/aws-sdk-go-v2/credentials/stscreds" "github.com/aws/aws-sdk-go-v2/service/organizations" "github.com/aws/aws-sdk-go-v2/service/sts" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -48,7 +47,7 @@ Use the CloudTrail event LeaveOrganization.`, }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { roleArn := params["role_arn"] awsConnection := providers.AWS().GetConnection() diff --git a/v2/internal/attacktechniques/aws/defense-evasion/vpc-remove-flow-logs/main.go b/v2/internal/attacktechniques/aws/defense-evasion/vpc-remove-flow-logs/main.go index bc19a687..acd8e2fe 100644 --- a/v2/internal/attacktechniques/aws/defense-evasion/vpc-remove-flow-logs/main.go +++ b/v2/internal/attacktechniques/aws/defense-evasion/vpc-remove-flow-logs/main.go @@ -5,7 +5,6 @@ import ( _ "embed" "errors" "github.com/aws/aws-sdk-go-v2/service/ec2" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -43,7 +42,7 @@ only when DeleteFlowLogs is not closely followed by DeleteVpc }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { ec2Client := ec2.NewFromConfig(providers.AWS().GetConnection()) vpcId := params["vpc_id"] diff --git a/v2/internal/attacktechniques/aws/discovery/ec2-enumerate-from-instance/main.go b/v2/internal/attacktechniques/aws/discovery/ec2-enumerate-from-instance/main.go index 3e97ff1c..997eb7a1 100644 --- a/v2/internal/attacktechniques/aws/discovery/ec2-enumerate-from-instance/main.go +++ b/v2/internal/attacktechniques/aws/discovery/ec2-enumerate-from-instance/main.go @@ -6,7 +6,6 @@ import ( "errors" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ssm" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -63,7 +62,7 @@ arn:aws:sts::012345678901:assumed-role/my-instance-role/i-0adc17a5acb70d9ae }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { ssmClient := ssm.NewFromConfig(providers.AWS().GetConnection()) instanceId := params["instance_id"] commands := []string{ diff --git a/v2/internal/attacktechniques/aws/discovery/ec2-get-user-data/main.go b/v2/internal/attacktechniques/aws/discovery/ec2-get-user-data/main.go index 67cd64e2..23986957 100644 --- a/v2/internal/attacktechniques/aws/discovery/ec2-get-user-data/main.go +++ b/v2/internal/attacktechniques/aws/discovery/ec2-get-user-data/main.go @@ -3,7 +3,6 @@ package aws import ( "context" _ "embed" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/internal/utils" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" @@ -58,7 +57,7 @@ See: const numCalls = 15 -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { awsConnection := providers.AWS().GetConnection() stsClient := sts.NewFromConfig(awsConnection) diff --git a/v2/internal/attacktechniques/aws/execution/ec2-launch-unusual-instances/main.go b/v2/internal/attacktechniques/aws/execution/ec2-launch-unusual-instances/main.go index 0da72852..b1796ebd 100644 --- a/v2/internal/attacktechniques/aws/execution/ec2-launch-unusual-instances/main.go +++ b/v2/internal/attacktechniques/aws/execution/ec2-launch-unusual-instances/main.go @@ -9,7 +9,6 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/aws/aws-sdk-go-v2/service/sts" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -50,7 +49,7 @@ Depending on your account limits you might also see VcpuLimitExceededAuthorizeSecurityGroupIngress when }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { ec2Client := ec2.NewFromConfig(providers.AWS().GetConnection()) // Find the snapshot to exfiltrate @@ -69,7 +68,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { ec2Client := ec2.NewFromConfig(providers.AWS().GetConnection()) // Find the snapshot to exfiltrate diff --git a/v2/internal/attacktechniques/aws/exfiltration/ec2-share-ami/main.go b/v2/internal/attacktechniques/aws/exfiltration/ec2-share-ami/main.go index eb394856..998f975e 100644 --- a/v2/internal/attacktechniques/aws/exfiltration/ec2-share-ami/main.go +++ b/v2/internal/attacktechniques/aws/exfiltration/ec2-share-ami/main.go @@ -7,7 +7,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2/types" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/internal/utils" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" @@ -62,7 +61,7 @@ var amiPermissions = []types.LaunchPermission{ {UserId: aws.String("012345678901")}, } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { ec2Client := ec2.NewFromConfig(providers.AWS().GetConnection()) amiId := params["ami_id"] @@ -88,7 +87,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { ec2Client := ec2.NewFromConfig(providers.AWS().GetConnection()) amiId := params["ami_id"] diff --git a/v2/internal/attacktechniques/aws/exfiltration/ec2-share-ebs-snapshot/main.go b/v2/internal/attacktechniques/aws/exfiltration/ec2-share-ebs-snapshot/main.go index 157aee09..cb8fe46d 100644 --- a/v2/internal/attacktechniques/aws/exfiltration/ec2-share-ebs-snapshot/main.go +++ b/v2/internal/attacktechniques/aws/exfiltration/ec2-share-ebs-snapshot/main.go @@ -5,7 +5,6 @@ import ( _ "embed" "github.com/aws/aws-sdk-go-v2/service/ec2" "github.com/aws/aws-sdk-go-v2/service/ec2/types" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/internal/utils" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" @@ -86,7 +85,7 @@ In that case, userIdentity.accountId contains the attacker's accoun var ShareWithAccountId = "012345678912" -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { ec2Client := ec2.NewFromConfig(providers.AWS().GetConnection()) // Find the snapshot to exfiltrate @@ -113,7 +112,7 @@ func detonate(params map[string]string) error { return err } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { ec2Client := ec2.NewFromConfig(providers.AWS().GetConnection()) ourSnapshotId := params["snapshot_id"] diff --git a/v2/internal/attacktechniques/aws/exfiltration/rds-share-snapshot/main.go b/v2/internal/attacktechniques/aws/exfiltration/rds-share-snapshot/main.go index 4c2d16da..30777a8c 100644 --- a/v2/internal/attacktechniques/aws/exfiltration/rds-share-snapshot/main.go +++ b/v2/internal/attacktechniques/aws/exfiltration/rds-share-snapshot/main.go @@ -6,7 +6,6 @@ import ( "errors" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/rds" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -57,7 +56,7 @@ An attacker can also make an RDS snapshot completely public. In this case, the v var AccountIdToShareWith = []string{"193672423079"} -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { snapshotId := params["snapshot_id"] rdsClient := rds.NewFromConfig(providers.AWS().GetConnection()) @@ -75,7 +74,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { snapshotId := params["snapshot_id"] rdsClient := rds.NewFromConfig(providers.AWS().GetConnection()) diff --git a/v2/internal/attacktechniques/aws/exfiltration/s3-backdoor-bucket-policy/main.go b/v2/internal/attacktechniques/aws/exfiltration/s3-backdoor-bucket-policy/main.go index b6e8a250..1bb31bdd 100644 --- a/v2/internal/attacktechniques/aws/exfiltration/s3-backdoor-bucket-policy/main.go +++ b/v2/internal/attacktechniques/aws/exfiltration/s3-backdoor-bucket-policy/main.go @@ -5,7 +5,6 @@ import ( _ "embed" "fmt" "github.com/aws/aws-sdk-go-v2/service/s3" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -56,7 +55,7 @@ which generates a finding when an S3 bucket is made public or accessible from an }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { s3Client := s3.NewFromConfig(providers.AWS().GetConnection()) bucketName := params["bucket_name"] policy := fmt.Sprintf(backdooredPolicy, bucketName, bucketName) @@ -70,7 +69,7 @@ func detonate(params map[string]string) error { return err } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { s3Client := s3.NewFromConfig(providers.AWS().GetConnection()) bucketName := params["bucket_name"] diff --git a/v2/internal/attacktechniques/aws/initial-access/console-login-without-mfa/main.go b/v2/internal/attacktechniques/aws/initial-access/console-login-without-mfa/main.go index 15ec1de7..3ed3e43a 100644 --- a/v2/internal/attacktechniques/aws/initial-access/console-login-without-mfa/main.go +++ b/v2/internal/attacktechniques/aws/initial-access/console-login-without-mfa/main.go @@ -4,7 +4,7 @@ import ( _ "embed" "encoding/json" "errors" - "github.com/datadog/stratus-red-team/v2/internal/providers" + providersInternal "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "io" @@ -84,13 +84,13 @@ Sample CloudTrail event (redacted for clarity): }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { // The code to generate a 'ConsoleLogin' event programmatically was inspired from // https://naikordian.github.io/blog/posts/brute-force-aws-console/ // courtesy of Naikordian (naikordian@protonmail.com) // Build the HTTP request - request := buildHttpRequest(params) + request := buildHttpRequest(params, providers) log.Println("Performing a console login for user " + params["username"] + " in account " + params["account_id"]) // Perform the HTTP request @@ -116,7 +116,7 @@ func detonate(params map[string]string) error { } // buildHttpRequest builds the HTTP request to send to the AWS console sign-in endpoint -func buildHttpRequest(params map[string]string) *http.Request { +func buildHttpRequest(params map[string]string, providers stratus.CloudProviders) *http.Request { // https://naikordian.github.io/blog/posts/brute-force-aws-console/ postData := url.Values{ "action": {"iam-user-authentication"}, @@ -134,7 +134,7 @@ func buildHttpRequest(params map[string]string) *http.Request { // http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(proxyUrl), TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} req.Header.Add("Referer", "https://signin.aws.amazon.com") req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("User-Agent", providers.GetStratusUserAgent()) + req.Header.Set("User-Agent", providersInternal.GetStratusUserAgentForUUID(providers.AWS().UniqueCorrelationId)) return req } diff --git a/v2/internal/attacktechniques/aws/persistence/iam-backdoor-role/main.go b/v2/internal/attacktechniques/aws/persistence/iam-backdoor-role/main.go index ef48e2b7..1d4c9e8b 100644 --- a/v2/internal/attacktechniques/aws/persistence/iam-backdoor-role/main.go +++ b/v2/internal/attacktechniques/aws/persistence/iam-backdoor-role/main.go @@ -5,7 +5,6 @@ import ( _ "embed" "errors" "github.com/aws/aws-sdk-go-v2/service/iam" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -54,11 +53,12 @@ which generates a finding when a role can be assumed from a new AWS account or p }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { roleName := params["role_name"] + iamClient := iam.NewFromConfig(providers.AWS().GetConnection()) log.Println("Backdooring IAM role " + roleName + " by allowing sts:AssumeRole from an external AWS account") - err := updateAssumeRolePolicy(roleName, maliciousIamPolicy) + err := updateAssumeRolePolicy(iamClient, roleName, maliciousIamPolicy) if err != nil { return errors.New("unable to backdoor IAM role: " + err.Error()) } @@ -67,12 +67,13 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { roleName := params["role_name"] roleTrustPolicy := strings.ReplaceAll(params["role_trust_policy"], "\\", "") // Terraform output adds backslashes for some reason + iamClient := iam.NewFromConfig(providers.AWS().GetConnection()) log.Println("Reverting trust policy of IAM role " + roleName + " to its original state") - err := updateAssumeRolePolicy(roleName, roleTrustPolicy) + err := updateAssumeRolePolicy(iamClient, roleName, roleTrustPolicy) if err != nil { return errors.New("unable to backdoor IAM role: " + err.Error()) @@ -80,8 +81,7 @@ func revert(params map[string]string) error { return nil } -func updateAssumeRolePolicy(roleName string, roleTrustPolicy string) error { - iamClient := iam.NewFromConfig(providers.AWS().GetConnection()) +func updateAssumeRolePolicy(iamClient *iam.Client, roleName string, roleTrustPolicy string) error { _, err := iamClient.UpdateAssumeRolePolicy(context.Background(), &iam.UpdateAssumeRolePolicyInput{ RoleName: &roleName, PolicyDocument: &roleTrustPolicy, diff --git a/v2/internal/attacktechniques/aws/persistence/iam-backdoor-user/main.go b/v2/internal/attacktechniques/aws/persistence/iam-backdoor-user/main.go index 3d835c32..774b60dc 100644 --- a/v2/internal/attacktechniques/aws/persistence/iam-backdoor-user/main.go +++ b/v2/internal/attacktechniques/aws/persistence/iam-backdoor-user/main.go @@ -4,7 +4,6 @@ import ( "context" _ "embed" "github.com/aws/aws-sdk-go-v2/service/iam" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -41,7 +40,7 @@ correlated with other indicators. }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { iamClient := iam.NewFromConfig(providers.AWS().GetConnection()) userName := params["user_name"] @@ -57,7 +56,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { iamClient := iam.NewFromConfig(providers.AWS().GetConnection()) userName := params["user_name"] diff --git a/v2/internal/attacktechniques/aws/persistence/iam-create-admin-user/main.go b/v2/internal/attacktechniques/aws/persistence/iam-create-admin-user/main.go index d7564e61..6d58d183 100644 --- a/v2/internal/attacktechniques/aws/persistence/iam-create-admin-user/main.go +++ b/v2/internal/attacktechniques/aws/persistence/iam-create-admin-user/main.go @@ -7,7 +7,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/iam" "github.com/aws/aws-sdk-go-v2/service/iam/types" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -47,7 +46,7 @@ can help to craft more precise detections: }) } -func detonate(map[string]string) error { +func detonate(_ map[string]string, providers stratus.CloudProviders) error { iamClient := iam.NewFromConfig(providers.AWS().GetConnection()) log.Println("Creating a malicious IAM user") @@ -83,7 +82,7 @@ func detonate(map[string]string) error { return nil } -func revert(map[string]string) error { +func revert(_ map[string]string, providers stratus.CloudProviders) error { iamClient := iam.NewFromConfig(providers.AWS().GetConnection()) result, err := iamClient.ListAccessKeys(context.Background(), &iam.ListAccessKeysInput{ diff --git a/v2/internal/attacktechniques/aws/persistence/iam-create-user-login-profile/main.go b/v2/internal/attacktechniques/aws/persistence/iam-create-user-login-profile/main.go index ec40ef06..cd7182a4 100644 --- a/v2/internal/attacktechniques/aws/persistence/iam-create-user-login-profile/main.go +++ b/v2/internal/attacktechniques/aws/persistence/iam-create-user-login-profile/main.go @@ -5,7 +5,6 @@ import ( _ "embed" "errors" "github.com/aws/aws-sdk-go-v2/service/iam" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/internal/utils" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" @@ -45,7 +44,7 @@ In particular, it's suspicious when these events occur on IAM users intended to }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { iamClient := iam.NewFromConfig(providers.AWS().GetConnection()) userName := params["user_name"] password := utils.RandomString(16) + ".#1Aa" // extra characters to ensure we meet password requirements, no matter the password policy @@ -68,7 +67,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { iamClient := iam.NewFromConfig(providers.AWS().GetConnection()) userName := params["user_name"] diff --git a/v2/internal/attacktechniques/aws/persistence/lambda-backdoor-function/main.go b/v2/internal/attacktechniques/aws/persistence/lambda-backdoor-function/main.go index 8c5a3298..fba3a0c8 100644 --- a/v2/internal/attacktechniques/aws/persistence/lambda-backdoor-function/main.go +++ b/v2/internal/attacktechniques/aws/persistence/lambda-backdoor-function/main.go @@ -6,7 +6,6 @@ import ( "errors" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/lambda" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -47,7 +46,7 @@ public or accessible from another account. var policyStatementId = "backdoor" -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { lambdaClient := lambda.NewFromConfig(providers.AWS().GetConnection()) lambdaFunctionName := params["lambda_function_name"] @@ -68,7 +67,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { lambdaClient := lambda.NewFromConfig(providers.AWS().GetConnection()) lambdaFunctionName := params["lambda_function_name"] diff --git a/v2/internal/attacktechniques/aws/persistence/lambda-overwrite-code/main.go b/v2/internal/attacktechniques/aws/persistence/lambda-overwrite-code/main.go index 71dd61b1..104948a0 100644 --- a/v2/internal/attacktechniques/aws/persistence/lambda-overwrite-code/main.go +++ b/v2/internal/attacktechniques/aws/persistence/lambda-overwrite-code/main.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "github.com/aws/aws-sdk-go-v2/service/lambda" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "io/ioutil" @@ -50,7 +49,7 @@ Through CloudTrail's UpdateFunctionCode* event, e.g. UpdateFu }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { functionName := params["lambda_function_name"] lambdaClient := lambda.NewFromConfig(providers.AWS().GetConnection()) zip := "UEsDBAoDAAAAABGy0lRE4o1NOwAAADsAAAAJAAAAbGFtYmRhLnB5ZGVmIGxhbWJkYV9oYW5kbGVyKGUsIGMpOgogICAgcHJpbnQoIlN0cmF0dXMgc2F5cyBoZWxsbyEiKQpQSwECPwMKAwAAAAARstJUROKNTTsAAAA7AAAACQAkAAAAAAAAACCApIEAAAAAbGFtYmRhLnB5CgAgAAAAAAABABgAAL0yTlCD2AEA6mNPUIPYAQC9Mk5Qg9gBUEsFBgAAAAABAAEAWwAAAGIAAAAAAA==" @@ -76,7 +75,7 @@ func detonate(params map[string]string) error { } // revert to original unmodified lambda -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { functionName := params["lambda_function_name"] bucketName := params["bucket_name"] bucketKey := params["bucket_object_key"] diff --git a/v2/internal/attacktechniques/aws/persistence/rolesanywhere-create-trust-anchor/main.go b/v2/internal/attacktechniques/aws/persistence/rolesanywhere-create-trust-anchor/main.go index 509bac37..ccacea76 100644 --- a/v2/internal/attacktechniques/aws/persistence/rolesanywhere-create-trust-anchor/main.go +++ b/v2/internal/attacktechniques/aws/persistence/rolesanywhere-create-trust-anchor/main.go @@ -7,7 +7,6 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" "github.com/aws/aws-sdk-go-v2/service/rolesanywhere/types" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/internal/utils" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" @@ -68,7 +67,7 @@ Identify when a trust anchor is created, through CloudTrail's CreateTrustA }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { rolesAnywhereClient := rolesanywhere.NewFromConfig(providers.AWS().GetConnection()) roleArn := params["role_arn"] tags := []types.Tag{ @@ -114,7 +113,7 @@ func detonate(params map[string]string) error { return nil } -func revert(map[string]string) error { +func revert(_ map[string]string, providers stratus.CloudProviders) error { rolesanywhereClient := rolesanywhere.NewFromConfig(providers.AWS().GetConnection()) errTrustAnchor := removeTrustAnchor(rolesanywhereClient) diff --git a/v2/internal/attacktechniques/azure/execution/vm-custom-script-extension/main.go b/v2/internal/attacktechniques/azure/execution/vm-custom-script-extension/main.go index e985c018..21d53338 100644 --- a/v2/internal/attacktechniques/azure/execution/vm-custom-script-extension/main.go +++ b/v2/internal/attacktechniques/azure/execution/vm-custom-script-extension/main.go @@ -5,7 +5,6 @@ import ( _ "embed" "errors" "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -74,7 +73,7 @@ Identify Azure events of type Microsoft.Compute/virtualMachines/extensions const ExtensionName = "CustomScriptExtension-StratusRedTeam-Example" -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { vmName := params["vm_name"] resourceGroup := params["resource_group_name"] @@ -131,7 +130,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { vmName := params["vm_name"] resourceGroup := params["resource_group_name"] diff --git a/v2/internal/attacktechniques/azure/execution/vm-run-command/main.go b/v2/internal/attacktechniques/azure/execution/vm-run-command/main.go index 9b0ef300..bb6d4ce7 100644 --- a/v2/internal/attacktechniques/azure/execution/vm-run-command/main.go +++ b/v2/internal/attacktechniques/azure/execution/vm-run-command/main.go @@ -7,7 +7,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "log" @@ -91,7 +90,7 @@ Sample event (redacted for clarity): }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { vmObjectId := params["vm_instance_object_id"] vmName := params["vm_name"] resourceGroup := params["resource_group_name"] diff --git a/v2/internal/attacktechniques/azure/exfiltration/disk-export/main.go b/v2/internal/attacktechniques/azure/exfiltration/disk-export/main.go index 3544f87b..c454ad35 100644 --- a/v2/internal/attacktechniques/azure/exfiltration/disk-export/main.go +++ b/v2/internal/attacktechniques/azure/exfiltration/disk-export/main.go @@ -71,9 +71,9 @@ Sample event (redacted for clarity): }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { diskName := params["disk_name"] - disksClient, err := getAzureDisksClient() + disksClient, err := getAzureDisksClient(providers.Azure()) if err != nil { return errors.New("unable to instantiate Azure disks client: " + err.Error()) } @@ -99,9 +99,9 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { diskName := params["disk_name"] - disksClient, err := getAzureDisksClient() + disksClient, err := getAzureDisksClient(providers.Azure()) if err != nil { return errors.New("unable to instantiate Azure disks client: " + err.Error()) } @@ -122,9 +122,6 @@ func revert(params map[string]string) error { return nil } -func getAzureDisksClient() (*armcompute.DisksClient, error) { - cred := providers.Azure().GetCredentials() - subscriptionID := providers.Azure().SubscriptionID - clientOptions := providers.Azure().ClientOptions - return armcompute.NewDisksClient(subscriptionID, cred, clientOptions) +func getAzureDisksClient(azure *providers.AzureProvider) (*armcompute.DisksClient, error) { + return armcompute.NewDisksClient(azure.SubscriptionID, azure.GetCredentials(), azure.ClientOptions) } diff --git a/v2/internal/attacktechniques/gcp/persistence/create-admin-service-account/main.go b/v2/internal/attacktechniques/gcp/persistence/create-admin-service-account/main.go index 477acfbb..84d1becb 100644 --- a/v2/internal/attacktechniques/gcp/persistence/create-admin-service-account/main.go +++ b/v2/internal/attacktechniques/gcp/persistence/create-admin-service-account/main.go @@ -52,10 +52,10 @@ Using the following GCP Admin Activity audit logs events: // Note: `roles/owner` cannot be granted through the API const roleToGrant = "roles/owner" -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { gcp := providers.GCP() serviceAccountName := params["service_account_name"] - serviceAccountEmail := getServiceAccountEmail(serviceAccountName) + serviceAccountEmail := getServiceAccountEmail(serviceAccountName, gcp.ProjectId) if err := createServiceAccount(gcp, serviceAccountName); err != nil { return err @@ -69,13 +69,13 @@ func detonate(params map[string]string) error { } // createServiceAccount creates a new service account inside of a GCP project -func createServiceAccount(gcp *providers.GcpProvider, serviceAccountName string) error { +func createServiceAccount(gcp *providers.GCPProvider, serviceAccountName string) error { iamClient, err := iam.NewService(context.Background(), gcp.Options()) if err != nil { return errors.New("Error instantiating GCP IAM Client: " + err.Error()) } serviceAccountDisplayName := fmt.Sprintf("%s (service account used by stratus red team)", serviceAccountName) - serviceAccountEmail := getServiceAccountEmail(serviceAccountName) + serviceAccountEmail := getServiceAccountEmail(serviceAccountName, gcp.GetProjectId()) path := fmt.Sprintf("projects/%s", gcp.GetProjectId()) log.Println("Creating service account " + serviceAccountName) @@ -95,7 +95,7 @@ func createServiceAccount(gcp *providers.GcpProvider, serviceAccountName string) // * Step 1: Read the project's IAM policy using [getIamPolicy](https://cloud.google.com/resource-manager/reference/rest/v1/projects/getIamPolicy) // * Step 2: Create a binding, or add the service account to an existing binding for the role to grant // * Step 3: Update the project's IAM policy using [setIamPolicy](https://cloud.google.com/resource-manager/reference/rest/v1/projects/setIamPolicy) -func assignProjectRole(gcp *providers.GcpProvider, serviceAccountEmail string, roleToGrant string) error { +func assignProjectRole(gcp *providers.GCPProvider, serviceAccountEmail string, roleToGrant string) error { resourceManager, err := cloudresourcemanager.NewService(context.Background(), gcp.Options()) if err != nil { return errors.New("unable to instantiate the GCP cloud resource manager: " + err.Error()) @@ -132,10 +132,10 @@ func assignProjectRole(gcp *providers.GcpProvider, serviceAccountEmail string, r return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { gcp := providers.GCP() serviceAccountName := params["service_account_name"] - serviceAccountEmail := getServiceAccountEmail(serviceAccountName) + serviceAccountEmail := getServiceAccountEmail(serviceAccountName, gcp.ProjectId) // Attempt to remove the role from the service account in the project's IAM policy // fail with a warning (but continue) in case of error @@ -150,7 +150,7 @@ func revert(params map[string]string) error { // * Step 1: Read the project's IAM policy using [getIamPolicy](https://cloud.google.com/resource-manager/reference/rest/v1/projects/getIamPolicy) // * Step 2: Remove a binding, or remove the service account from an existing binding for the role to grant // * Step 3: Update the project's IAM policy using [setIamPolicy](https://cloud.google.com/resource-manager/reference/rest/v1/projects/setIamPolicy) -func unassignProjectRole(gcp *providers.GcpProvider, serviceAccountEmail string, roleToGrant string) { +func unassignProjectRole(gcp *providers.GCPProvider, serviceAccountEmail string, roleToGrant string) { resourceManager, err := cloudresourcemanager.NewService(context.Background(), gcp.Options()) if err != nil { log.Println("Warning: unable to instantiate the GCP cloud resource manager: " + err.Error()) @@ -187,14 +187,14 @@ func unassignProjectRole(gcp *providers.GcpProvider, serviceAccountEmail string, } -func removeServiceAccount(gcp *providers.GcpProvider, serviceAccountName string) error { +func removeServiceAccount(gcp *providers.GCPProvider, serviceAccountName string) error { iamClient, err := iam.NewService(context.Background(), gcp.Options()) if err != nil { return errors.New("Error instantiating GCP IAM Client: " + err.Error()) } log.Println("Removing service account " + serviceAccountName) - _, err = iamClient.Projects.ServiceAccounts.Delete(getServiceAccountPath(serviceAccountName)).Do() + _, err = iamClient.Projects.ServiceAccounts.Delete(getServiceAccountPath(serviceAccountName, gcp.GetProjectId())).Do() if err != nil { return errors.New("Unable to delete service account: " + err.Error()) } @@ -203,12 +203,12 @@ func removeServiceAccount(gcp *providers.GcpProvider, serviceAccountName string) // Utility functions -func getServiceAccountPath(name string) string { - return fmt.Sprintf("projects/-/serviceAccounts/%s", getServiceAccountEmail(name)) +func getServiceAccountPath(name string, projectId string) string { + return fmt.Sprintf("projects/-/serviceAccounts/%s", getServiceAccountEmail(name, projectId)) } -func getServiceAccountEmail(name string) string { - return fmt.Sprintf("%s@%s.iam.gserviceaccount.com", name, providers.GCP().GetProjectId()) +func getServiceAccountEmail(name string, projectId string) string { + return fmt.Sprintf("%s@%s.iam.gserviceaccount.com", name, projectId) } func remove(slice []string, index int) []string { diff --git a/v2/internal/attacktechniques/gcp/persistence/create-service-account-key/main.go b/v2/internal/attacktechniques/gcp/persistence/create-service-account-key/main.go index c063f171..74ee8640 100644 --- a/v2/internal/attacktechniques/gcp/persistence/create-service-account-key/main.go +++ b/v2/internal/attacktechniques/gcp/persistence/create-service-account-key/main.go @@ -8,7 +8,6 @@ import ( "encoding/base64" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" iam "google.golang.org/api/iam/v1" @@ -49,7 +48,7 @@ Using GCP Admin Activity audit logs event google.iam.admin.v1.CreateServic }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { saEmail := params["sa_email"] ctx := context.Background() service, err := iam.NewService(ctx, providers.GCP().Options()) @@ -72,7 +71,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { saEmail := params["sa_email"] resource := "projects/-/serviceAccounts/" + saEmail diff --git a/v2/internal/attacktechniques/gcp/privilege-escalation/impersonate-service-accounts/main.go b/v2/internal/attacktechniques/gcp/privilege-escalation/impersonate-service-accounts/main.go index 147d313f..b6bf452e 100644 --- a/v2/internal/attacktechniques/gcp/privilege-escalation/impersonate-service-accounts/main.go +++ b/v2/internal/attacktechniques/gcp/privilege-escalation/impersonate-service-accounts/main.go @@ -4,7 +4,6 @@ import ( "context" _ "embed" "fmt" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "google.golang.org/api/iamcredentials/v1" @@ -157,7 +156,7 @@ short amount of time (successfully or not) }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { serviceAccountEmails := strings.Split(params["service_account_emails"], ",") numServiceAccounts := len(serviceAccountEmails) diff --git a/v2/internal/attacktechniques/k8s/credential-access/dump-secrets/main.go b/v2/internal/attacktechniques/k8s/credential-access/dump-secrets/main.go index 896030fd..2b9697b1 100644 --- a/v2/internal/attacktechniques/k8s/credential-access/dump-secrets/main.go +++ b/v2/internal/attacktechniques/k8s/credential-access/dump-secrets/main.go @@ -4,7 +4,6 @@ import ( "context" _ "embed" "errors" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -76,7 +75,7 @@ Some built-in Kubernetes components might need to be excluded from such a detect }) } -func detonate(map[string]string) error { +func detonate(_ map[string]string, providers stratus.CloudProviders) error { client := providers.K8s().GetClient() log.Println("Attempting to dump secrets in all namespaces") diff --git a/v2/internal/attacktechniques/k8s/credential-access/steal-serviceaccount-token/main.go b/v2/internal/attacktechniques/k8s/credential-access/steal-serviceaccount-token/main.go index 7ae6ecbc..83c408f7 100644 --- a/v2/internal/attacktechniques/k8s/credential-access/steal-serviceaccount-token/main.go +++ b/v2/internal/attacktechniques/k8s/credential-access/steal-serviceaccount-token/main.go @@ -3,7 +3,6 @@ package kubernetes import ( _ "embed" "errors" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" "github.com/golang-jwt/jwt" @@ -83,7 +82,7 @@ Sample event (shortened): }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { config := providers.K8s().GetRestConfig() client := providers.K8s().GetClient() namespace := params["namespace"] diff --git a/v2/internal/attacktechniques/k8s/persistence/create-admin-clusterrole/main.go b/v2/internal/attacktechniques/k8s/persistence/create-admin-clusterrole/main.go index 70ac3c35..befcb4bb 100644 --- a/v2/internal/attacktechniques/k8s/persistence/create-admin-clusterrole/main.go +++ b/v2/internal/attacktechniques/k8s/persistence/create-admin-clusterrole/main.go @@ -5,13 +5,13 @@ import ( _ "embed" "errors" "github.com/aws/smithy-go/ptr" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/kubernetes" "log" "time" ) @@ -61,7 +61,7 @@ var clusterRoleBinding = &rbacv1.ClusterRoleBinding{ RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", Name: clusterRole.Name}, } -func detonate(map[string]string) error { +func detonate(_ map[string]string, providers stratus.CloudProviders) error { client := providers.K8s().GetClient() ctx := context.Background() @@ -89,7 +89,7 @@ func detonate(map[string]string) error { // see https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/#token-controller var secretName string err = wait.PollImmediate(1*time.Second, 1*time.Minute, func() (done bool, err error) { - name, err := getServiceAccountSecretName() + name, err := getServiceAccountSecretName(client) secretName = name return name != "", err }) @@ -109,8 +109,7 @@ func detonate(map[string]string) error { } // Returns the name of the K8s secret containing the long-lived service account token -func getServiceAccountSecretName() (string, error) { - client := providers.K8s().GetClient() +func getServiceAccountSecretName(client *kubernetes.Clientset) (string, error) { serviceAccount, err := client.CoreV1().ServiceAccounts(namespace).Get(context.Background(), serviceAccount.Name, metav1.GetOptions{}) if err != nil { return "", err @@ -121,7 +120,7 @@ func getServiceAccountSecretName() (string, error) { return "", nil } -func revert(map[string]string) error { +func revert(_ map[string]string, providers stratus.CloudProviders) error { client := providers.K8s().GetClient() roleName := clusterRole.Name deleteOpts := metav1.DeleteOptions{GracePeriodSeconds: ptr.Int64(0)} diff --git a/v2/internal/attacktechniques/k8s/persistence/create-client-certificate/main.go b/v2/internal/attacktechniques/k8s/persistence/create-client-certificate/main.go index 2c8b3352..cc4d1353 100644 --- a/v2/internal/attacktechniques/k8s/persistence/create-client-certificate/main.go +++ b/v2/internal/attacktechniques/k8s/persistence/create-client-certificate/main.go @@ -13,7 +13,6 @@ import ( "log" "time" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" certificates "k8s.io/api/certificates/v1" @@ -59,7 +58,7 @@ not relate to standard cluster operation (e.g. Kubelet certificate issuance). const csrName = "stratus-red-team-csr" const commonName = "system:kube-controller-manager" -func detonate(map[string]string) error { +func detonate(_ map[string]string, providers stratus.CloudProviders) error { client := providers.K8s().GetClient() key, err := rsa.GenerateKey(rand.Reader, 1024) diff --git a/v2/internal/attacktechniques/k8s/persistence/create-token/main.go b/v2/internal/attacktechniques/k8s/persistence/create-token/main.go index 13bb68be..bd04b95b 100644 --- a/v2/internal/attacktechniques/k8s/persistence/create-token/main.go +++ b/v2/internal/attacktechniques/k8s/persistence/create-token/main.go @@ -5,7 +5,6 @@ import ( _ "embed" "errors" "github.com/aws/smithy-go/ptr" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" authenticationv1 "k8s.io/api/authentication/v1" @@ -81,7 +80,7 @@ var params = authenticationv1.TokenRequest{ }, } -func detonate(map[string]string) error { +func detonate(_ map[string]string, providers stratus.CloudProviders) error { client := providers.K8s().GetClient() ctx := context.Background() diff --git a/v2/internal/attacktechniques/k8s/privilege-escalation/hostpath-volume/main.go b/v2/internal/attacktechniques/k8s/privilege-escalation/hostpath-volume/main.go index e93215cc..9ae05b26 100644 --- a/v2/internal/attacktechniques/k8s/privilege-escalation/hostpath-volume/main.go +++ b/v2/internal/attacktechniques/k8s/privilege-escalation/hostpath-volume/main.go @@ -4,7 +4,6 @@ import ( "context" "errors" "github.com/aws/smithy-go/ptr" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" v1 "k8s.io/api/core/v1" @@ -48,7 +47,7 @@ Detonation: }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { client := providers.K8s().GetClient() namespace := params["namespace"] podSpec := nodeRootPodSpec(namespace) @@ -63,7 +62,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { client := providers.K8s().GetClient() namespace := params["namespace"] podSpec := nodeRootPodSpec(namespace) diff --git a/v2/internal/attacktechniques/k8s/privilege-escalation/nodes-proxy/main.go b/v2/internal/attacktechniques/k8s/privilege-escalation/nodes-proxy/main.go index 2b90dcaa..521acb13 100644 --- a/v2/internal/attacktechniques/k8s/privilege-escalation/nodes-proxy/main.go +++ b/v2/internal/attacktechniques/k8s/privilege-escalation/nodes-proxy/main.go @@ -92,8 +92,9 @@ See [kubeletctl](https://github.com/cyberark/kubeletctl/blob/master/pkg/api/cons }) } -func detonate(params map[string]string) error { - client := providers.K8s().GetClient() +func detonate(params map[string]string, providers stratus.CloudProviders) error { + k8s := providers.K8s() + client := k8s.GetClient() serviceAccountName := params["service_account_name"] serviceAccountNamespace := params["service_account_namespace"] @@ -112,7 +113,7 @@ func detonate(params map[string]string) error { // Step 3: Proxy the request to the Kubelet through this node log.Println("Using worker node '" + node + "' to proxy to the Kubelet API") - _, err = proxyKubeletRequest("/runningpods/", authenticationToken, node, client) + _, err = proxyKubeletRequest(k8s, "/runningpods/", authenticationToken, node, client) if err != nil { return err } @@ -144,20 +145,19 @@ func getRandomNodeName(client *kubernetes.Clientset) (string, error) { // Uses the nodes proxy API to proxy a request through a node to hit the Kubelet // see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.22/#-strong-proxy-operations-node-v1-core-strong- -func proxyKubeletRequest(kubeletApiPath string, token string, node string, client *kubernetes.Clientset) (string, error) { +func proxyKubeletRequest(k8s *providers.K8sProvider, kubeletApiPath string, token string, node string, client *kubernetes.Clientset) (string, error) { // Note: We have to use a raw HTTP request because it's not straightforward to create a new K8s API client from // a static bearer token - config := providers.K8s().GetRestConfig() httpClient := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, } - apiServerUrl := fmt.Sprintf("%s/%s", config.Host, config.APIPath) + apiServerUrl := fmt.Sprintf("%s/%s", k8s.RestConfig.Host, k8s.RestConfig.APIPath) endpointUrl := fmt.Sprintf("%sapi/v1/nodes/%s/proxy%s", apiServerUrl, node, kubeletApiPath) req, _ := http.NewRequest("GET", endpointUrl, nil) req.Header.Set("Authorization", "Bearer "+token) - req.Header.Set("User-Agent", providers.StratusUserAgent) + req.Header.Set("User-Agent", providers.GetStratusUserAgentForUUID(k8s.UniqueCorrelationId)) log.Println("Performing request to " + endpointUrl) response, err := httpClient.Do(req) diff --git a/v2/internal/attacktechniques/k8s/privilege-escalation/privileged-pod/main.go b/v2/internal/attacktechniques/k8s/privilege-escalation/privileged-pod/main.go index 38a063ee..656c66f0 100644 --- a/v2/internal/attacktechniques/k8s/privilege-escalation/privileged-pod/main.go +++ b/v2/internal/attacktechniques/k8s/privilege-escalation/privileged-pod/main.go @@ -4,7 +4,6 @@ import ( "context" "errors" "github.com/aws/smithy-go/ptr" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/pkg/stratus" "github.com/datadog/stratus-red-team/v2/pkg/stratus/mitreattack" v1 "k8s.io/api/core/v1" @@ -88,7 +87,7 @@ Sample event (shortened): }) } -func detonate(params map[string]string) error { +func detonate(params map[string]string, providers stratus.CloudProviders) error { client := providers.K8s().GetClient() namespace := params["namespace"] podSpec := podSpec(namespace) @@ -103,7 +102,7 @@ func detonate(params map[string]string) error { return nil } -func revert(params map[string]string) error { +func revert(params map[string]string, providers stratus.CloudProviders) error { client := providers.K8s().GetClient() namespace := params["namespace"] podSpec := podSpec(namespace) diff --git a/v2/internal/providers/aws.go b/v2/internal/providers/aws.go index 2d36823a..004f60c5 100644 --- a/v2/internal/providers/aws.go +++ b/v2/internal/providers/aws.go @@ -12,34 +12,23 @@ import ( "log" ) -var awsProvider = AWSProvider{ - UniqueCorrelationId: UniqueExecutionId, -} - -func AWS() *AWSProvider { - return &awsProvider -} - type AWSProvider struct { awsConfig *aws.Config UniqueCorrelationId uuid.UUID // unique value injected in the user-agent, to differentiate Stratus Red Team executions } -func (m *AWSProvider) GetConnection() aws.Config { - if m.awsConfig == nil { - cfg, err := config.LoadDefaultConfig(context.Background(), customUserAgentApiOptions(m.UniqueCorrelationId)) - if err != nil { - log.Fatalf("unable to load AWS configuration, %v", err) - } - m.awsConfig = &cfg +func NewAWSProvider(uuid uuid.UUID) *AWSProvider { + cfg, err := config.LoadDefaultConfig(context.Background(), customUserAgentApiOptions(uuid)) + if err != nil { + log.Fatalf("unable to load AWS configuration, %v", err) } - + return &AWSProvider{UniqueCorrelationId: uuid, awsConfig: &cfg} +} +func (m *AWSProvider) GetConnection() aws.Config { return *m.awsConfig } func (m *AWSProvider) IsAuthenticatedAgainstAWS() bool { - m.GetConnection() - // We make a sample API call to AWS to ensure the user is authenticated // Note: We use ec2:DescribeAccountAttributes as an arbitrary API call // instead of sts:GetCallerIdentity, to ensure an AWS region was properly set @@ -68,7 +57,7 @@ func customUserAgentMiddleware(uniqueId uuid.UUID) middleware.BuildMiddleware { if !ok { return out, metadata, fmt.Errorf("unknown transport type %T", input.Request) } - request.Header.Set("User-Agent", GetStratusUserAgent()) + request.Header.Set("User-Agent", GetStratusUserAgentForUUID(uniqueId)) return next.HandleBuild(ctx, input) }) diff --git a/v2/internal/providers/azure.go b/v2/internal/providers/azure.go index d202f21c..2b4f839d 100644 --- a/v2/internal/providers/azure.go +++ b/v2/internal/providers/azure.go @@ -21,41 +21,35 @@ type AzureProvider struct { UniqueCorrelationId uuid.UUID // unique value injected in the user-agent, to differentiate Stratus Red Team executions } -var DefaultClientOptions = arm.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - Telemetry: policy.TelemetryOptions{ApplicationID: UniqueExecutionId.String(), Disabled: false}, - }, -} - -var azureProvider = AzureProvider{ - UniqueCorrelationId: UniqueExecutionId, - SubscriptionID: os.Getenv(azureSubscriptionIdEnvVarKey), - ClientOptions: &DefaultClientOptions, -} - -func Azure() *AzureProvider { - return &azureProvider -} - -func (m *AzureProvider) GetCredentials() *azidentity.DefaultAzureCredential { - - if len(m.SubscriptionID) == 0 { +func NewAzureProvider(uuid uuid.UUID) *AzureProvider { + subscriptionID := os.Getenv(azureSubscriptionIdEnvVarKey) + if len(subscriptionID) == 0 { log.Fatal(azureSubscriptionIdEnvVarKey + " is not set.") } - - cred, err := azidentity.NewDefaultAzureCredential(nil) + creds, err := azidentity.NewDefaultAzureCredential(nil) if err != nil { log.Fatalf("failed to pull the result: %v", err) } - m.Credentials = cred + var DefaultClientOptions = arm.ClientOptions{ + ClientOptions: azcore.ClientOptions{ + Telemetry: policy.TelemetryOptions{ApplicationID: uuid.String(), Disabled: false}, + }, + } + return &AzureProvider{ + Credentials: creds, + ClientOptions: &DefaultClientOptions, + SubscriptionID: subscriptionID, + UniqueCorrelationId: uuid, + } +} + +func (m *AzureProvider) GetCredentials() *azidentity.DefaultAzureCredential { return m.Credentials } func (m *AzureProvider) IsAuthenticatedAgainstAzure() bool { - - cred := m.GetCredentials() - _, err := armresources.NewClient(m.SubscriptionID, cred, nil) + _, err := armresources.NewClient(m.SubscriptionID, m.Credentials, nil) return err == nil } diff --git a/v2/internal/providers/gcp.go b/v2/internal/providers/gcp.go index 2a9e6430..76c12f0b 100644 --- a/v2/internal/providers/gcp.go +++ b/v2/internal/providers/gcp.go @@ -27,30 +27,27 @@ func getProjectId() string { return "" } -type GcpProvider struct { +type GCPProvider struct { UniqueCorrelationId uuid.UUID ProjectId string } -var gcpProvider = GcpProvider{ - UniqueCorrelationId: UniqueExecutionId, - ProjectId: getProjectId(), -} - -func GCP() *GcpProvider { - return &gcpProvider +func NewGCPProvider(uuid uuid.UUID) *GCPProvider { + return &GCPProvider{ + UniqueCorrelationId: uuid, + ProjectId: getProjectId(), + } } -func (m *GcpProvider) Options() option.ClientOption { - return option.WithUserAgent(GetStratusUserAgent()) +func (m *GCPProvider) Options() option.ClientOption { + return option.WithUserAgent(GetStratusUserAgentForUUID(m.UniqueCorrelationId)) } -func (m *GcpProvider) IsAuthenticated() bool { - ctx := context.Background() - _, err := iam.NewService(ctx) +func (m *GCPProvider) IsAuthenticated() bool { + _, err := iam.NewService(context.Background()) return err == nil && m.ProjectId != "" } -func (m *GcpProvider) GetProjectId() string { +func (m *GCPProvider) GetProjectId() string { return m.ProjectId } diff --git a/v2/internal/providers/kubernetes.go b/v2/internal/providers/kubernetes.go index 5fef0ead..a47da4b0 100644 --- a/v2/internal/providers/kubernetes.go +++ b/v2/internal/providers/kubernetes.go @@ -26,13 +26,30 @@ type K8sProvider struct { } var ( - k8sProvider = K8sProvider{UniqueCorrelationId: UniqueExecutionId} kubeConfigPath string kubeConfigPathWasResolved bool ) -func K8s() *K8sProvider { - return &k8sProvider +func NewK8sProvider(uuid uuid.UUID) *K8sProvider { + kubeconfig := GetKubeConfigPath() + + // Will default to an in-cluster client config if kubeconfig path is not set + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + log.Fatalf("unable to build kube config: %v", err) + } + restConfig := config + restConfig.UserAgent = GetStratusUserAgentForUUID(uuid) + k8sClient, err := kubernetes.NewForConfig(restConfig) + if err != nil { + log.Fatalf("unable to create kube client: %v", err) + } + + return &K8sProvider{ + UniqueCorrelationId: uuid, + RestConfig: restConfig, + k8sClient: k8sClient, + } } // GetKubeConfigPath returns the path of the kubeconfig, with the following priority: @@ -68,19 +85,6 @@ func getKubeConfigPath() string { // GetClient is used to authenticate with Kubernetes and build the client from a kubeconfig func (m *K8sProvider) GetClient() *kubernetes.Clientset { - kubeconfig := GetKubeConfigPath() - - // Will default to an in-cluster client config if kubeconfig path is not set - config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) - if err != nil { - log.Fatalf("unable to build kube config: %v", err) - } - m.RestConfig = config - m.RestConfig.UserAgent = GetStratusUserAgent() - m.k8sClient, err = kubernetes.NewForConfig(m.RestConfig) - if err != nil { - log.Fatalf("unable to create kube client: %v", err) - } return m.k8sClient } @@ -89,8 +93,6 @@ func (m *K8sProvider) GetRestConfig() *rest.Config { } func (m *K8sProvider) IsAuthenticated() bool { - m.GetClient() - // We assume if the current user can do 'kubectl list pods' in the default namespace, they are authenticated // Note: we do not perform authorization checks var self = authv1.SelfSubjectAccessReview{ diff --git a/v2/internal/providers/main.go b/v2/internal/providers/main.go index 9249dcde..c568c3c0 100644 --- a/v2/internal/providers/main.go +++ b/v2/internal/providers/main.go @@ -5,10 +5,8 @@ import ( "github.com/google/uuid" ) -const StratusUserAgent = "stratus-red-team" +const StratusUserAgentPrefix = "stratus-red-team" -var UniqueExecutionId = uuid.New() - -func GetStratusUserAgent() string { - return fmt.Sprintf("%s_%s", StratusUserAgent, UniqueExecutionId) +func GetStratusUserAgentForUUID(uuid uuid.UUID) string { + return fmt.Sprintf("%s_%s", StratusUserAgentPrefix, uuid.String()) } diff --git a/v2/internal/state/mocks/FileSystem.go b/v2/internal/state/mocks/FileSystem.go index 2fef0deb..a3885d82 100644 --- a/v2/internal/state/mocks/FileSystem.go +++ b/v2/internal/state/mocks/FileSystem.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.16.0. DO NOT EDIT. package mocks diff --git a/v2/internal/state/mocks/StateManager.go b/v2/internal/state/mocks/StateManager.go index b05ab329..2d9241a5 100644 --- a/v2/internal/state/mocks/StateManager.go +++ b/v2/internal/state/mocks/StateManager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.16.0. DO NOT EDIT. package mocks diff --git a/v2/internal/state/state_test.go b/v2/internal/state/state_test.go index 42515c8d..96c75e59 100644 --- a/v2/internal/state/state_test.go +++ b/v2/internal/state/state_test.go @@ -8,7 +8,7 @@ import ( "testing" ) -func noop(map[string]string) error { +func noop(map[string]string, stratus.CloudProviders) error { return nil } diff --git a/v2/pkg/stratus/attack_technique.go b/v2/pkg/stratus/attack_technique.go index f8454870..69167893 100644 --- a/v2/pkg/stratus/attack_technique.go +++ b/v2/pkg/stratus/attack_technique.go @@ -32,13 +32,13 @@ type AttackTechnique struct { // Detonation function // Parameters are the Terraform outputs - Detonate func(params map[string]string) error `yaml:"-"` + Detonate func(params map[string]string, providerFactory CloudProviders) error `yaml:"-"` // Indicates if the detonation function is idempotent, i.e. if it can be run multiple times without reverting it IsIdempotent bool `yaml:"isIdempotent"` // Reversion function, to revert the side effects of a detonation - Revert func(params map[string]string) error `yaml:"-"` + Revert func(params map[string]string, providerFactory CloudProviders) error `yaml:"-"` } func (m AttackTechnique) String() string { diff --git a/v2/pkg/stratus/providers.go b/v2/pkg/stratus/providers.go index 4d425597..ec2409e8 100644 --- a/v2/pkg/stratus/providers.go +++ b/v2/pkg/stratus/providers.go @@ -2,40 +2,78 @@ package stratus import ( "errors" + "github.com/google/uuid" "github.com/datadog/stratus-red-team/v2/internal/providers" ) -func AWSProvider() *providers.AWSProvider { - return providers.AWS() +// CloudProviders provides a unified interface to access the various cloud providers SDKs +type CloudProviders interface { + AWS() *providers.AWSProvider + K8s() *providers.K8sProvider + Azure() *providers.AzureProvider + GCP() *providers.GCPProvider } -func K8sProvider() *providers.K8sProvider { - return providers.K8s() +type CloudProvidersImpl struct { + UniqueCorrelationID uuid.UUID + AWSProvider *providers.AWSProvider + K8sProvider *providers.K8sProvider + AzureProvider *providers.AzureProvider + GCPProvider *providers.GCPProvider +} + +func (m CloudProvidersImpl) AWS() *providers.AWSProvider { + if m.AWSProvider == nil { + m.AWSProvider = providers.NewAWSProvider(m.UniqueCorrelationID) + } + return m.AWSProvider +} + +func (m CloudProvidersImpl) K8s() *providers.K8sProvider { + if m.K8sProvider == nil { + m.K8sProvider = providers.NewK8sProvider(m.UniqueCorrelationID) + } + return m.K8sProvider +} + +func (m CloudProvidersImpl) Azure() *providers.AzureProvider { + if m.AzureProvider == nil { + m.AzureProvider = providers.NewAzureProvider(m.UniqueCorrelationID) + } + return m.AzureProvider +} + +func (m CloudProvidersImpl) GCP() *providers.GCPProvider { + if m.GCPProvider == nil { + m.GCPProvider = providers.NewGCPProvider(m.UniqueCorrelationID) + } + return m.GCPProvider } // EnsureAuthenticated ensures that the current user is properly authenticated against a specific platform func EnsureAuthenticated(platform Platform) error { + providerFactory := CloudProvidersImpl{UniqueCorrelationID: uuid.New()} switch platform { case AWS: - if !providers.AWS().IsAuthenticatedAgainstAWS() { + if !providerFactory.AWS().IsAuthenticatedAgainstAWS() { return errors.New("you are not authenticated against AWS, or you have not set your region. " + "Make sure you are authenticated against AWS, and you have a default region set in your AWS config " + "or environment (export AWS_DEFAULT_REGION=us-east-1)") } case Azure: - if !providers.Azure().IsAuthenticatedAgainstAzure() { + if !providerFactory.Azure().IsAuthenticatedAgainstAzure() { return errors.New("you are not authenticated against Azure, or you have not set your subscription. " + "Make sure you are authenticated against Azure and you have your Azure subscription ID set in your environment" + " (export AZURE_SUBSCRIPTION_ID=xxx)") } case Kubernetes: - if !providers.K8s().IsAuthenticated() { + if !providerFactory.K8s().IsAuthenticated() { return errors.New("You do not have a kubeconfig set up, or you do not have proper permissions for " + "this cluster. Make sure you have proper credentials set in " + providers.GetKubeConfigPath()) } case GCP: - if !providers.GCP().IsAuthenticated() { + if !providerFactory.GCP().IsAuthenticated() { return errors.New("you are not authenticated against GCP, or you have not set your project. " + "Make sure you are authenticated against GCP and you have set your GCP Project ID in your environment variables" + " (export GOOGLE_PROJECT=xxx)") diff --git a/v2/pkg/stratus/runner/mocks/TerraformManager.go b/v2/pkg/stratus/runner/mocks/TerraformManager.go index 16ec1885..324c48d9 100644 --- a/v2/pkg/stratus/runner/mocks/TerraformManager.go +++ b/v2/pkg/stratus/runner/mocks/TerraformManager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.14.0. DO NOT EDIT. +// Code generated by mockery v2.16.0. DO NOT EDIT. package mocks diff --git a/v2/pkg/stratus/runner/runner.go b/v2/pkg/stratus/runner/runner.go index 5a752016..d1a4afe9 100644 --- a/v2/pkg/stratus/runner/runner.go +++ b/v2/pkg/stratus/runner/runner.go @@ -5,6 +5,7 @@ import ( "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/internal/state" "github.com/datadog/stratus-red-team/v2/pkg/stratus" + "github.com/google/uuid" "log" "path/filepath" "strings" @@ -14,21 +15,27 @@ const StratusRunnerForce = true const StratusRunnerNoForce = false type Runner struct { - Technique *stratus.AttackTechnique - TechniqueState stratus.AttackTechniqueState - TerraformDir string - ShouldForce bool - TerraformManager TerraformManager - StateManager state.StateManager + Technique *stratus.AttackTechnique + TechniqueState stratus.AttackTechniqueState + TerraformDir string + ShouldForce bool + TerraformManager TerraformManager + StateManager state.StateManager + ProviderFactory stratus.CloudProviders + UniqueCorrelationID uuid.UUID } func NewRunner(technique *stratus.AttackTechnique, force bool) Runner { stateManager := state.NewFileSystemStateManager(technique) + uuid := uuid.New() runner := Runner{ - Technique: technique, - ShouldForce: force, - TerraformManager: NewTerraformManager(filepath.Join(stateManager.GetRootDirectory(), "terraform")), - StateManager: stateManager, + Technique: technique, + ShouldForce: force, + StateManager: stateManager, + UniqueCorrelationID: uuid, + TerraformManager: NewTerraformManager( + filepath.Join(stateManager.GetRootDirectory(), "terraform"), providers.GetStratusUserAgentForUUID(uuid), + ), } runner.initialize() @@ -41,6 +48,7 @@ func (m *Runner) initialize() { if m.TechniqueState == "" { m.TechniqueState = stratus.AttackTechniqueStatusCold } + m.ProviderFactory = stratus.CloudProvidersImpl{UniqueCorrelationID: m.UniqueCorrelationID} } func (m *Runner) WarmUp() (map[string]string, error) { @@ -119,7 +127,7 @@ func (m *Runner) Detonate() error { } // Detonate - err = m.Technique.Detonate(outputs) + err = m.Technique.Detonate(outputs, m.ProviderFactory) if err != nil { return errors.New("Error while detonating attack technique " + m.Technique.ID + ": " + err.Error()) } @@ -140,7 +148,7 @@ func (m *Runner) Revert() error { log.Println("Reverting detonation of technique " + m.Technique.ID) if m.Technique.Revert != nil { - err = m.Technique.Revert(outputs) + err = m.Technique.Revert(outputs, m.ProviderFactory) if err != nil { return errors.New("unable to revert detonation of " + m.Technique.ID + ": " + err.Error()) } @@ -203,9 +211,9 @@ func (m *Runner) setState(state stratus.AttackTechniqueState) { m.TechniqueState = state } -// GetUniqueExecutionId returns an unique execution ID, unique per run of Stratus Red Team (not for each TTP detonated) +// GetUniqueExecutionId returns an unique execution ID, unique for each runner instance func (m *Runner) GetUniqueExecutionId() string { - return providers.UniqueExecutionId.String() + return m.UniqueCorrelationID.String() } // Utility function to display better error messages than the Terraform ones diff --git a/v2/pkg/stratus/runner/runner_test.go b/v2/pkg/stratus/runner/runner_test.go index 39c2c020..2adc8d23 100644 --- a/v2/pkg/stratus/runner/runner_test.go +++ b/v2/pkg/stratus/runner/runner_test.go @@ -200,7 +200,7 @@ func TestRunnerDetonate(t *testing.T) { runner := Runner{ Technique: &stratus.AttackTechnique{ ID: "sample-technique", - Detonate: func(map[string]string) error { + Detonate: func(map[string]string, stratus.CloudProviders) error { wasDetonated = true return nil }, @@ -289,8 +289,8 @@ func TestRunnerRevert(t *testing.T) { runner := Runner{ Technique: &stratus.AttackTechnique{ ID: "foo", - Detonate: func(map[string]string) error { return nil }, - Revert: func(params map[string]string) error { + Detonate: func(map[string]string, stratus.CloudProviders) error { return nil }, + Revert: func(params map[string]string, factory stratus.CloudProviders) error { wasReverted = true return nil }, @@ -428,7 +428,7 @@ func TestRunnerCleanup(t *testing.T) { terraform.On("TerraformDestroy", mock.Anything).Return(nil) } if scenario[i].RevertFails { - scenario[i].Technique.Revert = func(map[string]string) error { + scenario[i].Technique.Revert = func(map[string]string, stratus.CloudProviders) error { return errors.New("nope") } } diff --git a/v2/pkg/stratus/runner/terraform.go b/v2/pkg/stratus/runner/terraform.go index e06f4646..e3935a1d 100644 --- a/v2/pkg/stratus/runner/terraform.go +++ b/v2/pkg/stratus/runner/terraform.go @@ -3,7 +3,6 @@ package runner import ( "context" "errors" - "github.com/datadog/stratus-red-team/v2/internal/providers" "github.com/datadog/stratus-red-team/v2/internal/utils" "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/product" @@ -26,12 +25,14 @@ type TerraformManager interface { type TerraformManagerImpl struct { terraformBinaryPath string terraformVersion string + terraformUserAgent string } -func NewTerraformManager(terraformBinaryPath string) TerraformManager { +func NewTerraformManager(terraformBinaryPath string, userAgent string) TerraformManager { manager := TerraformManagerImpl{ terraformVersion: TerraformVersion, terraformBinaryPath: terraformBinaryPath, + terraformUserAgent: userAgent, } manager.Initialize() return &manager @@ -60,7 +61,7 @@ func (m *TerraformManagerImpl) TerraformInitAndApply(directory string) (map[stri return map[string]string{}, errors.New("unable to instantiate Terraform: " + err.Error()) } - err = terraform.SetAppendUserAgent(providers.GetStratusUserAgent()) + err = terraform.SetAppendUserAgent(m.terraformUserAgent) if err != nil { return map[string]string{}, errors.New("unable to configure Terraform: " + err.Error()) }