Private
Public Access
1
0
Files
linux_patch_api/API_DOCUMENTATION.md
Echo 653623b9f0
Some checks failed
CI/CD Pipeline / Code Format (push) Failing after 2s
CI/CD Pipeline / Clippy Lints (push) Successful in 44s
CI/CD Pipeline / Enrollment Tests (push) Has been skipped
CI/CD Pipeline / All Unit Tests (push) Successful in 1m13s
CI/CD Pipeline / Build Debian Package (push) Has been skipped
CI/CD Pipeline / Build Debian Package (Ubuntu 22.04) (push) Has been skipped
CI/CD Pipeline / Build RPM Package (push) Has been skipped
CI/CD Pipeline / Build Alpine Package (push) Has been skipped
CI/CD Pipeline / Build Arch Package (push) Has been skipped
CI/CD Pipeline / Security Audit (push) Successful in 4s
CI/CD Pipeline / Verify Enrollment CLI Flag (push) Successful in 55s
fix: FQDN resolution and display_name blank bug; fix: Arch/Alpine/RPM packages
Bug fixes:
- get_fqdn() now prioritizes 'hostname -f' (returns full FQDN) over /etc/hostname (returns short hostname)
- Added get_hostname() for short hostname extraction
- Added hostname field to EnrollmentRequest for manager display_name population
- Updated SPEC.md and API_DOCUMENTATION.md

Package fixes:
- Arch: Added linux-patch-api.install with post_install/upgrade/remove hooks, user creation, directory creation, config handling
- Alpine: Added linux-patch-api.apk-install with pre/post install/deinstall hooks, user creation, directory creation, config handling, missing config.yaml.example
- RPM: Dynamic version from Cargo.toml, %ghost %config(noreplace) for live configs, tarball exclusions, /var/log in %files
2026-05-18 23:51:00 +00:00

33 KiB

Linux Patch API - API Documentation

Version: 1.0.0
Base Path: /api/v1/
Protocol: HTTPS (TLS 1.3 only)
Port: 12443

Complete API reference for the Linux Patch API service.


Table of Contents


Overview

The Linux Patch API provides a secure REST interface for remote package and patch management. All operations require mTLS authentication and IP whitelist validation.

Design Principles:

  • Pure REST architecture (resources as nouns, HTTP verbs for actions)
  • Stateless authentication (no sessions)
  • Async operations for long-running tasks
  • Real-time status via WebSocket streaming
  • Standard JSON request/response envelope

Authentication

Requirements

All API requests must include:

  1. Valid Client Certificate

    • Signed by internal CA
    • Not expired (max 1-year validity)
    • Unique per client (no shared certificates)
  2. IP Whitelist Validation

    • Source IP must be in /etc/linux_patch_api/whitelist.yaml
    • Default: Deny all (block unless explicitly allowed)
    • Changes applied automatically (no restart required)

Connection Example

curl --cacert /etc/linux_patch_api/certs/ca.pem \
     --cert /etc/linux_patch_api/certs/client.pem \
     --key /etc/linux_patch_api/certs/client.key.pem \
     https://localhost:12443/api/v1/health

Authentication Failures

Condition Response
No certificate Silent drop (no response)
Invalid certificate Silent drop (no response)
Expired certificate Silent drop (no response)
IP not whitelisted Silent drop (no response)

Note: Failed authentication results in silent drop for security (no information leakage).


Standard Response Format

All API responses use this standard JSON envelope:

{
  "success": true,
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {},
  "error": null
}

Fields

Field Type Description
success boolean true for successful requests, false for errors
request_id UUID Unique identifier for request tracking and auditing
timestamp ISO 8601 Server timestamp of response (UTC)
data object Response payload (null on error)
error object Error details (null on success)

Error Handling

Error Response Format

{
  "success": false,
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": null,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable description",
    "details": {},
    "retryable": false
  }
}

Error Codes Reference

