Skip to main content

ICP Platform - Linux VM Integration Guide

Complete guide for integrating ICP (Identity Control Plane) workload identity on Linux virtual machines and bare-metal servers.

Table of Contents


Prerequisites

System Requirements

  • Operating System: Ubuntu 20.04+, Debian 11+, RHEL 8+, CentOS 8+, Amazon Linux 2
  • Architecture: x86_64 (ARM64 support available)
  • RAM: Minimum 512MB, Recommended 1GB
  • Disk: 100MB for agent + 500MB for logs/cache
  • Python: 3.8+ (for SDK integration)

Network Requirements

Outbound Connections Required:
- ICP Server: https://dev.api.authsec.dev/spiresvc (HTTPS)
- Package Repos: For installing dependencies

Inbound Connections:
- None (agent listens only on local Unix socket)

Credentials

You'll need:

  1. Tenant ID - Provided by AuthSec
  2. ICP Server URL - Your ICP Server endpoint
  3. Node ID - Unique identifier for this VM (e.g., vm-prod-web-01)
  4. Join Token (optional) - For automatic attestation

Architecture Overview

Linux VM Deployment Architecture

┌──────────────────────────────────────────────────────────────────┐
│ Linux VM / Bare Metal │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ ICP Agent (systemd service) │ │
│ │ │ │
│ │ Process: /usr/local/bin/icp-agent │ │
│ │ Config: /etc/icp-agent/config.yaml │ │
│ │ Socket: /run/spire/sockets/agent.sock │ │
│ │ User: root (or icp-agent) │ │
│ │ Auto-start: systemd │ │
│ └──────────────────────┬─────────────────────────────────────┘ │
│ │ │
│ │ Unix Socket │
│ │ /run/spire/sockets/agent.sock │
│ ┌───────────────┴────────────────┐ │
│ │ │ │
│ ┌──────▼──────────────┐ ┌────────────▼─────────────┐ │
│ │ Workload 1 │ │ Workload 2 │ │
│ │ (Python App) │ │ (Go Service) │ │
│ │ │ │ │ │
│ │ User: app-user │ │ User: svc-user │ │
│ │ PID: 12345 │ │ PID: 67890 │ │
│ │ UID: 1000 │ │ UID: 1001 │ │
│ │ GID: 1000 │ │ GID: 1001 │ │
│ │ │ │ │ │
│ │ SDK Integration │ │ SDK Integration │ │
│ └─────────────────────┘ └──────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────┘

│ HTTPS + mTLS

┌─────────────────────────────┐
│ ICP Server (SaaS) │
│ - Agent SVID Issuance │
│ - Workload SVID Issuance │
└─────────────────────────────┘

How Unix Attestation Works

The agent collects these selectors from running processes:

SelectorDescriptionExample
unix:uidUser ID"1000"
unix:gidGroup ID"1000"
unix:pathExecutable path"/usr/bin/python3"
unix:sha256Binary SHA-256 hash"abc123def456..."
unix:processProcess name"python3"
unix:pidProcess ID"12345" (not recommended - changes on restart)

Note: Username and group name lookups are not included as selectors. Use numeric UID/GID instead.

Matching: Workload entries with selectors that are a subset of collected selectors will match.


Installation

Method 1: Quick Install Script

# Download and run installer
curl -fsSL https://install.authsec.ai/icp-agent.sh | sudo bash -s -- \
--tenant-id "your-tenant-id-here" \
--icp-server "https://dev.api.authsec.dev/spiresvc" \
--node-id "vm-prod-web-01"

The script will:

  1. Install dependencies (Python 3, systemd)
  2. Download ICP Agent binary
  3. Create systemd service
  4. Start the agent
  5. Enable auto-start on boot

Method 2: Manual Installation

Step 1: Install Dependencies

Ubuntu/Debian:

sudo apt-get update
sudo apt-get install -y python3 python3-pip git systemd

RHEL/CentOS:

sudo yum install -y python3 python3-pip git systemd

Step 2: Download ICP Agent

# Create installation directory
sudo mkdir -p /opt/icp-agent
cd /opt/icp-agent

