Skip to content

Policy Server Deployment and Operation

aigo-policy-server is the central serving spine for an organization's signed enterprise policy. It holds the organization's private Ed25519 signing key, serves the signed policy that every desktop and headless client verifies with only the public trust anchor, and handles device enrollment.

The policy server is server-side and private. It is a deliberately separate artifact from aigo-server (the inference server) and from the desktop app, and it is not bundled with the client. Keeping the signing key and the authored policies out of the artifact that ships to every user and inference node is least privilege: the signing key is the root of trust, so it lives only on the policy server you operate.

For the policy document schema (what a policy may target, lock, and gate), see the Policy Schema Reference. This page covers running the server.

What the policy server does

The server has one job split across four endpoints:

  • It validates a one-time enrollment token and returns the public trust anchors plus polling metadata, so a new device can verify future policies.
  • It serves the current signed policy.
  • It serves a cheap policy id plus issue date for poll-without-download.
  • It accepts signed client audit batches.

It does not run the inference runtime, the continuum-router, or any model loader. It reuses the shared aigo-policy crate for the policy schema and Ed25519 signing, and the same authenticated REST stack as the headless server.

Prerequisites

Before standing up the server, plan the following.

  • A host reachable by your managed devices over HTTPS (terminate TLS at a reverse proxy in front of the server, or on the network path).
  • A secure location for the private signing key, with access limited to the operators who manage policy.
  • An authored EnterprisePolicy JSON document (see the Policy Schema Reference).

Generate the signing key

The signing key is a PKCS#8 v2 Ed25519 key. Generate one and store it securely:

aigo-policy-server --generate-signing-key > policy-signing.pk8.b64

The key is printed as base64 to stdout (the human-readable notice goes to stderr, so the redirect captures only the key). This is the root of trust for your fleet. Store it in a secrets manager or an access-controlled file, back it up, and never commit it or place it on a client.

You may also keep the key as a raw binary PKCS#8 file. The server accepts either a raw PKCS#8 document or its base64 text encoding from --signing-key-file.

Extract and distribute the trust anchor

Clients verify the signed policy with the public trust anchor only. Print it from the signing key:

aigo-policy-server \
    --signing-key-file policy-signing.pk8.b64 \
    --anchor-id org-2026 \
    --print-anchor

This prints a JSON trust anchor (an id and a base64 public key). The --anchor-id you choose is recorded in the served policy's anchorId and must match the anchor id you provision into clients.

You distribute this public anchor to devices inside the provisioning profile (trustAnchors). When a device enrolls against this server, the server returns the same anchor derived from this key, so a freshly enrolled device can verify the policy it then fetches. See Device Enrollment and Trust Anchors.

Author and sign the policy

Write your EnterprisePolicy document (for example /etc/aigo/policy.json) following the Policy Schema Reference. The server can sign it at startup, or serve an already-signed document verbatim.

Sign an authored policy at startup with the signing key:

aigo-policy-server \
    --signing-key-file /etc/aigo/policy-signing.pk8 \
    --policy-file /etc/aigo/policy.json \
    --anchor-id org-2026

Serve a pre-signed SignedPolicy document verbatim (no signing key needed at runtime, useful when signing happens on a separate, more isolated host):

aigo-policy-server --signed-policy-file /etc/aigo/signed-policy.json

--policy-file and --signed-policy-file are mutually exclusive. The signing key may also be supplied through AIGO_POLICY_SIGNING_KEY (base64) or AIGO_POLICY_SIGNING_KEY_FILE. It is never read from the wire.

Validate the configuration without starting the server with --dry-run. It confirms the policy parses, the schema is in range, and the key signs, then exits.

Configure device enrollment

Enrollment is offered only when you configure one or more one-time tokens. Each token is single-use: it is consumed on the first successful enrollment and cannot be reused. A token can optionally bind the enrolling device to an organization label with TOKEN:ORG.

Pass tokens on the command line (repeat for several):

aigo-policy-server \
    --signing-key-file /etc/aigo/policy-signing.pk8 \
    --policy-file /etc/aigo/policy.json \
    --anchor-id org-2026 \
    --enrollment-token tok-alice:acme \
    --enrollment-token tok-bob:acme \
    --poll-interval-secs 900

Or supply a JSON file mapping tokens to organization labels (null binds without an org). Keep the file operator-only readable; tokens are never logged in clear:

{
  "tok-alice": "acme",
  "tok-bob": "acme",
  "tok-guest": null
}
aigo-policy-server \
    --signing-key-file /etc/aigo/policy-signing.pk8 \
    --policy-file /etc/aigo/policy.json \
    --anchor-id org-2026 \
    --enrollment-tokens-file /etc/aigo/enrollment-tokens.json