Code HTTP Status Description Retryable
AUTH_INVALID_CERT 401 Certificate validation failed No
AUTH_CERT_EXPIRED 401 Certificate has expired No
AUTHZ_IP_DENIED 403 IP not in whitelist No
PKG_NOT_FOUND 404 Package not found No
PKG_MANAGER_ERROR 500 Package manager operation failed Yes
PKG_DEPENDENCY_ERROR 400 Package dependency conflict No
PKG_VERSION_CONFLICT 400 Requested version not available No
PATCH_NOT_FOUND 404 Patch not found No
PATCH_APPLY_ERROR 500 Patch application failed Yes
JOB_NOT_FOUND 404 Job ID not found No
JOB_TIMEOUT 408 Job exceeded 30-minute timeout Yes
JOB_CANCELLED 400 Job was cancelled No
CONFIG_INVALID 400 Configuration validation failed No
CONFIG_RELOAD_ERROR 500 Configuration reload failed Yes
SERVICE_UNHEALTHY 503 Service not ready Yes
SYSTEM_REBOOT_ERROR 500 Reboot operation failed Yes
INVALID_REQUEST 400 Request body validation failed No
RATE_LIMIT_EXCEEDED 429 Too many requests Yes

Package Management Endpoints

POST /api/v1/packages

Description: Install one or more packages (async operation)

Request:

{
  "packages": [
    {
      "name": "nginx",
      "version": "1.24.0-1"
    },
    {
      "name": "openssl",
      "version": null
    }
  ],
  "options": {
    "force": false,
    "no_recommends": true,
    "allow_downgrade": false
  }
}

Request Fields:

Field Type Required Description
packages array Yes List of packages to install
packages[].name string Yes Package name
packages[].version string No Specific version (null for latest)
options object No Installation options
options.force boolean No Force reinstall (default: false)
options.no_recommends boolean No Skip recommended packages (default: false)
options.allow_downgrade boolean No Allow version downgrade (default: false)

Response (202 Accepted):

{
  "success": true,
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "job_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "status": "pending",
    "operation": "install",
    "packages": ["nginx", "openssl"],
    "created_at": "2026-04-09T13:04:02Z"
  },
  "error": null
}

Response Fields:

Field Type Description
job_id UUID Job identifier for status tracking
status string Initial status: pending
operation string Operation type: install
packages array List of package names
created_at ISO 8601 Job creation timestamp

GET /api/v1/packages

Description: List installed packages with filtering and sorting

Query Parameters:

Parameter Type Description Example
name string Filter by package name (supports * wildcard) nginx*
status string Filter by status: installed, upgradable, all upgradable
limit integer Maximum results (default: 100, max: 1000) 50
offset integer Pagination offset 100
sort string Sort field: name, version, size name
order string Sort order: asc, desc asc

Response (200 OK):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "packages": [
      {
        "name": "nginx",
        "version": "1.24.0-1",
        "status": "installed",
        "description": "High-performance web server",
        "size_bytes": 2458624,
        "installed_at": "2026-04-01T10:00:00Z",
        "upgradable": true,
        "available_version": "1.24.0-2",
        "dependencies": ["openssl", "libpcre3"],
        "maintainer": "nginx-team@example.com"
      }
    ],
    "total": 245,
    "limit": 100,
    "offset": 0
  },
  "error": null
}

GET /api/v1/packages/{name}

Description: Get details for a specific installed package

Path Parameters:

Parameter Type Description
name string Package name (URL-encoded)

Response (200 OK):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "name": "nginx",
    "version": "1.24.0-1",
    "status": "installed",
    "description": "High-performance web server",
    "size_bytes": 2458624,
    "installed_at": "2026-04-01T10:00:00Z",
    "upgradable": true,
    "available_version": "1.24.0-2",
    "dependencies": [
      {"name": "openssl", "version": ">=1.1.1", "required": true},
      {"name": "libpcre3", "version": ">=8.0", "required": true}
    ],
    "reverse_dependencies": ["nginx-module-vts"],
    "maintainer": "nginx-team@example.com",
    "homepage": "https://nginx.org",
    "license": "BSD-2-Clause",
    "files": [
      "/usr/sbin/nginx",
      "/etc/nginx/nginx.conf",
      "/var/log/nginx/access.log"
    ]
  },
  "error": null
}