# Clone repository
sudo git clone https://github.com/your-org/icp-agent.git .

# Install Python dependencies
sudo pip3 install -r requirements.txt

Step 3: Create Configuration

# Create config directory
sudo mkdir -p /etc/icp-agent

# Create config file
sudo tee /etc/icp-agent/config.yaml > /dev/null <<EOF
agent:
tenant_id: "your-tenant-id-here"
node_id: "vm-prod-web-01"
data_dir: "/var/lib/icp-agent"
socket_path: "/run/spire/sockets/agent.sock"
renewal_threshold: "6h"

icp_service:
address: "https://your-icp-server.example.com/spiresvc"
trust_bundle_path: "/etc/icp-agent/ca-bundle.pem"
timeout: 30
max_retries: 3
retry_backoff: 5

attestation:
type: "unix"
unix:
method: "procfs"

security:
cache_encryption_key: ""
cache_path: "/var/lib/icp-agent/cache/svid.cache"

logging:
level: "info"
format: "json"
file_path: "/var/log/icp-agent/agent.log"

health:
enabled: true
port: 8080
bind_address: "127.0.0.1"
EOF

Step 4: Create systemd Service

sudo tee /etc/systemd/system/icp-agent.service > /dev/null <<EOF
[Unit]
Description=ICP Agent - Workload Identity Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=root
Group=root
ExecStart=/opt/icp-agent/icp_agent/main.py -c /etc/icp-agent/config.yaml
Restart=on-failure
RestartSec=5s
LimitNOFILE=65536

# Security hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/icp-agent /var/log/icp-agent /run/spire

# Environment
Environment="PYTHONUNBUFFERED=1"

[Install]
WantedBy=multi-user.target
EOF

Step 5: Create Required Directories

# Create data and log directories
sudo mkdir -p /var/lib/icp-agent/cache
sudo mkdir -p /var/log/icp-agent
sudo mkdir -p /run/spire/sockets

# Set permissions
sudo chmod 755 /run/spire/sockets
sudo chmod 700 /var/lib/icp-agent

Step 6: Start Agent

# Reload systemd
sudo systemctl daemon-reload

# Start agent
sudo systemctl start icp-agent

# Enable auto-start on boot
sudo systemctl enable icp-agent

# Check status
sudo systemctl status icp-agent

Verification

# Check agent is running
sudo systemctl status icp-agent

# Check logs
sudo journalctl -u icp-agent -f

# Check socket exists
ls -l /run/spire/sockets/agent.sock

# Test health endpoint
curl http://localhost:8080/healthz

Expected output:

{"status": "healthy"}

Configuration

Configuration File Reference

File: /etc/icp-agent/config.yaml

# Agent Configuration
agent:
# Your tenant ID (provided by AuthSec)
tenant_id: "your-tenant-id-here"

# Unique node identifier (hostname, IP, or custom ID)
node_id: "vm-prod-web-01"

# Data directory for cache and state
data_dir: "/var/lib/icp-agent"

# Unix socket path for Workload API
socket_path: "/run/spire/sockets/agent.sock"

# When to renew agent SVID (before expiry)
renewal_threshold: "6h"

# ICP Server Configuration
icp_service:
# ICP Server URL
address: "https://dev.api.authsec.dev/spiresvc"

# Path to CA bundle (for verifying ICP Server)
trust_bundle_path: "/etc/icp-agent/ca-bundle.pem"

# Connection timeout (seconds)
timeout: 30

# Number of retries for failed requests
max_retries: 3

# Backoff time between retries (seconds)
retry_backoff: 5

# Attestation Configuration
attestation:
# Attestation type (unix, auto)
type: "unix"

# Unix-specific configuration
unix:
# Method for collecting process info (procfs, ps)
method: "procfs"

# Security Configuration
security:
# Optional: AES-256 key for encrypting cached SVIDs
cache_encryption_key: ""

# Path to SVID cache file
cache_path: "/var/lib/icp-agent/cache/svid.cache"

# Logging Configuration
logging:
# Log level (debug, info, warn, error)
level: "info"

