Access and security

Who should have access to the DataMind Installer?

A very small number of ultra-privileged system administrators — in practice two or three people, never a team. Installer access is not application administration; it is effectively infrastructure administration for the whole host.

The reason is what the Installer can reach:

CapabilityConsequence
Mounts the host Docker socketAccess to the Docker socket is equivalent to root on the host
Renders and writes the deployment .envEvery service credential in the deployment passes through it
Reads system_secretsIt decrypts the Azure client secret and generated values
Runs a privileged, host-PID container for migrationThat container runs the retirement script as root inside the host's namespaces
Reads the Jenkins configuration mount read-onlyLegacy credentials in config.xml are readable through the import
Replaces the DataMind OS stackIt can stop, kill and recreate every service the business depends on

Grant it the way you grant root: named individuals, no shared accounts, and only for as long as the work requires it.

Why is Installer access effectively host-root access?

Because the Installer drives the host's Docker daemon, and Docker daemon access is root access. The compose file mounts the socket in directly:

yaml
- ${DOCKER_SOCK:-/var/run/docker.sock}:/var/run/docker.sock

Anyone who can ask that daemon to run a container with a host bind mount can read or write anything on the host. The Installer itself does not exploit that, but the capability is inherent to the design: an orchestrator that runs docker compose on your behalf must be able to. Treat "can log in to the Installer" and "is a system administrator on this host" as the same statement.

Warning

Do not expose the Installer's port to the internet, and do not put it behind an authenticating proxy and treat that as the security boundary. The boundary is who holds an Installer account.

Are Installer users connected to DataMind OS application users?

No. They are two entirely separate identity stores, with no shared accounts and no single sign-on. The Installer keeps its own users table, signs its own session tokens with jwt.secret from its own secrets volume, and has its own login endpoint. DataMind OS users belong to the DataMind OS stack and are managed there.

The only bridge between the two systems is a shared service token:

DirectionMechanismScope
DataMind OS → InstallerCURATO_SERVICE_TOKEN, presented as a bearer tokenFirst-party service call; treated as an administrator-level caller
Acting user's identityx-actor-user-id and x-actor-email headersAttribution only — used so the audit trail records a person; explicitly not what grants access
Important

Creating a user in DataMind OS does not create an Installer user, and deleting an Installer user does not remove any DataMind OS access. When someone changes roles, both stores need attention.

What roles exist in the Installer?

Two: admin and user. Every role check in the API names one of these.

RoleIntended meaning
adminMay change configuration, manage users, and start, stop, update or recreate the deployment
userMay sign in and see status and logs; may not change anything about the deployment's configuration or lifecycle

The interface hides or disables admin-only controls for a user, and the API enforces the same rules independently — the decoration of a button is not the control.

What can a non-admin user actually do?

More than "read-only" suggests, and one capability in particular deserves attention. A user can call any authenticated endpoint that declares no role requirement:

Allowed for userNot allowed for user
Read configuration (secret values masked) and the memory-limits calculationChange any configuration value, generate .env, import from Jenkins, re-seed
Read deployment status, groups, stale services and the update checkDeploy, pull, compose up, restart, apply changes, stop, kill, cancel a job
Read the version and check for a newer Installer buildRead the deployment action audit trail
Stream container logs and job logsRead or download the dedicated deployment-log feed
Read release notesManage users, roles, activation; set the Azure secret; set the VM user; run host migration
Change their own password

Two consequences are worth stating plainly:

If that is wider than your policy allows, keep the user role unused and give administrator accounts only to the small set of people from the first question.

How do I create the first administrator?

Register it from the login screen. The first account ever created is an administrator, and that can happen only once. The registration endpoint is public until any user exists, after which it is refused with a forbidden error and only an administrator can create accounts.

The password policy applies from the start: at least 8 characters, with an uppercase letter, a lowercase letter, a number and a symbol.

How do I add another administrator?

Sign in as an administrator and use the Register User view, choosing the Admin role. The role selector is only shown to administrators, and the API refuses account creation from a non-admin.

bash
curl -X POST http://localhost:8000/api/user/register \
  -H "Authorization: Bearer <access-token>" \
  -H "Content-Type: application/json" \
  -d '{"email":"ops@example.com","password":"<strong-password>","role":"admin"}'

An administrator can also promote or demote an existing account, but cannot change their own role — that check exists so you cannot accidentally lock yourself out of administration.

What happens when an administrator leaves?

Deactivate or delete the account, then rotate anything that person could have seen. In order:

  1. Sign in as another administrator and either deactivate the account (keeps the record, blocks sign-in immediately) or delete it (soft-deletes the record and removes its refresh tokens).
  2. If they were the only other administrator, make sure at least one administrator remains — you cannot deactivate or delete your own account, so there is no way to empty the role by accident.
  3. If that person ever handled encr.key, jwt.secret, the Azure client secret or the service token, treat those as disclosed and rotate what you can. See the rotation questions in Configuration and variables.
Note

Deactivation and deletion both revoke the account's refresh tokens, so existing sessions cannot be renewed. Access tokens already issued remain valid until they expire — which is the argument for short access-token lifetimes.

How do I deactivate a user without deleting them?

Set the account inactive; sign-in is then refused and existing refresh tokens are rejected. From the Users panel, or:

bash
curl -X PATCH http://localhost:8000/api/user/<id> \
  -H "Authorization: Bearer <access-token>" \
  -H "Content-Type: application/json" \
  -d '{"isActive":false}'

