Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
47cf40d
init
amirbenun Dec 18, 2025
11ab07e
improve exmpale
amirbenun Dec 18, 2025
a2179fe
seup
amirbenun Dec 18, 2025
7db0820
gem
amirbenun Dec 18, 2025
4915931
gem2
amirbenun Dec 18, 2025
16aa11f
deploysh
amirbenun Dec 18, 2025
4557a0f
bc
amirbenun Dec 18, 2025
cd4af80
rename
amirbenun Dec 19, 2025
a77e7d7
chmod
amirbenun Dec 19, 2025
66e3b4f
STACK_VERSION
amirbenun Dec 19, 2025
5b16816
test git
amirbenun Dec 22, 2025
1fb41f4
amirbenun
amirbenun Dec 22, 2025
4837aea
remove ssh
amirbenun Dec 22, 2025
82569f7
suffix
amirbenun Dec 22, 2025
7513705
enhance
amirbenun Dec 22, 2025
07eb90c
vars
amirbenun Dec 22, 2025
fab8da3
no test
amirbenun Dec 22, 2025
32ae443
elastic-agent-gcp
amirbenun Dec 22, 2025
4aa2e57
server
amirbenun Dec 22, 2025
246e20f
guest atr
amirbenun Dec 22, 2025
b48d1ee
ns
amirbenun Dec 22, 2025
83d12b0
validation
amirbenun Dec 22, 2025
100a557
rm
amirbenun Dec 22, 2025
89371c6
report_failure
amirbenun Dec 22, 2025
fd3b32b
proj id in validator
amirbenun Dec 22, 2025
039bdab
validate curl
amirbenun Dec 23, 2025
b438e77
google_client_config
amirbenun Dec 23, 2025
41d980b
gcloud examples
amirbenun Dec 23, 2025
6741f9f
space
amirbenun Dec 23, 2025
a6bee5a
script.sh
amirbenun Dec 23, 2025
526a049
envar
amirbenun Dec 23, 2025
9af94a2
debug instructions
amirbenun Dec 23, 2025
7dba993
permissions:
amirbenun Dec 23, 2025
787ad6e
"
amirbenun Dec 23, 2025
e962d00
shellcheck
amirbenun Dec 23, 2025
3c7563f
shfmt
amirbenun Dec 24, 2025
e1ba7e6
setup
amirbenun Dec 24, 2025
89bef81
enhance
amirbenun Dec 24, 2025
9f41dfe
review
amirbenun Dec 24, 2025
28f8eaa
always create sa
amirbenun Dec 29, 2025
fc6b840
links
amirbenun Jan 8, 2026
10aaf6f
remove key
amirbenun Jan 8, 2026
27cd509
vars
amirbenun Jan 8, 2026
d815693
naming
amirbenun Jan 8, 2026
9ef21a4
naming
amirbenun Jan 8, 2026
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
105 changes: 105 additions & 0 deletions deploy/infrastructure-manager/gcp-elastic-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
## Elastic Agent Infrastructure Manager (Terraform)

Deploy Elastic Agent for CIS GCP integration using GCP Infrastructure Manager. Creates a compute instance with Elastic Agent pre-installed and configured with necessary permissions.

### Prerequisites

1. Elastic Stack with Fleet Server deployed
2. GCP project with required permissions (Editor, IAM Admin)
3. Fleet URL and enrollment token from Kibana

### Quick Deploy

#### Option 1: Cloud Shell (Recommended)

