Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions environment/computetype/compute_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ const (
// AKS is an Azure Kubernetes Service cluster authenticating to AWS via the projected
// service-account web-identity credential chain.
AKS ComputeType = "AKS"
// GCE is a non-AWS host authenticating to AWS via the GCP web-identity credential chain.
GCE ComputeType = "GCE"
// GKE is a Google Kubernetes Engine cluster authenticating to AWS via the projected
// service-account web-identity credential chain.
GKE ComputeType = "GKE"
)

var (
Expand All @@ -25,6 +30,8 @@ var (
"EKS": EKS,
"AZUREVM": AzureVM,
"AKS": AKS,
"GCE": GCE,
"GKE": GKE,
}
)

Expand Down
12 changes: 10 additions & 2 deletions environment/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type MetaData struct {
AzureVMSize string
AzureResourceGroup string
AzureLocation string
GKEClusterName string
ProxyUrl string
AssumeRoleArn string
InstanceArn string
Expand Down Expand Up @@ -96,6 +97,7 @@ type MetaDataStrings struct {
AzureVMSize string
AzureResourceGroup string
AzureLocation string
GKEClusterName string
ProxyUrl string
AssumeRoleArn string
InstanceArn string
Expand Down Expand Up @@ -129,7 +131,7 @@ type MetaDataStrings struct {
}

func registerComputeType(dataString *MetaDataStrings) {
flag.StringVar(&(dataString.ComputeType), "computeType", "", "EC2/ECS/EKS/AZUREVM/AKS")
flag.StringVar(&(dataString.ComputeType), "computeType", "", "EC2/ECS/EKS/AZUREVM/AKS/GCE/GKE")
}
func registerBucket(dataString *MetaDataStrings) {
flag.StringVar(&(dataString.Bucket), "bucket", "", "s3 bucket ex cloudwatch-agent-integration-bucket")
Expand Down Expand Up @@ -163,6 +165,10 @@ func registerAzureVMData(d *MetaDataStrings) {
flag.StringVar(&(d.AzureLocation), "azureLocation", "", "expected cloud.region resource attribute (Azure location, e.g. eastus)")
}

func registerGKEData(d *MetaDataStrings) {
flag.StringVar(&(d.GKEClusterName), "gkeClusterName", "", "GKE cluster name")
}

func registerEKSData(d *MetaDataStrings) {
flag.StringVar(&(d.EKSClusterName), "eksClusterName", "", "EKS cluster name")
flag.StringVar(&(d.EksDeploymentStrategy), "eksDeploymentStrategy", "", "Daemon/Replica/Sidecar")
Expand Down Expand Up @@ -208,7 +214,7 @@ func registerProxyUrl(dataString *MetaDataStrings) {
func fillComputeType(e *MetaData, data *MetaDataStrings) {
computeType, ok := computetype.FromString(data.ComputeType)
if !ok {
log.Panic("Invalid compute type. Needs to be EC2/ECS/EKS/AZUREVM/AKS. Compute Type is a required flag. :" + data.ComputeType)
log.Panic("Invalid compute type. Needs to be EC2/ECS/EKS/AZUREVM/AKS/GCE/GKE. Compute Type is a required flag. :" + data.ComputeType)
}
e.ComputeType = computeType
}
Expand Down Expand Up @@ -344,6 +350,7 @@ func RegisterEnvironmentMetaDataFlags() *MetaDataStrings {
registerEKSData(registeredMetaDataStrings)
registerAKSData(registeredMetaDataStrings)
registerAzureVMData(registeredMetaDataStrings)
registerGKEData(registeredMetaDataStrings)
registerEKSE2ETestData(registeredMetaDataStrings)
registerBucket(registeredMetaDataStrings)
registerS3Key(registeredMetaDataStrings)
Expand Down Expand Up @@ -387,6 +394,7 @@ func GetEnvironmentMetaData() *MetaData {
metaDataStorage.AzureVMSize = registeredMetaDataStrings.AzureVMSize
metaDataStorage.AzureResourceGroup = registeredMetaDataStrings.AzureResourceGroup
metaDataStorage.AzureLocation = registeredMetaDataStrings.AzureLocation
metaDataStorage.GKEClusterName = registeredMetaDataStrings.GKEClusterName
metaDataStorage.InstancePlatform = registeredMetaDataStrings.InstancePlatform
metaDataStorage.AgentStartCommand = registeredMetaDataStrings.AgentStartCommand
metaDataStorage.EksGpuType = registeredMetaDataStrings.EksGpuType
Expand Down
3 changes: 2 additions & 1 deletion terraform/azure/aks/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ resource "kubernetes_job_v1" "otlp_load" {
name = "load-gen"
image = "curlimages/curl:8.8.0"
command = ["/bin/sh", "-c"]
args = [templatefile("${path.module}/otlp_load_generator.sh", {
args = [templatefile("${path.module}/../../otlp_load_generator.sh", {
prefix = "aks"
service_name = local.load_gen_service_name
instance_id = azurerm_kubernetes_cluster.cwagent.name
endpoint = "http://127.0.0.1:4318"
Expand Down
119 changes: 119 additions & 0 deletions terraform/gcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# GCP integration tests — environment setup

One-time setup the GCP project and AWS account need before the `terraform/gcp`
modules can run. Everything else is created and destroyed per run by the
modules themselves.

The commands below use these placeholders:

```sh
PROJECT_ID=<project-id>
CI_SA=otel-collector-integ-tests@$PROJECT_ID.iam.gserviceaccount.com
```

## GCP project

Enable the APIs:

```sh
gcloud services enable compute.googleapis.com iam.googleapis.com \
container.googleapis.com --project "$PROJECT_ID"
```

An existing VPC network and subnetwork must be passed as `gcp_network_name` /
`gcp_subnetwork_name`. Prefer a dedicated network over the auto-created
`default` one: the default network comes with pre-populated firewall rules
(`default-allow-ssh` among them) that admit any source IP, which defeats the
modules' runner-IP-scoped SSH rule. A dedicated network with no extra rules
leaves the modules' own firewall rules as the only ingress:

```sh
gcloud compute networks create cwagent-integ --subnet-mode auto --project "$PROJECT_ID"
```

Create the CI service account and grant it — and any human running the suites
locally — these project roles:

| Role | Needed for |
|---|---|
| `roles/compute.admin` | VM and firewall lifecycle (`gce`) |
| `roles/iam.serviceAccountAdmin` | creating the per-run service account (`gce`) |
| `roles/iam.serviceAccountUser` | attaching service accounts to VMs and GKE nodes |
| `roles/container.admin` | cluster lifecycle and in-cluster RBAC objects (`gke`; project editor alone is not enough) |

```sh
gcloud iam service-accounts create otel-collector-integ-tests --project "$PROJECT_ID"

for role in roles/compute.admin roles/iam.serviceAccountAdmin \
roles/iam.serviceAccountUser roles/container.admin; do
gcloud projects add-iam-policy-binding "$PROJECT_ID" \
--member "serviceAccount:$CI_SA" --role "$role"
done
```

For a human, the same bindings with `--member "user:<user-email>"`.

## CI authentication

**Keyless (Workload Identity Federation)** — required when org policy disables
service-account key creation. The provider resource path and the
service-account email go in the workflow's auth step; the path embeds the
project number, so a project move means updating the workflow file as well.

```sh
gcloud iam workload-identity-pools create github-actions \
--project "$PROJECT_ID" --location global --display-name "GitHub Actions"

gcloud iam workload-identity-pools providers create-oidc github \
--project "$PROJECT_ID" --location global \
--workload-identity-pool github-actions --display-name "GitHub" \
--issuer-uri "https://token.actions.githubusercontent.com" \
--attribute-mapping "google.subject=assertion.sub,attribute.repository=assertion.repository" \
--attribute-condition "assertion.repository == 'aws/amazon-cloudwatch-agent'"

PROJECT_NUMBER=$(gcloud projects describe "$PROJECT_ID" --format "value(projectNumber)")

gcloud iam service-accounts add-iam-policy-binding "$CI_SA" \
--project "$PROJECT_ID" --role roles/iam.workloadIdentityUser \
--member "principalSet://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/github-actions/attribute.repository/aws/amazon-cloudwatch-agent"
```

**Static key** — where key creation is allowed, the simpler alternative:

```sh
gcloud iam service-accounts keys create key.json --iam-account "$CI_SA"
gh secret set GCP_CREDENTIALS < key.json
```

## GitHub repository configuration

| Setting | Value |
|---|---|
| `vars.GCP_PROJECT` | project ID (not the display name or number) |
| `vars.GCP_NETWORK_NAME` | VPC network name |
| `secrets.GCP_CREDENTIALS` | service-account key JSON (static-key model only) |

```sh
gh variable set GCP_PROJECT --body "$PROJECT_ID"
gh variable set GCP_NETWORK_NAME --body default
```

## AWS account

Enable Transaction Search in the suite region (us-east-2): trace validation
reads the `aws/spans` log group, which only exists where the account's X-Ray
trace destination is CloudWatch Logs. See the `region` variable comment in
`gce/variables.tf`.

## Local runs

- Google credentials come from application-default credentials
(`gcloud auth application-default login`; on Workspace-managed accounts that
reject it with `admin_policy_enforced`, use `gcloud auth login --update-adc`).
- `gce`: a locally built agent `.deb` (`agent_deb_path`) and a test-repo clone
URL/branch reachable from the VM (`github_test_repo`,
`github_test_repo_branch`).
- `gke`: `kubectl` on PATH, and an agent container image in an ECR repository
your AWS credentials can access (`cwagent_image_repo`, `cwagent_image_tag`,
`ecr_region`). The agent repo's `make docker-build-amd64 IMAGE=...` target
builds a suitable image from local binaries.
93 changes: 93 additions & 0 deletions terraform/gcp/gce/iam.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: MIT

# CWAGENT_ROLE: assumed via web identity (GCP service-account identity token). Carries the agent's
# default:otel CloudWatch writes plus the reads the on-VM test needs to assert delivery.

module "common" {
source = "../../common"
}

# Per-run service account: the identity the VM mints tokens as. It holds no GCP permissions: it
# exists only so the metadata server mints identity tokens for it, and its unique ID is what the
# role trust pins. account_id caps at 30 chars, hence the short prefix.
resource "google_service_account" "cwagent" {
account_id = "cwa-gce-${module.common.testing_id}"
display_name = "cwa-gce-integ-${module.common.testing_id}"
}

data "aws_iam_policy_document" "cwagent_assume_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]

# Google is a built-in web-identity provider, so no IAM OIDC provider resource is involved:
# the principal is accounts.google.com itself.
principals {
type = "Federated"
identifiers = ["accounts.google.com"]
}

# Pin all three Google condition keys -- the recommended trust policy for Google-issued tokens
# and the same shape the onboarding scripts create. :sub is the service account's unique ID,
# :aud reads the token's azp claim (the unique ID again on service-account tokens), and :oaud
# is the audience the token was requested with, rejecting tokens minted for other services.
# https://aws.amazon.com/blogs/security/access-aws-using-a-google-cloud-platform-native-workload-identity/
condition {
test = "StringEquals"
variable = "accounts.google.com:aud"
values = [google_service_account.cwagent.unique_id]
}

condition {
test = "StringEquals"
variable = "accounts.google.com:sub"
values = [google_service_account.cwagent.unique_id]
}

condition {
test = "StringEquals"
variable = "accounts.google.com:oaud"
values = [var.gcp_token_audience]
}
}
}

resource "aws_iam_role" "cwagent" {
name = "cwa-gce-integ-role-${module.common.testing_id}"
assume_role_policy = data.aws_iam_policy_document.cwagent_assume_role.json
}

# The agent's own writes come from the same AWS-managed policy customers are told to use, so a green run
# also proves that documented policy is sufficient over the GCP web-identity path.
resource "aws_iam_role_policy_attachment" "cwagent_server_policy" {
role = aws_iam_role.cwagent.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy"
}

# Validation reads only -- the agent's own writes are fully covered by CloudWatchAgentServerPolicy. The
# test binary runs on the VM under this same role, so these have to live here.
data "aws_iam_policy_document" "cwagent_permissions" {
statement {
effect = "Allow"
actions = [
"cloudwatch:ListMetrics",
"cloudwatch:GetMetricData",
"logs:GetLogEvents",
# Cleanup: the test deletes its own stream from the shared /aws/cwagent/otlp group when it finishes.
"logs:DeleteLogStream",
# StartQuery/GetQueryResults validate OTLP trace delivery via the aws/spans log group. That group
# is only populated where the X-Ray trace segment destination is set to CloudWatchLogs, which is a
# per-region setting -- hence the region default in variables.tf.
"logs:StartQuery",
"logs:GetQueryResults",
]
resources = ["*"]
}
}

resource "aws_iam_role_policy" "cwagent" {
name = "cwa-gce-integ-policy-${module.common.testing_id}"
role = aws_iam_role.cwagent.id
policy = data.aws_iam_policy_document.cwagent_permissions.json
}
Loading
Loading