The poll interval you set (--poll-interval-secs, default 900) is returned to the device and controls how often it polls for an updated policy. When no enrollment tokens are configured, enrollment is simply not offered and POST /api/v1/enroll returns 503.

HTTP surface

The server binds to --host (default localhost, env AIGO_POLICY_HOST) and --port (default 8444, env AIGO_POLICY_PORT). The default port differs from the headless aigo-server default (8001) so a policy server and an inference server can share one box. Authentication is always required: the server overrides an auth-disabled config, because the policy and audit routes rely on device-signature verification before the enterprise_read scope check runs.

Method Route Auth Purpose
POST /api/v1/enroll One-time enrollment token Validates the token, returns trust anchors and poll interval
GET /api/v1/policy enterprise_read Returns the current SignedPolicy
GET /api/v1/policy/version enterprise_read Returns the policy id and issue date for cheap polling
POST /api/v1/audit Enrolled device signature Accepts signed client audit batches

Device-signature auth grants the narrow enterprise_read scope to enrolled devices. API-key and session auth remain available for operator and admin flows. Client audit reporting is covered in Audit and Compliance.

Run as a service (systemd)

Run the server under a service supervisor so it restarts on failure and starts on boot. The unit below mirrors the hardening of the bundled aigo-server unit. Adjust paths, the user, and the listen address for your environment.

[Unit]
Description=Backend.AI GO Policy Server
Documentation=https://github.com/lablup/backend.ai-go
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=aigo
Group=aigo
Environment=AIGO_POLICY_SIGNING_KEY_FILE=/etc/aigo/policy-signing.pk8
ExecStart=/usr/bin/aigo-policy-server \
    --host 0.0.0.0 \
    --port 8444 \
    --policy-file /etc/aigo/policy.json \
    --anchor-id org-2026 \
    --enrollment-tokens-file /etc/aigo/enrollment-tokens.json
ExecReload=/bin/kill -HUP $MAINPID

NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
ReadOnlyPaths=/etc/aigo
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

Keep the signing key and the enrollment-tokens file readable only by the service user (chmod 600, owned by root:aigo or the service account). The ReadOnlyPaths=/etc/aigo line lets the service read its key and policy while preventing the process from modifying them.

Run in a container

The policy server runs cleanly in a container. Mount the signing key and policy as read-only, and publish the port behind your TLS terminator. Add a health check against the cheap version route:

HEALTHCHECK --interval=30s --timeout=5s \
  CMD curl -fsS http://localhost:8444/api/v1/policy/version \
      -H "Authorization: Bearer $AIGO_HEALTHCHECK_KEY" || exit 1

Inspect the resolved health state with docker inspect:

docker inspect --format='{{.State.Health.Status}}' aigo-policy-server

Verify the deployment

After starting the server, confirm it serves a policy and that the version route responds. The version route is the cheapest check and is what clients poll:

curl -fsS https://policy.acme.example/api/v1/policy/version \
    -H "Authorization: Bearer <operator-api-key>"

A healthy response carries the policy id (the policyId from your document) and its issue date. Clients compare this id against their cached id and only download the full policy when it changes.

Command reference

Flag Env Purpose
--signing-key-file AIGO_POLICY_SIGNING_KEY_FILE PKCS#8 Ed25519 signing key (raw or base64).
--anchor-id AIGO_POLICY_ANCHOR_ID Trust-anchor id recorded in anchorId and provisioned into clients.
--policy-file AIGO_POLICY_FILE Authored EnterprisePolicy JSON, signed at startup.
--signed-policy-file AIGO_POLICY_SIGNED_FILE Pre-signed SignedPolicy JSON, served verbatim.
--enrollment-token One-time token TOKEN[:ORG]. Repeatable, single-use.
--enrollment-tokens-file AIGO_POLICY_ENROLLMENT_TOKENS_FILE JSON map of token to org (null for no org).
--poll-interval-secs AIGO_POLICY_POLL_INTERVAL_SECS Poll interval returned to enrolling devices (default 900).
--host / -H AIGO_POLICY_HOST Bind host (default localhost).
--port / -p AIGO_POLICY_PORT Bind port (default 8444).
--data-dir / -D AIGO_POLICY_DATA_DIR Application data directory (access keys, settings).
--config / -c AIGO_POLICY_CONFIG TOML config file (reuses the aigo-server config shape).
--print-anchor Print the public trust anchor and exit.
--generate-signing-key Print a fresh base64 PKCS#8 Ed25519 key and exit.
--no-socket Disable the Unix domain socket listener (TCP only).
--dry-run Validate config, policy, and key, then exit.
--verbose / -v Increase logging verbosity (-v, -vv).
--quiet / -q Suppress non-error output.

Next steps