# Log format (json, text)
format: "json"

# Log file path (empty = stdout only)
file_path: "/var/log/icp-agent/agent.log"

# Health Check Configuration
health:
# Enable HTTP health endpoint
enabled: true

# Health endpoint port
port: 8080

# Bind address (127.0.0.1 = localhost only)
bind_address: "127.0.0.1"

Environment Variable Overrides

You can override configuration using environment variables:

# Example: Override tenant ID
export ICP_AGENT_AGENT__TENANT_ID="your-tenant-id-here"

# Example: Override log level
export ICP_AGENT_LOGGING__LEVEL="debug"

# Example: Override ICP server URL
export ICP_AGENT_ICP_SERVICE__ADDRESS="https://dev.api.authsec.dev/spiresvc"

Workload Registration

Understanding Selectors

Before workloads can receive SVIDs, they must be registered with the ICP Server. Registration defines which processes should receive which SPIFFE IDs.

Common Selector Strategies

Best for applications running as dedicated system users:

# Create dedicated user for your application
sudo useradd -r -s /bin/false app-user

# Register workload for this user
export ICP_SERVER_URL="https://your-icp-server.example.com/spiresvc"
export TENANT_ID="your-tenant-id-here"
export NODE_ID="vm-prod-web-01"

curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"unix:uid": "1000"
},
"ttl": 3600
}'

Strategy 2: UID/GID Combined

For stricter matching:

curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"unix:uid": "1000",
"unix:gid": "1000"
},
"ttl": 3600
}'

Multi-Workload Scenarios

Example: Web Server + Database Client

# Register web server (runs as UID 33, typically www-data)
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"unix:uid": "33"
}
}'

# Register background worker (runs as UID 1001)
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"unix:uid": "1001"
}
}'

# Register database client (runs as UID 999, typically postgres)
curl -X POST "${ICP_SERVER_URL}/api/v1/workloads" \
-H "Content-Type: application/json" \
-d '{
"spiffe_id": "spiffe://your-spiffe-id",
"parent_id": "spiffe://your-parent-id",
"selectors": {
"unix:uid": "999"
}
}'

SDK Integration

Installation

Install the AuthSec SDK in your application:

# Install from GitHub
pip install git+https://github.com/authsec-ai/sdk-authsec.git

Quick Start Example

File: app.py

import asyncio
from authsec_sdk import QuickStartSVID
import httpx

async def main():
# Initialize SDK (automatic SVID fetching)
svid = await QuickStartSVID.initialize(
socket_path="/run/spire/sockets/agent.sock"
)

print(f"✅ Authenticated as: {svid.spiffe_id}")
print(f"📜 Certificate expires: {svid.expires_at}")

# Make mTLS request to another service
ssl_context = svid.create_ssl_context_for_client()

async with httpx.AsyncClient(verify=ssl_context) as client:
response = await client.post(
"https://api-service.example.com:8443/endpoint",
json={"data": "value"}
)
print(f"Response: {response.json()}")

if __name__ == "__main__":
asyncio.run(main())

Running Your Application

# Run as the user configured in workload registration
sudo -u app-user python3 app.py

Expected output:

✅ Authenticated as: spiffe://your-trust-domain.example.com/workload/web-app
📜 Certificate expires: 2025-12-08 11:00:00
Response: {'status': 'success'}

Server-Side mTLS (FastAPI Example)

File: server.py

from fastapi import FastAPI
import uvicorn
from authsec_sdk import QuickStartSVID
import asyncio

app = FastAPI()

# Global SVID instance
svid = None

@app.on_event("startup")
async def startup():
global svid
svid = await QuickStartSVID.initialize()
print(f"✅ Server authenticated as: {svid.spiffe_id}")

@app.post("/endpoint")
async def endpoint(data: dict):
return {"status": "success", "received": data}

if __name__ == "__main__":
# Note: uvicorn doesn't reload certs automatically
# For production, use a process manager that restarts on cert rotation
uvicorn.run(
app,
host="0.0.0.0",
port=8443,
ssl_certfile=svid.cert_file_path,
ssl_keyfile=svid.key_file_path,
ssl_ca_certs=svid.ca_file_path
)