[![Open in Cloud Shell](https://gstatic.com/cloudssh/images/open-btn.svg)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https://github.com/elastic/cloudbeat.git&cloudshell_workspace=deploy/infrastructure-manager&show=terminal&ephemeral=true)

```bash
# Enable required APIs
gcloud services enable iam.googleapis.com config.googleapis.com compute.googleapis.com \
cloudresourcemanager.googleapis.com cloudasset.googleapis.com

# Set variables from current session
export PROJECT_ID=$(gcloud config get-value project)
export ZONE=$(gcloud config get-value compute/zone 2>/dev/null || echo "us-central1-a")
export LOCATION=$(echo $ZONE | sed 's/-[a-z]$//') # Extract region from zone (e.g., us-central1-a -> us-central1)

# Deploy
gcloud infra-manager deployments apply elastic-agent-cspm \
--location=${LOCATION} \
--service-account="projects/${PROJECT_ID}/serviceAccounts/$(gcloud projects describe ${PROJECT_ID} --format='value(projectNumber)')@cloudservices.gserviceaccount.com" \
--git-source-repo="https://github.com/elastic/cloudbeat.git" \
--git-source-directory="deploy/infrastructure-manager" \
--git-source-ref="main" \
--input-values="\
project_id=${PROJECT_ID},\
deployment_name=elastic-agent-cspm,\
zone=${ZONE},\
fleet_url=YOUR_FLEET_URL,\
enrollment_token=YOUR_TOKEN,\
elastic_agent_version=8.19.0,\
scope=projects,\
parent_id=${PROJECT_ID},\
allow_ssh=false"
```

Replace `YOUR_FLEET_URL` and `YOUR_TOKEN` with your actual values.

For **organization-level** deployment, use `scope=organizations,parent_id=YOUR_ORG_ID`.

#### Option 2: GCP Console

1. Go to [Infrastructure Manager Console](https://console.cloud.google.com/infrastructure-manager/deployments/create)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
1. Go to [Infrastructure Manager Console](https://console.cloud.google.com/infrastructure-manager/deployments/create)
1. Go to [Infrastructure Manager Console](https://console.cloud.google.com/infra-manager/deployments/create)

2. Configure:
- **Source**: Git repository
- **Repository URL**: `https://github.com/elastic/cloudbeat.git`
- **Branch**: `main`
- **Directory**: `deploy/infrastructure-manager`
- **Location**: `us-central1`
3. Add input variables (see table below)
4. Click **Create**

### Input Variables
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Infrastructure manager requires the values to be in JSON format. Can you clarify in the example how to pass it?

Image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a strange behavior on their UI but string values should be wrapped with ""


| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `project_id` | Yes | - | GCP Project ID |
| `deployment_name` | Yes | - | Deployment name |
| `fleet_url` | Yes | - | Fleet Server URL |
| `enrollment_token` | Yes | - | Enrollment token (sensitive) |
| `elastic_agent_version` | Yes | - | Agent version (e.g., `8.15.0`) |
| `zone` | No | `us-central1-a` | GCP zone |
| `scope` | No | `projects` | `projects` or `organizations` |
| `parent_id` | Yes | - | Project ID or Organization ID |
| `service_account_name` | No | `""` | Existing SA (creates new if empty) |
| `allow_ssh` | No | `false` | Enable SSH firewall rule |

### Resources Created

- Compute instance (Ubuntu, n2-standard-4, 32GB disk)
- Service account with `cloudasset.viewer` and `browser` roles
- VPC network with auto-created subnets
- IAM bindings (project or organization level)
- Optional: SSH firewall rule

### Management

**View deployment:**
```bash
gcloud infra-manager deployments describe elastic-agent-cspm --location=us-central1
```

**Delete deployment:**
```bash
gcloud infra-manager deployments delete elastic-agent-cspm --location=us-central1
```

### Troubleshooting

**Check agent logs:**
```bash
gcloud compute ssh elastic-agent-cspm --zone us-central1-a
sudo journalctl -u google-startup-scripts.service
```

**Console:** [Infrastructure Manager Deployments](https://console.cloud.google.com/infrastructure-manager/deployments)
144 changes: 144 additions & 0 deletions deploy/infrastructure-manager/gcp-elastic-agent/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
terraform {
required_version = ">= 1.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}

provider "google" {
project = var.project_id
}

locals {
create_service_account = var.service_account_name == ""
sa_name = local.create_service_account ? "${var.deployment_name}-sa" : var.service_account_name
sa_email = local.create_service_account ? google_service_account.elastic_agent[0].email : "${var.service_account_name}@${var.project_id}.iam.gserviceaccount.com"
network_name = "${var.deployment_name}-network"

# Determine install command based on version
install_command = startswith(var.elastic_agent_version, "9.") ? "sudo ./elastic-agent install --non-interactive --install-servers" : "sudo ./elastic-agent install --non-interactive"
}

# VPC Network
resource "google_compute_network" "elastic_agent" {
name = local.network_name
auto_create_subnetworks = true
routing_mode = "REGIONAL"
}

# Firewall rule for SSH (optional)
resource "google_compute_firewall" "ssh" {
count = var.allow_ssh ? 1 : 0
name = "elastic-agent-firewall-rule"
network = google_compute_network.elastic_agent.self_link

allow {
protocol = "tcp"
ports = ["22"]
}

source_ranges = ["0.0.0.0/0"]
}

# Service Account (created only if not provided)
resource "google_service_account" "elastic_agent" {
count = local.create_service_account ? 1 : 0
account_id = local.sa_name
display_name = "Elastic agent service account for CSPM"
project = var.project_id
}

# IAM Bindings for Cloud Asset Viewer role
resource "google_project_iam_member" "cloudasset_viewer" {
count = local.create_service_account && var.scope == "projects" ? 1 : 0
project = var.parent_id
role = "roles/cloudasset.viewer"
member = "serviceAccount:${local.sa_email}"

depends_on = [google_service_account.elastic_agent]
}

resource "google_project_iam_member" "browser" {
count = local.create_service_account && var.scope == "projects" ? 1 : 0
project = var.parent_id
role = "roles/browser"
member = "serviceAccount:${local.sa_email}"

depends_on = [google_service_account.elastic_agent]
}

# Organization-level IAM bindings
resource "google_organization_iam_member" "cloudasset_viewer_org" {
count = local.create_service_account && var.scope == "organizations" ? 1 : 0
org_id = var.parent_id
role = "roles/cloudasset.viewer"
member = "serviceAccount:${local.sa_email}"

depends_on = [google_service_account.elastic_agent]
}

resource "google_organization_iam_member" "browser_org" {
count = local.create_service_account && var.scope == "organizations" ? 1 : 0
org_id = var.parent_id
role = "roles/browser"
member = "serviceAccount:${local.sa_email}"

depends_on = [google_service_account.elastic_agent]
}

# Compute Instance
resource "google_compute_instance" "elastic_agent" {
name = var.deployment_name
machine_type = var.machine_type
zone = var.zone

labels = {
name = "elastic-agent"
}

boot_disk {
initialize_params {
image = "ubuntu-os-cloud/ubuntu-minimal-2204-lts"
size = 32
type = "pd-standard"
}
auto_delete = true
}

network_interface {
network = google_compute_network.elastic_agent.self_link

access_config {
# Ephemeral public IP
}
}

service_account {
email = local.sa_email
scopes = [
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/cloudplatformorganizations",
]
}

metadata_startup_script = <<-EOT
#!/bin/bash
set -x
ElasticAgentArtifact=elastic-agent-${var.elastic_agent_version}-linux-x86_64
curl -L -O ${var.elastic_artifact_server}/$ElasticAgentArtifact.tar.gz
tar xzvf $ElasticAgentArtifact.tar.gz
cd $ElasticAgentArtifact
${local.install_command} --url=${var.fleet_url} --enrollment-token=${var.enrollment_token}
EOT

depends_on = [
google_service_account.elastic_agent,
google_project_iam_member.cloudasset_viewer,
google_project_iam_member.browser,
google_organization_iam_member.cloudasset_viewer_org,
google_organization_iam_member.browser_org,
]
}
24 changes: 24 additions & 0 deletions deploy/infrastructure-manager/gcp-elastic-agent/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
output "instance_name" {
description = "Name of the compute instance"
value = google_compute_instance.elastic_agent.name
}

output "instance_id" {
description = "ID of the compute instance"
value = google_compute_instance.elastic_agent.id
}

output "instance_zone" {
description = "Zone of the compute instance"
value = google_compute_instance.elastic_agent.zone
}

output "network_name" {
description = "Name of the VPC network"
value = google_compute_network.elastic_agent.name
}

output "service_account_email" {
description = "Email of the service account used by the instance"
value = local.sa_email
}
98 changes: 98 additions & 0 deletions deploy/infrastructure-manager/gcp-elastic-agent/service_account.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# This file is used for standalone service account deployment
# It creates a service account with IAM roles and generates a key

terraform {
required_version = ">= 1.0"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}

provider "google" {
project = var.sa_project_id
}

variable "sa_project_id" {
description = "GCP Project ID"
type = string
}

variable "sa_deployment_name" {
description = "Name of the deployment"
type = string
default = "elastic-agent-cspm-user"
}

variable "sa_service_account_name" {
description = "Service account name"
type = string
default = "elastic-agent-cspm-user-sa"
}

variable "sa_scope" {
description = "Scope for IAM bindings (projects or organizations)"
type = string
default = "projects"
}

variable "sa_parent_id" {
description = "Parent ID (project ID or organization ID)"
type = string
}

# Service Account
resource "google_service_account" "cspm_user" {
account_id = var.sa_service_account_name
display_name = "Elastic agent service account for CSPM"
project = var.sa_project_id
}

# Service Account Key
resource "google_service_account_key" "cspm_user_key" {
service_account_id = google_service_account.cspm_user.name
}

# Project-level IAM bindings
resource "google_project_iam_member" "cloudasset_viewer" {
count = var.sa_scope == "projects" ? 1 : 0
project = var.sa_parent_id
role = "roles/cloudasset.viewer"
member = "serviceAccount:${google_service_account.cspm_user.email}"
}

resource "google_project_iam_member" "browser" {
count = var.sa_scope == "projects" ? 1 : 0
project = var.sa_parent_id
role = "roles/browser"
member = "serviceAccount:${google_service_account.cspm_user.email}"
}

# Organization-level IAM bindings
resource "google_organization_iam_member" "cloudasset_viewer_org" {
count = var.sa_scope == "organizations" ? 1 : 0
org_id = var.sa_parent_id
role = "roles/cloudasset.viewer"
member = "serviceAccount:${google_service_account.cspm_user.email}"
}

resource "google_organization_iam_member" "browser_org" {
count = var.sa_scope == "organizations" ? 1 : 0
org_id = var.sa_parent_id
role = "roles/browser"
member = "serviceAccount:${google_service_account.cspm_user.email}"
}

# Output the service account key
output "service_account_key" {
description = "Service account private key (base64 encoded)"
value = google_service_account_key.cspm_user_key.private_key
sensitive = true
}

output "service_account_email" {
description = "Service account email"
value = google_service_account.cspm_user.email
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Example tfvars file for service account deployment
# Copy this to service_account.tfvars and fill in your values

sa_project_id = "your-gcp-project-id"
sa_deployment_name = "elastic-agent-cspm-user"
sa_service_account_name = "elastic-agent-cspm-user-sa"
sa_scope = "projects" # or "organizations"
sa_parent_id = "your-gcp-project-id" # or organization ID
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Example terraform.tfvars file
# Copy this to terraform.tfvars and fill in your values

project_id = "your-gcp-project-id"
deployment_name = "elastic-agent-cspm"
zone = "us-central1-a"
fleet_url = "https://your-fleet-url:443"
enrollment_token = "your-enrollment-token"
elastic_agent_version = "8.15.0"
elastic_artifact_server = "https://artifacts.elastic.co/downloads/beats/elastic-agent"
scope = "projects" # or "organizations"
parent_id = "your-gcp-project-id" # or organization ID
service_account_name = "" # leave empty to create new SA
allow_ssh = false
Loading
Loading