A deactivated account gets a forbidden response at sign-in and its refresh attempts are revoked. Reactivating restores access immediately. You cannot deactivate your own account.

Does deleting a user revoke their sessions?

Yes — the refresh tokens are deleted outright, and the account is soft-deleted. A soft delete keeps the row (so the audit trail still resolves the user's email) while removing the account from the active list.

Registering the same email address later revives the row rather than creating a duplicate.

Do Installer sessions expire, and can I shorten them?

Yes. Access tokens default to 30 minutes and refresh tokens to 7 days, both configurable. Sessions are cookie-based: an access token scoped to /api and a refresh token scoped to /api/auth, both httpOnly, so script in the page cannot read them.

Tokens are not issued once and trusted forever: the refresh token is stored only as a hash, is rotated on every refresh, and is invalidated by a new sign-in, a password change, a password reset, deactivation or deletion. The strategy also rejects any token issued before the account's last password change.

Tip

Shortening access-token lifetimes is the cheapest way to bound how long a departed administrator's live session can last. Set JWT_ACCESS_EXPIRY in the Installer's own .env and recreate the backend.

Where is the audit trail of who did what?

In the deployment actions table, served as a paginated, administrator-only list.

bash
curl -s "http://localhost:8000/api/deployment/actions?limit=50&offset=0" \
  -H "Authorization: Bearer <access-token>"

Each row records the action, the target services, the job id, the outcome, the exit code, the detail and the acting user resolved to an email address. Actions include restart, stop, kill, pull, compose-up, deploy, apply-config, host migration, job cancellation and self-update.

Note

The audit trail covers deployment actions. It is not a general access log: reads of configuration or logs are not recorded as actions.

How does DataMind OS authenticate to the Installer?

With a shared service token generated by the Installer on first boot. The value is 48 alphanumeric characters, stored at delamain_secrets/curato.token, compared in constant time, and injected into the deployment .env as CURATO_SERVICE_TOKEN on every render.

When a request presents that token, the Installer accepts it as an administrator-level service caller without requiring an Installer user row. The caller may forward the acting user's id and email in headers, which are used only to attribute the action in the audit trail.

Important

The acting-user headers are not an authorization mechanism — the code says so explicitly. Do not build anything that treats a forwarded user id as proof of identity.

What is the risk of the shared service token?

It is a long-lived bearer credential that bypasses user accounts and role checks. Anything that can read the deployment .env — where the token is written beside the deployment's own service credentials — can act on the Installer as an administrator. That includes starting a self-update.

Mitigations that exist in the design:

Mitigations that are yours to apply: treat the deployment .env as a secret file, restrict who can read it on the host, and rotate the token deliberately if it is ever exposed.

Should the Installer's UI be exposed to the internet?

No. Keep it on the management network, reached over SSH or a VPN. The Installer is an infrastructure control plane with Docker-socket privileges; the right answer is to make it unreachable from the internet rather than to harden it exposed.

Two supporting details: the interface is served by the Installer itself on the same origin as the API, so there is no separate front-end host to secure; and the cookie Secure flag is set from the request, not from a hardcoded assumption, so if you terminate TLS in front of the Installer the protective flag is applied automatically.

Warning

If you do put the Installer behind TLS, verify it end to end. A cookie offered over plain HTTP will be sent without the Secure attribute, and the interface will keep working — which hides the problem until something inspects the traffic.

How do I rotate the Installer's signing secret?

Stop the Installer, replace jwt.secret in the secrets volume, and start it again. The secret is read at boot; there is no in-place rotation endpoint. Every issued token becomes invalid, so everyone signs in again — which is exactly why this is the right action when you suspect a token leak.

With the Installer's containers stopped, from the host:

bash
openssl rand -hex 64 > <secrets-volume-path>/jwt.secret
Note

The secret is not the same as the encryption key. Rotating jwt.secret invalidates sessions; changing encr.key destroys stored secrets. Do not confuse the two.

Is the Installer's database shared with DataMind OS?

No. The Installer runs its own PostgreSQL container and its own database. It is not published on the host in the production compose file, and its password lives in the secrets volume rather than in .env.

DataMind OS has its own databases inside the stack it deploys. Nothing in the Installer reads or writes those.

What does the Installer run with elevated privileges, and when?

One thing only, and only on demand: the host-migration helper. Starting host migration copies migrate-host.sh to the host mount and launches an ephemeral container with --privileged --pid=host that uses nsenter to run the script as root inside the host's namespaces.

The script acts on a fixed allowlist of legacy systemd units, plus NFS and stray old containers — it never touches Docker itself, the OS, or Jenkins. It is the one destructive operation in the product, it is administrator-only, and it is worth reading before you run it. See Migrating from bare metal.

Everything else the Installer does is ordinary docker compose work through the socket.

How are stored secrets protected at rest?

With AES-256-GCM under a key generated on first boot. Secret configuration values and generated values are encrypted inside the Installer's database, and the system secrets table uses the same scheme, with a version prefix on the ciphertext. The key itself lives in delamain_secrets/encr.key, written with mode 0600.

In GET /api/configs, secret values are replaced with a fixed mask for any caller who is not an administrator, so a user cannot read them even though they can read the rest of the configuration.

For how to protect and restore those files, see Data and backups.

The access model in full is in Who should have access, Identity and users and Host privilege and the Docker socket; Network exposure covers where the Installer should sit on your network.