Client-Side mTLS Example

from authsec_sdk import QuickStartSVID
import httpx
import asyncio

async def call_api():
# Initialize SVID
svid = await QuickStartSVID.initialize()

# Create fresh SSL context for each request (handles cert renewal)
ssl_context = svid.create_ssl_context_for_client()

async with httpx.AsyncClient(verify=ssl_context) as client:
response = await client.post(
"https://api-service.example.com:8443/endpoint",
json={"key": "value"}
)
return response.json()

# Call periodically
async def main():
while True:
result = await call_api()
print(f"API Response: {result}")
await asyncio.sleep(60)

if __name__ == "__main__":
asyncio.run(main())

Database mTLS Integration (PostgreSQL)

from authsec_sdk import QuickStartSVID
import asyncpg
import asyncio

async def connect_to_db():
# Get SVID
svid = await QuickStartSVID.initialize()

# Connect with mTLS
conn = await asyncpg.connect(
host='postgres.example.com',
port=5432,
database='mydb',
ssl=svid.create_ssl_context_for_client()
)

# Query
result = await conn.fetch('SELECT * FROM users LIMIT 10')
print(result)

await conn.close()

if __name__ == "__main__":
asyncio.run(connect_to_db())

PostgreSQL Configuration:

Edit /etc/postgresql/14/main/pg_hba.conf:

# Allow mTLS connections
hostssl all all 0.0.0.0/0 cert clientcert=verify-full

Edit /etc/postgresql/14/main/postgresql.conf:

ssl = on
ssl_cert_file = '/path/to/server.crt'
ssl_key_file = '/path/to/server.key'
ssl_ca_file = '/path/to/ca-bundle.pem'

Troubleshooting

Issue 1: Agent Won't Start

Symptoms:

sudo systemctl status icp-agent
● icp-agent.service - ICP Agent
Loaded: loaded
Active: failed

Solution:

# Check logs
sudo journalctl -u icp-agent -n 50

# Common fixes:
# 1. Check config file syntax
sudo python3 -c "import yaml; yaml.safe_load(open('/etc/icp-agent/config.yaml'))"

# 2. Check permissions
sudo chmod 600 /etc/icp-agent/config.yaml
sudo chmod 755 /run/spire/sockets

# 3. Check connectivity to ICP Server
curl -v https://your-icp-server.example.com/spiresvc/health

Issue 2: Workload Can't Get SVID

Symptoms:

Error: failed to fetch X509-SVID: rpc error: code = Unknown

Solution:

# 1. Check agent is running
sudo systemctl status icp-agent

# 2. Check socket exists and is accessible
ls -l /run/spire/sockets/agent.sock
# Should show: srwxrwxrwx

# 3. Check workload is registered
curl "${ICP_SERVER_URL}/api/v1/workloads?parent_id=spiffe://${TENANT_ID}/agent/${NODE_ID}"

# 4. Check selectors match
sudo -u app-user id
# Compare UID/GID with registered selectors

# 5. Enable debug logging
sudo sed -i 's/level: "info"/level: "debug"/' /etc/icp-agent/config.yaml
sudo systemctl restart icp-agent
sudo journalctl -u icp-agent -f

Issue 3: Certificate Expired

Symptoms:

Error: certificate has expired or is not yet valid

Solution:

# 1. Check system time
date

# 2. Sync time with NTP
sudo ntpdate pool.ntp.org

# 3. Restart agent to force renewal
sudo systemctl restart icp-agent

# 4. Check agent SVID expiry
sudo cat /var/lib/icp-agent/cache/svid.cache | jq .expires_at

Issue 4: Permission Denied

Symptoms:

Error: connect: permission denied

Solution:

# 1. Check socket permissions
ls -l /run/spire/sockets/agent.sock

# 2. Fix permissions
sudo chmod 777 /run/spire/sockets/agent.sock

# 3. Add user to socket group (if using group permissions)
sudo usermod -aG icp-agent app-user

Next Steps

Questions? Contact support@authsec.dev