PUT /api/v1/packages/{name}

Description: Update a specific package (async operation)

Path Parameters:

Parameter Type Description
name string Package name

Request:

{
  "version": "1.24.0-2",
  "options": {
    "force": false,
    "no_recommends": false
  }
}

Response (202 Accepted):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "job_id": "uuid",
    "status": "pending",
    "operation": "update",
    "package": "nginx",
    "target_version": "1.24.0-2"
  },
  "error": null
}

DELETE /api/v1/packages/{name}

Description: Remove a package (async operation)

Path Parameters:

Parameter Type Description
name string Package name

Query Parameters:

Parameter Type Description
purge boolean Remove config files (default: false)
force boolean Force removal despite dependencies (default: false)

Response (202 Accepted):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "job_id": "uuid",
    "status": "pending",
    "operation": "remove",
    "package": "nginx",
    "purge": false
  },
  "error": null
}

Patch Management Endpoints

GET /api/v1/patches

Description: List available security patches

Query Parameters:

Parameter Type Description
severity string Filter: critical, high, medium, low, all
status string Filter: available, applied, pending, all
limit integer Maximum results (default: 100)
offset integer Pagination offset
sort string Sort: severity, published_date, name
order string Order: asc, desc

Response (200 OK):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "patches": [
      {
        "id": "USN-6000-1",
        "name": "linux-security-update",
        "severity": "critical",
        "status": "available",
        "published_date": "2026-04-08T00:00:00Z",
        "description": "Security update for Linux kernel",
        "cve_ids": ["CVE-2026-1234", "CVE-2026-5678"],
        "affected_packages": ["linux-image-generic", "linux-headers-generic"],
        "requires_reboot": true
      }
    ],
    "total": 15,
    "limit": 100,
    "offset": 0
  },
  "error": null
}

POST /api/v1/patches/apply

Description: Apply security patches (async operation)

Request:

{
  "patches": ["USN-6000-1"],
  "options": {
    "reboot": false,
    "reboot_delay_minutes": 0,
    "exclude_packages": []
  }
}

Request Fields:

Field Type Required Description
patches array No Specific patch IDs (empty = all available)
options object No Application options
options.reboot boolean No Auto-reboot if required (default: false)
options.reboot_delay_minutes integer No Delay before reboot (default: 0)
options.exclude_packages array No Packages to exclude from update

Response (202 Accepted):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "job_id": "uuid",
    "status": "pending",
    "operation": "patch_apply",
    "patches_count": 1,
    "requires_reboot": true,
    "auto_reboot": false
  },
  "error": null
}

System Management Endpoints

GET /api/v1/system/info

Description: Get system information

Response (200 OK):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "hostname": "patch-server-01",
    "os": {
      "name": "Ubuntu",
      "version": "22.04 LTS",
      "codename": "jammy",
      "architecture": "x86_64"
    },
    "kernel": {
      "version": "5.15.0-100-generic",
      "architecture": "x86_64"
    },
    "uptime_seconds": 864000,
    "last_boot": "2026-04-01T00:00:00Z",
    "package_manager": "apt",
    "api_version": "1.0.0",
    "service_status": "running"
  },
  "error": null
}

GET /health

Description: Health check endpoint (no authentication required for monitoring systems)

Note: This endpoint may be configured to allow unauthenticated access for load balancer health checks.

Response (200 OK):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "status": "healthy",
    "version": "1.0.0",
    "uptime_seconds": 864000,
    "checks": {
      "config": "ok",
      "certificates": "ok",
      "package_manager": "ok",
      "job_queue": "ok"
    }
  },
  "error": null
}

