Self-Hosted Runners
Run Controlinfra scans in your own infrastructure for enhanced security and compliance.
Overview
Self-hosted runners allow you to:
- Keep credentials local: Cloud credentials never leave your infrastructure
- Meet compliance requirements: Run scans within your security perimeter
- Use cloud-native auth: IAM roles (AWS), Managed Identity (Azure), or Workload Identity (GCP)
- Control the environment: Your infrastructure, your rules
Supported Cloud Platforms
| Platform | VM Type | Authentication | Guide |
|---|---|---|---|
| AWS | EC2 | Instance Profile, Assume Role | AWS Setup |
| Azure | Azure VM | Managed Identity | Azure Setup |
| GCP | Compute Engine | Service Account, Workload Identity | GCP Setup |
How It Works
┌─────────────────────────────────────────────────────────────┐
│ Your Infrastructure │
│ ┌─────────────────┐ ┌───────────────────────────────┐ │
│ │ Self-Hosted │ │ Your Cloud Resources │ │
│ │ Runner │───▶│ (VMs, Databases, Storage) │ │
│ │ │ └───────────────────────────────┘ │
│ │ • Terraform │ │
│ │ • Cloud CLI │ ┌───────────────────────────────┐ │
│ │ • Git │───▶│ Terraform State │ │
│ └────────┬────────┘ └───────────────────────────────┘ │
│ │ │
└───────────┼─────────────────────────────────────────────────┘
│ Results only
▼
┌─────────────────────────────────────────────────────────────┐
│ Controlinfra Cloud │
│ • Receives scan results │
│ • Displays in dashboard │
│ • AI analysis (using your API key) │
└─────────────────────────────────────────────────────────────┘AWS EC2 Runner
Prerequisites
- An EC2 instance in your AWS account
- Network access to your AWS resources
- IAM role with read permissions
- Outbound internet access (HTTPS to Controlinfra)
Setup Guide
Step 1: Create IAM Role
Create an IAM role for the runner with the necessary permissions.
Option A: Use AWS Managed Policies (Recommended)
The simplest approach is to attach AWS managed policies:
# Create the IAM role
aws iam create-role \
--role-name ControlinfraRunnerRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Attach ReadOnlyAccess for drift detection across all AWS services
aws iam attach-role-policy \
--role-name ControlinfraRunnerRole \
--policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
# Create and attach Terraform state access policy
aws iam put-role-policy \
--role-name ControlinfraRunnerRole \
--policy-name TerraformStateAccess \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TerraformStateS3",
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject", "s3:ListBucket", "s3:GetBucketLocation"],
"Resource": [
"arn:aws:s3:::YOUR-TERRAFORM-STATE-BUCKET",
"arn:aws:s3:::YOUR-TERRAFORM-STATE-BUCKET/*"
]
},
{
"Sid": "TerraformDynamoDBLock",
"Effect": "Allow",
"Action": ["dynamodb:GetItem", "dynamodb:PutItem", "dynamodb:DeleteItem"],
"Resource": "arn:aws:dynamodb:*:*:table/terraform-*"
}
]
}'
# Create instance profile and attach role
aws iam create-instance-profile --instance-profile-name ControlinfraRunnerProfile
aws iam add-role-to-instance-profile \
--instance-profile-name ControlinfraRunnerProfile \
--role-name ControlinfraRunnerRoleReplace Placeholder Values
Replace YOUR-TERRAFORM-STATE-BUCKET with your actual S3 bucket name where Terraform state is stored.
Required Permissions Summary
| Permission | Purpose |
|---|---|
| ReadOnlyAccess | Query AWS resources for drift detection (EC2, RDS, IAM, Lambda, etc.) |
| S3 State Access | Read Terraform state file from S3 backend |
| DynamoDB Lock | Terraform state locking (if using DynamoDB) |
Option B: Custom Least-Privilege Policy
For more restrictive access, create a custom policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "TerraformReadAccess",
"Effect": "Allow",
"Action": [
"ec2:Describe*",
"s3:GetObject",
"s3:GetBucket*",
"s3:ListBucket",
"rds:Describe*",
"iam:Get*",
"iam:List*",
"lambda:Get*",
"lambda:List*",
"dynamodb:Describe*",
"elasticloadbalancing:Describe*",
"autoscaling:Describe*",
"cloudwatch:Describe*",
"sns:Get*",
"sqs:Get*",
"route53:Get*",
"route53:List*"
],
"Resource": "*"
},
{
"Sid": "TerraformStateAccess",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::your-terraform-state-bucket",
"arn:aws:s3:::your-terraform-state-bucket/*"
]
},
{
"Sid": "DynamoDBLocking",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem"
],
"Resource": "arn:aws:dynamodb:*:*:table/terraform-*"
}
]
}When to Use Custom Policy
Use a custom policy when you want to limit access to specific AWS services that your Terraform manages. Add or remove service-specific Describe*/Get*/List* actions based on your infrastructure.
Step 2: Launch EC2 Instance
Launch an EC2 instance with:
| Setting | Recommendation |
|---|---|
| AMI | Amazon Linux 2023 or Ubuntu 22.04 |
| Instance Type | t3.small or larger |
| IAM Role | The role created in Step 1 |
| Security Group | Outbound HTTPS (443) only |
| Storage | 20GB+ (for Terraform providers) |
Step 3: Install Dependencies
SSH into your instance and run:
#!/bin/bash
# Update system
sudo yum update -y # Amazon Linux
# sudo apt update && sudo apt upgrade -y # Ubuntu
# Install Git
sudo yum install -y git
# sudo apt install -y git
# Install Terraform
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
sudo yum install -y terraform
# Verify installations
git --version
terraform --version
aws --versionStep 4: Register Runner in Controlinfra
- Go to Settings → Runners
- Click "Add Runner"
- Copy the registration token
Step 5: Install Runner Agent
On your EC2 instance, first install the Controlinfra CLI:
# Install Controlinfra CLI
curl -fsSL https://controlinfra.com/cli/install.sh | bash
# Verify installation
controlinfra --versionThen authenticate and set up the runner:
# Authenticate with your CLI token (from Settings > CLI Tokens)
controlinfra login --token YOUR_CLI_TOKEN
# Get the runner setup script (replace RUNNER_ID with your runner ID from Step 4)
controlinfra runners setup RUNNER_IDThe runners setup command will output an installation script. Run it to install and start the runner agent:
# Example output - run the command shown by 'runners setup'
curl -sL "https://api.controlinfra.com/api/runners/RUNNER_ID/setup?token=RUNNER_TOKEN" | sudo bashAlternative: Direct Installation
If you already have the runner token from Step 4, you can install directly:
curl -sL "https://api.controlinfra.com/api/runners/RUNNER_ID/setup?token=YOUR_RUNNER_TOKEN" | sudo bashStep 6: Verify Connection
In Controlinfra:
- Go to Settings → Runners
- Your runner should show as "Online"
Runner Configuration
Configuration File
The runner configuration is stored at ~/.controlinfra/runner.yml:
token: "your-runner-token"
name: "production-runner"
labels:
- production
- aws
- us-east-1
workDir: "/tmp/controlinfra"
logLevel: "info"Environment Variables
Configure the runner using environment variables:
export CONTROLINFRA_TOKEN="your-token"
export CONTROLINFRA_RUNNER_NAME="production-runner"
export CONTROLINFRA_WORK_DIR="/tmp/controlinfra"
export CONTROLINFRA_LOG_LEVEL="debug"Labels
Use labels to target specific runners:
labels:
- production
- aws
- us-east-1Then in repository settings, specify which runner to use:
Runner: production, awsRunning as a Service
systemd Service
Create /etc/systemd/system/controlinfra-runner.service:
[Unit]
Description=Controlinfra Runner
After=network.target
[Service]
Type=simple
User=controlinfra
ExecStart=/usr/local/bin/controlinfra-runner start
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetEnable and start:
sudo systemctl enable controlinfra-runner
sudo systemctl start controlinfra-runnerDocker
Run the runner in Docker:
docker run -d \
--name controlinfra-runner \
--restart always \
-e CONTROLINFRA_TOKEN=your-token \
-v /var/run/docker.sock:/var/run/docker.sock \
controlinfra/runner:latestSecurity Best Practices
1. Network Isolation
- Place runner in a private subnet
- Use NAT Gateway for outbound access
- Restrict inbound access completely
2. IAM Least Privilege
- Only grant read permissions needed
- Use resource-level permissions where possible
- Regularly audit IAM policies
3. Secrets Management
# Don't store secrets in environment variables
# Use AWS Secrets Manager or Parameter Store
aws secretsmanager get-secret-value --secret-id controlinfra/token4. Logging and Monitoring
# Enable CloudWatch logging
sudo yum install -y amazon-cloudwatch-agent5. Regular Updates
# Update runner agent regularly
controlinfra-runner updateMultiple Runners
For high availability or multi-environment setups:
Environment-Specific Runners
production-runner (labels: production, us-east-1)
└── Scans production repositories
staging-runner (labels: staging, us-east-1)
└── Scans staging repositories
eu-runner (labels: production, eu-west-1)
└── Scans EU region resourcesLoad Balancing
Multiple runners with the same labels will automatically load balance:
runner-1 (labels: production)
runner-2 (labels: production)
runner-3 (labels: production)Assigning Runners to Repositories
In Repository Settings
- Navigate to your repository
- Go to Settings → Runner
- Select runner type:
- Cloud Runner: Use Controlinfra's hosted runner
- Self-Hosted: Use your own runner
- If self-hosted, select the runner or specify labels
Using Labels
Repository: my-terraform-repo
Runner Type: Self-Hosted
Runner Labels: production, us-east-1Troubleshooting
Runner Shows "Offline"
Check the runner service is running:
bashsystemctl status controlinfra-runnerCheck network connectivity:
bashcurl -v https://api.controlinfra.com/healthVerify the token:
bashcontrolinfra-runner verify
Scans Failing on Runner
Check runner logs:
bashjournalctl -u controlinfra-runner -fVerify Terraform is installed:
bashterraform versionCheck IAM permissions:
bashaws sts get-caller-identity
"Permission Denied" Errors
- Verify IAM role is attached to the instance
- Check IAM policy has required permissions
- Ensure the work directory is writable
Terraform Init Failures
Common causes and solutions:
S3 State Access Denied:
Error: Failed to get state: AccessDenied: Access Denied- Ensure the IAM role has
s3:ListBucketands3:GetObjecton your state bucket - Check both bucket-level (
s3:ListBucket) and object-level (s3:GetObject) permissions - Verify the bucket ARN matches exactly:
arn:aws:s3:::your-bucket-name
- Ensure the IAM role has
DynamoDB Lock Access Denied:
Error: Error acquiring the state lock- Add
dynamodb:GetItem,dynamodb:PutItem,dynamodb:DeleteItempermissions - Check the table name pattern matches your lock table
- Add
Network/Registry Issues:
- Check network access to Terraform registries
- Verify outbound HTTPS (443) is allowed
- Check disk space for provider downloads
Missing ReadOnlyAccess:
Error: error reading EC2 instance: UnauthorizedOperation- Attach the AWS managed
ReadOnlyAccesspolicy - Or add specific
Describe*/Get*/List*actions for your resources
- Attach the AWS managed
Azure VM Runner
Deploy a self-hosted runner on an Azure Virtual Machine using Managed Identity for secure, credential-free authentication.
Prerequisites
- An Azure subscription
- An Azure Virtual Machine (Ubuntu 22.04 recommended)
- System-assigned Managed Identity enabled
- Outbound internet access (HTTPS to Controlinfra)
Step 1: Create Azure VM with Managed Identity
# Create resource group
az group create \
--name controlinfra-runner-rg \
--location eastus
# Create VM with system-assigned managed identity
az vm create \
--resource-group controlinfra-runner-rg \
--name controlinfra-runner-vm \
--image Ubuntu2204 \
--size Standard_B2s \
--admin-username adminuser \
--generate-ssh-keys \
--assign-identity
# Note the public IP for SSH accessOr enable managed identity on an existing VM:
az vm identity assign \
--resource-group YOUR_RESOURCE_GROUP \
--name YOUR_VM_NAMEStep 2: Assign Required Roles
The managed identity needs permissions to read your Azure resources and access Terraform state.
# Get the principal ID of the managed identity
PRINCIPAL_ID=$(az vm identity show \
--resource-group controlinfra-runner-rg \
--name controlinfra-runner-vm \
--query principalId -o tsv)
# Assign Reader role on the resource group containing your infrastructure
az role assignment create \
--assignee-object-id $PRINCIPAL_ID \
--assignee-principal-type ServicePrincipal \
--role "Reader" \
--scope /subscriptions/YOUR_SUB_ID/resourceGroups/YOUR_INFRA_RG
# Assign Storage Blob Data Contributor on your Terraform state storage account
az role assignment create \
--assignee-object-id $PRINCIPAL_ID \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Contributor" \
--scope /subscriptions/YOUR_SUB_ID/resourceGroups/YOUR_RG/providers/Microsoft.Storage/storageAccounts/YOUR_STORAGE
# For full drift detection, assign Contributor on the infrastructure resource group
az role assignment create \
--assignee-object-id $PRINCIPAL_ID \
--assignee-principal-type ServicePrincipal \
--role "Contributor" \
--scope /subscriptions/YOUR_SUB_ID/resourceGroups/YOUR_INFRA_RGRequired Permissions - All Three Are Needed
| Role | Scope | Purpose |
|---|---|---|
| Reader | Resource Group | Basic read access to Azure resources |
| Storage Blob Data Contributor | Storage Account | Access Terraform state file |
| Contributor | Resource Group | Required for listKeys/action and full resource state |
Important: Terraform needs Microsoft.Storage/storageAccounts/listKeys/action to read storage account state. Without Contributor role, you'll get "AuthorizationFailed" errors during scans.
Step 3: Install Dependencies
SSH into your Azure VM and install required tools:
#!/bin/bash
# Update system
sudo apt update && sudo apt upgrade -y
# Install Git
sudo apt install -y git curl jq
# Install Terraform
sudo apt install -y gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | \
sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install -y terraform
# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
# Verify installations
git --version
terraform --version
az --versionStep 4: Register Runner in Controlinfra
- Go to Settings → Runners
- Click "Add Runner"
- Enter a name and add the
azurelabel - Copy the registration token and runner ID
Step 5: Install Runner Agent
On your Azure VM, first install the Controlinfra CLI:
# Install Controlinfra CLI
curl -fsSL https://controlinfra.com/cli/install.sh | bash
# Verify installation
controlinfra --versionThen authenticate and set up the runner:
# Authenticate with your CLI token (from Settings > CLI Tokens)
controlinfra login --token YOUR_CLI_TOKEN
# Get the runner setup script (replace RUNNER_ID with your runner ID from Step 4)
controlinfra runners setup RUNNER_IDThe runners setup command will output an installation script. Run it to install the runner agent:
# Example output - run the command shown by 'runners setup'
curl -sL "https://api.controlinfra.com/api/runners/RUNNER_ID/setup?token=RUNNER_TOKEN" | sudo bashAlternative: Direct Installation
If you already have the runner token from Step 4, you can install directly:
curl -sL "https://api.controlinfra.com/api/runners/RUNNER_ID/setup?token=YOUR_RUNNER_TOKEN" | sudo bashStep 6: Configure Managed Identity Environment
Create the systemd service file at /etc/systemd/system/controlinfra-runner.service:
[Unit]
Description=Controlinfra Self-Hosted Runner
After=network.target
[Service]
Type=simple
User=controlinfra
WorkingDirectory=/opt/controlinfra-runner
# Azure Managed Identity configuration for Terraform
Environment="ARM_USE_MSI=true"
Environment="ARM_SUBSCRIPTION_ID=YOUR_SUBSCRIPTION_ID"
Environment="ARM_TENANT_ID=YOUR_TENANT_ID"
ExecStart=/opt/controlinfra-runner/bin/controlinfra-agent
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetImportant
The ARM_USE_MSI=true environment variable tells Terraform to use Managed Identity instead of Azure CLI authentication. Without this, you'll see "Azure CLI auth only supported as User" errors.
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable controlinfra-runner
sudo systemctl start controlinfra-runnerStep 7: Verify Connection
Check runner service status:
bashsudo systemctl status controlinfra-runnerView logs:
bashsudo journalctl -u controlinfra-runner -fIn Controlinfra, go to Settings → Runners - your runner should show as "Online"
Troubleshooting Azure Runner
"Azure CLI auth only supported as User" Error
Error building ARM Config: Authenticating using the Azure CLI is only supported as a User (not a Service Principal)Solution: Add ARM environment variables to the systemd service:
Environment="ARM_USE_MSI=true"
Environment="ARM_SUBSCRIPTION_ID=your-sub-id"
Environment="ARM_TENANT_ID=your-tenant-id"Then restart: sudo systemctl daemon-reload && sudo systemctl restart controlinfra-runner
"AuthorizationPermissionMismatch" Error
This request is not authorized to perform this operation using this permissionSolution: Assign Storage Blob Data Contributor role on the Terraform state storage account.
"listKeys/action" Permission Error
does not have authorization to perform action 'Microsoft.Storage/storageAccounts/listKeys/action'Solution: Assign Contributor role on the resource group containing your infrastructure.
Runner Shows "Offline"
Check the service is running:
bashsudo systemctl status controlinfra-runnerCheck network connectivity:
bashcurl -v https://api.controlinfra.com/healthView detailed logs:
bashsudo journalctl -u controlinfra-runner -n 100 --no-pager
GCP Compute Engine Runner
Deploy a self-hosted runner on a Google Cloud Compute Engine instance using Service Account or Workload Identity for secure authentication.
Prerequisites
- A Google Cloud project with billing enabled
- A Compute Engine VM instance (Ubuntu 22.04 recommended)
- Service Account or Workload Identity configured
- Outbound internet access (HTTPS to Controlinfra)
Authentication Methods
| Method | Best For | Credentials |
|---|---|---|
| Service Account | Cross-project access, external VMs | JSON key file |
| Workload Identity | GCE VMs in same project | Automatic via metadata |
Step 1: Create Service Account
Create a service account with the necessary permissions for drift detection:
# Set your project
export PROJECT_ID="your-project-id"
gcloud config set project $PROJECT_ID
# Create service account
gcloud iam service-accounts create controlinfra-runner \
--display-name="Controlinfra Runner" \
--description="Service account for Controlinfra self-hosted runner"
# Get the service account email
export SA_EMAIL="controlinfra-runner@${PROJECT_ID}.iam.gserviceaccount.com"Step 2: Assign Required Roles
The service account needs read access to your GCP resources and Terraform state:
# Viewer role for drift detection (read access to all resources)
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/viewer"
# Storage Object Viewer for Terraform state in GCS
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/storage.objectViewer"
# If using GCS for state with locking, also add:
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/storage.objectAdmin" \
--condition=NoneRequired Permissions Summary
| Role | Purpose |
|---|---|
| roles/viewer | Read access to GCP resources for drift detection |
| roles/storage.objectViewer | Read Terraform state from GCS bucket |
| roles/storage.objectAdmin | (Optional) If Terraform needs to write state |
Step 3: Create Compute Engine VM
Option A: With Attached Service Account (Workload Identity)
# Create VM with attached service account
gcloud compute instances create controlinfra-runner \
--project=$PROJECT_ID \
--zone=us-central1-a \
--machine-type=e2-medium \
--image-family=ubuntu-2204-lts \
--image-project=ubuntu-os-cloud \
--boot-disk-size=20GB \
--service-account=$SA_EMAIL \
--scopes=cloud-platform \
--tags=controlinfra-runner
# Note the external IP for SSH accessOption B: Using Service Account Key
If using a key file instead of attached identity:
# Create and download key file
gcloud iam service-accounts keys create ~/controlinfra-sa-key.json \
--iam-account=$SA_EMAIL
# Securely copy to your VM
scp ~/controlinfra-sa-key.json user@VM_IP:~/Step 4: Install Dependencies
SSH into your GCE instance and install required tools:
#!/bin/bash
# Update system
sudo apt update && sudo apt upgrade -y
# Install Git and utilities
sudo apt install -y git curl jq
# Install Terraform
sudo apt install -y gnupg software-properties-common
wget -O- https://apt.releases.hashicorp.com/gpg | \
gpg --dearmor | \
sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
https://apt.releases.hashicorp.com $(lsb_release -cs) main" | \
sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update && sudo apt install -y terraform
# Install Google Cloud CLI (if not pre-installed)
curl https://sdk.cloud.google.com | bash
exec -l $SHELL
gcloud init
# Verify installations
git --version
terraform --version
gcloud --versionStep 5: Register Runner in Controlinfra
- Go to Settings → Runners
- Click "Add Runner"
- Enter a name and add the
gcplabel - Copy the registration token and runner ID
Step 6: Install Runner Agent
On your GCE instance, first install the Controlinfra CLI:
# Install Controlinfra CLI
curl -fsSL https://controlinfra.com/cli/install.sh | bash
# Verify installation
controlinfra --versionThen authenticate and set up the runner:
# Authenticate with your CLI token (from Settings > CLI Tokens)
controlinfra login --token YOUR_CLI_TOKEN
# Get the runner setup script (replace RUNNER_ID with your runner ID from Step 5)
controlinfra runners setup RUNNER_IDThe runners setup command will output an installation script. Run it to install the runner agent:
# Example output - run the command shown by 'runners setup'
curl -sL "https://api.controlinfra.com/api/runners/RUNNER_ID/setup?token=RUNNER_TOKEN" | sudo bashAlternative: Direct Installation
If you already have the runner token from Step 5, you can install directly:
curl -sL "https://api.controlinfra.com/api/runners/RUNNER_ID/setup?token=YOUR_RUNNER_TOKEN" | sudo bashStep 7: Configure Authentication
Create the systemd service file at /etc/systemd/system/controlinfra-runner.service:
For Workload Identity (Attached Service Account):
[Unit]
Description=Controlinfra Self-Hosted Runner
After=network.target
[Service]
Type=simple
User=controlinfra
WorkingDirectory=/opt/controlinfra-runner
# GCP Workload Identity - uses metadata server automatically
Environment="GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID"
ExecStart=/opt/controlinfra-runner/bin/controlinfra-agent
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetFor Service Account Key:
[Unit]
Description=Controlinfra Self-Hosted Runner
After=network.target
[Service]
Type=simple
User=controlinfra
WorkingDirectory=/opt/controlinfra-runner
# GCP Service Account authentication
Environment="GOOGLE_APPLICATION_CREDENTIALS=/opt/controlinfra-runner/gcp-sa-key.json"
Environment="GOOGLE_CLOUD_PROJECT=YOUR_PROJECT_ID"
ExecStart=/opt/controlinfra-runner/bin/controlinfra-agent
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.targetWorkload Identity vs Service Account Key
Workload Identity (attached service account) is recommended for GCE VMs as it:
- Requires no key management
- Automatically rotates credentials
- Uses the GCP metadata server for authentication
Service Account Key is useful when:
- Running on non-GCP infrastructure
- Needing cross-project access
- Running in containers without metadata access
Enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable controlinfra-runner
sudo systemctl start controlinfra-runnerStep 8: Verify Connection
Check runner service status:
bashsudo systemctl status controlinfra-runnerView logs:
bashsudo journalctl -u controlinfra-runner -fIn Controlinfra, go to Settings → Runners - your runner should show as "Online"
Troubleshooting GCP Runner
"Could not find default credentials" Error
Error: google: could not find default credentialsSolution: Ensure either:
- Service account is attached to the VM (Workload Identity)
GOOGLE_APPLICATION_CREDENTIALSpoints to valid key file
Check with:
gcloud auth application-default print-access-token"Permission Denied" on GCS State
Error: Failed to get state: googleapi: Error 403: Access deniedSolution: Ensure the service account has roles/storage.objectViewer on the state bucket:
gsutil iam ch serviceAccount:${SA_EMAIL}:objectViewer gs://YOUR_STATE_BUCKET"Caller does not have permission" Error
Error: googleapi: Error 403: Caller does not have permissionSolution: Add the roles/viewer role to the service account:
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/viewer"Runner Shows "Offline"
Check the service is running:
bashsudo systemctl status controlinfra-runnerCheck network connectivity:
bashcurl -v https://api.controlinfra.com/healthVerify GCP authentication:
bashgcloud auth list gcloud config get-value projectView detailed logs:
bashsudo journalctl -u controlinfra-runner -n 100 --no-pager
Metadata Server Not Accessible
Error: metadata: GCE metadata server not availableSolution: This error occurs when running outside GCE or in a container. Use a service account key file instead:
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/key.json"Maintenance
Updating the Runner
# Stop the runner
sudo systemctl stop controlinfra-runner
# Update
controlinfra-runner update
# Start the runner
sudo systemctl start controlinfra-runnerRotating Tokens
- Generate new token in Controlinfra Settings
- Update runner configuration
- Restart the runner
- Revoke old token
Log Rotation
Configure log rotation in /etc/logrotate.d/controlinfra:
/var/log/controlinfra/*.log {
daily
rotate 7
compress
missingok
notifempty
}Next Steps
- Configure AWS Credentials - AWS IAM setup details
- Configure Azure Credentials - Azure authentication setup
- Configure GCP Credentials - GCP Service Account setup
- Repository Configuration - Assign runners to repos
- Run Your First Scan - Test your runner