POST /api/v1/system/reboot

Description: Initiate system reboot (async operation)

Request:

{
  "delay_seconds": 60,
  "force": false,
  "reason": "Scheduled maintenance"
}

Request Fields:

Field Type Required Description
delay_seconds integer No Delay before reboot (default: 0)
force boolean No Force reboot despite active jobs (default: false)
reason string No Reason for reboot (logged for audit)

Response (202 Accepted):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "job_id": "uuid",
    "status": "pending",
    "operation": "reboot",
    "scheduled_at": "2026-04-09T13:05:02Z",
    "reason": "Scheduled maintenance"
  },
  "error": null
}

Job Management Endpoints

GET /api/v1/jobs

Description: List jobs with filtering and sorting

Query Parameters:

Parameter Type Description
status string Filter: pending, running, completed, failed, cancelled
operation string Filter by operation type
limit integer Maximum results (default: 100)
offset integer Pagination offset
sort string Sort: created_at, updated_at, status
order string Order: asc, desc

Response (200 OK):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "jobs": [
      {
        "job_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
        "operation": "install",
        "status": "completed",
        "progress_percent": 100,
        "created_at": "2026-04-09T13:00:00Z",
        "updated_at": "2026-04-09T13:02:00Z",
        "completed_at": "2026-04-09T13:02:00Z"
      }
    ],
    "total": 50,
    "limit": 100,
    "offset": 0
  },
  "error": null
}

GET /api/v1/jobs/{id}

Description: Get detailed job status

Path Parameters:

Parameter Type Description
id UUID Job identifier

Response (200 OK):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "job_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "operation": "install",
    "status": "completed",
    "progress_percent": 100,
    "created_at": "2026-04-09T13:00:00Z",
    "updated_at": "2026-04-09T13:02:00Z",
    "completed_at": "2026-04-09T13:02:00Z",
    "packages": ["nginx"],
    "result": {
      "success": true,
      "packages_installed": ["nginx"],
      "packages_failed": []
    },
    "logs": [
      {"timestamp": "2026-04-09T13:00:01Z", "level": "info", "message": "Starting package installation"},
      {"timestamp": "2026-04-09T13:01:00Z", "level": "info", "message": "Downloading nginx 1.24.0-1"},
      {"timestamp": "2026-04-09T13:02:00Z", "level": "info", "message": "Installation complete"}
    ]
  },
  "error": null
}

Job Status Values:

Status Description
pending Job queued, waiting for execution
running Job currently executing
completed Job finished successfully
failed Job finished with errors
cancelled Job was cancelled by user
timeout Job exceeded 30-minute limit

POST /api/v1/jobs/{id}/rollback

Description: Rollback a completed job (async operation)

Path Parameters:

Parameter Type Description
id UUID Job identifier

Response (202 Accepted):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "job_id": "uuid",
    "status": "pending",
    "operation": "rollback",
    "original_job_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
  },
  "error": null
}

DELETE /api/v1/jobs/{id}

Description: Cancel a pending/running job or delete a completed job

Path Parameters:

Parameter Type Description
id UUID Job identifier

Response (200 OK):

{
  "success": true,
  "request_id": "uuid",
  "timestamp": "2026-04-09T13:04:02Z",
  "data": {
    "job_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
    "previous_status": "running",
    "current_status": "cancelled",
    "action": "cancelled"
  },
  "error": null
}

WebSocket Streaming

WS /api/v1/ws/jobs

Description: Real-time job status streaming

Connection:

const ws = new WebSocket('wss://localhost:12443/api/v1/ws/jobs', {
  cert: clientCert,
  key: clientKey,
  ca: caCert
});

Client Messages:

Type Payload Description
subscribe {"job_id": "uuid"} Subscribe to specific job
unsubscribe {"job_id": "uuid"} Unsubscribe from job
subscribe_all {} Subscribe to all jobs
ping {} Keep-alive ping

Server Messages:

Type Payload Description
job_status Job status object Job status update
job_complete Job result object Job completion notification
pong {} Ping response
error Error object WebSocket error

Example Flow:

ws.onopen = () => {
  // Subscribe to job updates
  ws.send(JSON.stringify({
    type: 'subscribe',
    job_id: '6ba7b810-9dad-11d1-80b4-00c04fd430c8'
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  
  if (data.type === 'job_status') {
    console.log('Job progress:', data.payload.progress_percent, '%');
  } else if (data.type === 'job_complete') {
    console.log('Job completed:', data.payload.result);
  }
};

Server Message Format:

{
  "type": "job_status",
  "payload": {
    "job_id": "uuid",
    "status": "running",
    "progress_percent": 45,
    "updated_at": "2026-04-09T13:01:30Z"
  }
}

Async Job Handling Guide

Understanding Async Operations

Long-running operations return immediately with a 202 Accepted status and a job_id. Clients must poll or use WebSocket to track completion.

Operations Using Async Pattern

Operation Endpoint Typical Duration
Package Install POST /api/v1/packages 10s - 5min
Package Update PUT /api/v1/packages/{name} 10s - 3min
Package Remove DELETE /api/v1/packages/{name} 5s - 2min
Patch Apply POST /api/v1/patches/apply 1min - 30min
System Reboot POST /api/v1/system/reboot 1min + reboot time
Job Rollback POST /api/v1/jobs/{id}/rollback 5s - 5min

Polling Strategy

import time
import requests

def wait_for_job(job_id, base_url, certs, poll_interval=2):
    """Poll job status until completion."""
    while True:
        response = requests.get(
            f"{base_url}/api/v1/jobs/{job_id}",
            cert=certs,
            verify=ca_cert
        )
        data = response.json()['data']
        
        if data['status'] in ['completed', 'failed', 'cancelled', 'timeout']:
            return data
        
        time.sleep(poll_interval)

Job Timeout

  • Default Timeout: 30 minutes
  • Timeout Behavior: Job marked as timeout, partial changes may exist
  • Recovery: Use rollback endpoint to revert changes

Concurrent Job Limits

  • Default: 5 concurrent jobs
  • Configuration: jobs.max_concurrent in config.yaml
  • Behavior: Additional jobs queued until slot available

Rate Limiting

Endpoint Category Limit Window
Health Check 60 requests 1 minute
Package List 30 requests 1 minute
Package Operations 10 requests 1 minute
Patch Operations 5 requests 1 minute
Job Operations 60 requests 1 minute
System Operations 5 requests 1 minute

Response on Limit Exceeded:

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Too many requests",
    "retryable": true,
    "details": {
      "retry_after_seconds": 30
    }
  }
}

Enrollment Endpoints

Enrollment endpoints enable new hosts to register with the Patch Manager and receive mTLS certificates for authenticated API access. These endpoints operate without client certificate authentication — security is enforced through rate limiting, single-use tokens, and admin approval workflows.

Base path: /api/v1/ (on the Patch Manager server) Authentication: None (pre-provisioning phase) Transport: HTTPS recommended; TLS verification intentionally relaxed on initial connection per security model

Cross-reference: SPEC.md §4.2 Enrollment Workflow · DEPLOYMENT_SECURITY_GUIDE.md


POST /api/v1/enroll

Description: Initiates a host self-enrollment request with the Patch Manager. The manager assigns a unique polling token that the host uses to check approval status.

Authentication: None (unauthenticated public endpoint)

Request Body

Field Type Required Description
machine_id string Yes Linux machine-id from /etc/machine-id
fqdn string Yes Fully qualified domain name of the host
ip_address string Yes Primary non-loopback IPv4 address
os_details object Yes OS metadata (free-form JSON object)
hostname string No Short hostname (without domain). Used by the manager to populate display_name on approval. If omitted, the manager falls back to the FQDN.

os_details common fields:

Field Type Description
name string Distribution name (e.g., Debian, Ubuntu)
version_id string OS version identifier (e.g., 12, 24.04)
kernel string Kernel release string (e.g., 6.1.0-kali9-amd64)
id_like string Family identifier (e.g., debian)

Request Example

curl -X POST https://manager.example.com/api/v1/enroll \
  -H "Content-Type: application/json" \
  -d '{
    "machine_id": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
    "fqdn": "host-01.example.com",
    "ip_address": "192.168.1.50",
    "os_details": {
      "name": "Debian",
      "version_id": "12",
      "kernel": "6.1.0-kali9-amd64",
      "id_like": "debian"
    },
    "hostname": "host-01"
  }'

Success Response (202 Accepted)

{
  "polling_token": "aB3dE6gH9jK2mN5pQ8rS1tU4vW7xY0zA"
}
Field Type Description
polling_token string 64-character alphanumeric bearer token for status polling. Treat as secret credential.

Error Responses

HTTP Status Body Description
429 { "error": "Rate limit exceeded. Try again in a minute." } Rate limit exceeded: 1 request/minute per source IP
500 { "error": "Database error" } Internal server or database error

GET /api/v1/enroll/status/{token}

Description: Returns the current approval status of an enrollment request. When approved, the response includes the complete PKI bundle (CA certificate, server certificate, and server private key) needed for mTLS provisioning.

Authentication: None (token serves as bearer credential)

Path Parameters

Parameter Type Description
token string 64-character alphanumeric polling token from POST /enroll response

Response Format

The endpoint returns a tagged JSON object with a status discriminator field. All responses return HTTP 200 OK — the status value determines the outcome.

Pending (Awaiting Admin Approval)
{ "status": "pending" }

The enrollment request has been received and is awaiting administrator review. The host should continue polling at regular intervals.

Approved (PKI Bundle Provided)
{
  "status": "approved",
  "ca_crt": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----",
  "server_crt": "-----BEGIN CERTIFICATE-----\nMIID...\n-----END CERTIFICATE-----",
  "server_key": "-----BEGIN PRIVATE KEY-----\nMIGH...\n-----END PRIVATE KEY-----"
}
Field Type Description
status string Always "approved" for this variant
ca_crt string PEM-encoded CA root certificate (for TLS verification)
server_crt string PEM-encoded server certificate (manager's TLS leaf)
server_key string PEM-encoded server private key (PKCS#8 format)
Denied
{ "status": "denied" }

The administrator has rejected the enrollment request. The host should abort the enrollment process.

Not Found (Token Expired or Invalid)
{ "status": "not_found" }

The polling token does not match any pending or approved enrollment. This occurs when:

  • The token has expired (default TTL: 24 hours)
  • The token was never issued
  • The enrollment was already fulfilled and purged

curl Examples

# Check enrollment status
curl https://manager.example.com/api/v1/enroll/status/aB3dE6gH9jK2mN5pQ8rS1tU4vW7xY0zA

# Extract PKI bundle when approved
curl -s https://manager.example.com/api/v1/enroll/status/$TOKEN | jq -r '.ca_crt' > /etc/linux_patch_api/certs/ca.crt
curl -s https://manager.example.com/api/v1/enroll/status/$TOKEN | jq -r '.server_crt' > /etc/linux_patch_api/certs/server.crt
curl -s https://manager.example.com/api/v1/enroll/status/$TOKEN | jq -r '.server_key' > /etc/linux_patch_api/certs/server.key.pem

Enrollment Flow Sequence

Complete step-by-step enrollment lifecycle from initial registration to mTLS provisioning:

┌──────────────┐         ┌──────────────┐         ┌──────────────┐
│   Linux Host  │         │ Patch Manager │         │  Admin UI    │
│  (linux_patch │         │   Server      │         │             │
│   _api)       │         │               │         │             │
└──────┬───────┘         └──────┬───────┘         └──────┬───────┘
       │                        │                        │
       │  1. POST /enroll       │                        │
       │  { machine_id, fqdn,   │                        │
       │    ip_address,         │                        │
       │    os_details }        │                        │
       │──────────────────────▶│                        │
       │                        │                        │
       │                        │  Store request + token │
       │                        │  (SHA256 hashed)       │
       │                        │                        │
       │  2. 202 Accepted       │                        │
       │  { polling_token }     │                        │
       │──────────────────────▶│                        │
       │                        │                        │
       │                        │  3. List pending       │
       │                        │  enrollments           │
       │                        │─────────────────────▶│
       │                        │                        │
       │                        │  Admin reviews &       │
       │                        │  approves request      │
       │                        │◀──────────────────────│
       │                        │                        │
       │                        │  Generate PKI bundle   │
       │                        │  (CA cert + server     │
       │                        │   cert + server key)   │
       │                        │                        │
       │  4. GET /enroll/status │                        │
       │  /{token}              │                        │
       │──────────────────────▶│                        │
       │                        │                        │
       │  5. 200 { status:      │                        │
       │     "approved",        │                        │
       │     ca_crt,            │                        │
       │     server_crt,        │                        │
       │     server_key }       │                        │
       │◀──────────────────────│                        │
       │                        │                        │
       │  6. Provision:         │                        │
       │  - Write certs to disk │                        │
       │  - Update whitelist    │                        │
       │  - Restart with mTLS   │                        │
       │                        │                        │

Step Details:

Step Action Details
1 Host sends enrollment request Extracts identity from /etc/machine-id, hostname, network interfaces, and OS release data
2 Manager returns polling token Token is 64-character random alphanumeric string; SHA256 hash stored in database
3 Admin reviews pending requests Manager exposes admin API for listing/approving/denying enrollment requests
4 Host polls status periodically Default interval: 60 seconds. Configurable via --poll-interval flag
5 Host receives PKI bundle on approval Complete CA chain, server certificate, and private key in PEM format
6 Host provisions mTLS infrastructure Writes certificates to configured paths, updates IP whitelist, transitions to authenticated mode

Rate Limiting

Endpoint Limit Window Scope
POST /api/v1/enroll 1 request Per minute Per source IP address
GET /api/v1/enroll/status/{token} No explicit limit Host-controlled polling interval

Rate Limit Enforcement:

  • POST /enroll: Enforced by manager using in-memory LRU cache keyed on source IP (or X-Forwarded-For first entry when behind reverse proxy)
  • Status endpoint: No server-side rate limiting; client controls poll frequency (default: 60s interval)

Security Notes

Concern Mitigation
Initial connection security TLS verification disabled on enrollment client (danger_accept_invalid_certs). Manager approval workflow provides authorization — transport encryption is secondary during pre-provisioning phase
Token secrecy Polling token is a 64-character random alphanumeric bearer credential. Never log the raw token value (only hash stored in DB). Tokens expire after 24 hours by default
Host identity machine_id from /etc/machine-id provides unique host identification. Combined with FQDN and IP for collision detection during admin approval
FQDN/IP collision Admin approval checks existing hosts table — rejects enrollment if FQDN or IP already registered to another host (HTTP 409 Conflict)
Certificate isolation Each approved host receives a unique client certificate signed by internal CA. Certificates have max 1-year validity

Error Reference Table

HTTP Status Error Context Description Retryable
429 POST /enroll Rate limit exceeded (1/min per IP) Yes — wait 60s
409 Admin approve endpoint FQDN or IP collision with existing host No — resolve conflict first
500 Any enrollment endpoint Database error or internal server failure Yes — transient
200 { "status": "denied" } GET /enroll/status/{token} Administrator rejected request No — contact administrator
200 { "status": "not_found" } GET /enroll/status/{token} Token expired, invalid, or already consumed No — re-enroll with new request

Support