Skip to main content
Version: vNext

๐Ÿ“„ Machine config schema

Every field in machine.yaml and what it means.

A CryptOS node is described by one YAML document, the machine config. It sets the node's role, its network, the administrator it trusts on first boot, and every certificate setting. The node has no other settings and no way to change them by hand. See Declarative config for the idea behind it.

The schema is long, so it is split across three pages:

  • This page: the document header, metadata, role, network, bootstrap, install, state_key and management, plus how the node reads and applies a config.
  • Machine config: pki: the CA key, subject and lifetime, the parent anchor, revocation, and certificate profiles.
  • Machine config: ACME and EST: the two enrolment protocols.

๐Ÿงพ A complete exampleโ€‹

A Root CA with a static address, a DNS server, and a revocation URL:

apiVersion: cryptos.dev/v1alpha1
kind: MachineConfig
metadata:
name: root-ca-1
role:
kind: root
network:
interface: eth0
address: 192.0.2.10/24
gateway: 192.0.2.1
nameservers: [192.0.2.53]
search: [example.org]
ntp_servers: [192.0.2.123]
bootstrap:
admin_cert_pem: |
-----BEGIN CERTIFICATE-----
(the bootstrap admin certificate from cryptosctl bootstrap)
-----END CERTIFICATE-----
install:
disk: /dev/sda
state_key:
mode: tpm
pki:
root_key_alg: ECDSA-P384
root_subject:
common_name: Example Root CA
organization: Example Org
country: US
root_validity_years: 20
revocation_base_url: http://pki.example.org

With a real certificate in admin_cert_pem, this document passes the node's validator as written.

๐Ÿ” How the node reads itโ€‹

  • Strict parsing. An unknown or misspelled key is an error, not something silently ignored. A stray name under role, for example, fails like this:

    config: parse YAML: yaml: unmarshal errors:
    line 5: field name not found in type config.Role
  • One error at a time. Validation stops at the first rule that fails and names the field path, for example config: network.gateway: must be IP: .... Fix it and validate again.

  • Checked on both ends. cryptosctl config apply validates the file on your machine before it sends anything, and the node validates again before it writes. A config the node rejects comes back as InvalidArgument and nothing is written.

  • No network checks at validation time. URLs are checked for shape only. Whether a name resolves or a server answers is checked at run time, because the node may validate its config before its network is up.

The node keeps the applied config at /var/lib/cryptos/config/machine.yaml on its encrypted state partition, next to a generation counter that goes up by one with every successful apply.

๐Ÿ“‹ Top-level fieldsโ€‹

FieldTypeRequiredMeaning
apiVersionstringyesMust be exactly cryptos.dev/v1alpha1.
kindstringyesMust be exactly MachineConfig.
metadataobjectnoOperator-facing name.
roleobjectyesThe CA role the node boots into.
networkobjectyesStatic IPv4 network settings.
bootstrapobjectyesThe administrator certificate trusted on first boot.
pkiobjectyesCA settings. See Machine config: pki.
installobjectfor an installThe disk the maintenance-mode installer writes to.
state_keyobjectnoHow the state-partition key is protected.
managementobjectnoSet when a FleetOS Manager links the node.
RuleError
apiVersion is not cryptos.dev/v1alpha1config: apiVersion: expected "cryptos.dev/v1alpha1", got "<value>"
kind is not MachineConfigconfig: kind: expected "MachineConfig", got "<value>"
Empty fileconfig: empty input

๐Ÿท๏ธ metadataโ€‹

FieldTypeDefaultMeaning
metadata.namestringemptyThe node's hostname, set at boot. It is not used for trust. Empty leaves the hostname unset.

๐ŸŽญ roleโ€‹

FieldTypeDefaultMeaning
role.kindstringnoneroot, intermediate or issuing. See CA roles.

A root node creates and self-signs its own key in the first-boot ceremony. An intermediate or issuing node generates its key at first boot, stages a CSR, and waits for its parent to sign it. It must pin that parent under pki.parent.

RuleError
Any other valueconfig: role.kind: must be one of "root"/"intermediate"/"issuing", got "<value>"

๐ŸŒ networkโ€‹

The node is IPv4 only. At boot it flushes any address the kernel got over DHCP and applies these settings, so the static address is the one that counts.

FieldTypeDefaultMeaning
network.interfacestringnoneThe interface name, for example eth0. Required.
network.addressstringnoneThe node's address in CIDR form, for example 192.0.2.10/24. The management API listens on this address, port 443. Required.
network.gatewaystringnoneThe default gateway, as an IP address. Required.
network.nameserverslist of stringsemptyUp to 3 DNS servers, as IPv4 addresses, tried in order. Empty falls back to the DNS servers from the kernel's DHCP lease, if it had one.
network.searchlist of stringsemptyUp to 6 DNS search domains, in order. Empty falls back to the domain from the DHCP lease, if any.
network.ntp_serverslist of stringsemptyUp to 3 time servers the node keeps its clock in sync with, each an IPv4 address or a hostname. A hostname is looked up at every poll. Empty falls back to the NTP servers from the kernel's DHCP lease (option 42); with neither, the node runs on its hardware clock. See Keep the clock in sync.
RuleError
interface emptyconfig: network.interface: required
address not CIDRconfig: network.address: must be CIDR: <reason>
gateway not an IP addressconfig: network.gateway: must be IP: <reason>
More than 3 nameserversconfig: network.nameservers: at most 3 entries, got <n>
A nameserver that is not IPv4config: network.nameservers[<i>]: must be an IPv4 address, got "<value>"
0.0.0.0, multicast or 255.255.255.255config: network.nameservers[<i>]: <address> is not a unicast address
The same nameserver twiceconfig: network.nameservers[<i>]: <address> is listed twice
More than 6 search domainsconfig: network.search: at most 6 entries, got <n>
A search domain that is not a valid DNS nameconfig: network.search[<i>]: <reason>, for example "<name>" has a label starting or ending with a hyphen
More than 3 time serversconfig: network.ntp_servers: at most 3 entries, got <n>
A time server that is neither an IPv4 address nor a valid hostnameconfig: network.ntp_servers[<i>]: must be an IPv4 address or a hostname: <reason>
An IPv6 time serverconfig: network.ntp_servers[<i>]: must be an IPv4 address, got "<value>"
An IPv4 time server that is 0.0.0.0, multicast or 255.255.255.255config: network.ntp_servers[<i>]: <address> is not a unicast address
The same time server twiceconfig: network.ntp_servers[<i>]: <value> is listed twice

A search domain may use letters, digits and hyphens, in labels of 1 to 63 characters, 253 characters at most, with an optional trailing dot.

An IPv6 address passes validation but not boot

The validator accepts any CIDR, but the node configures IPv4 only. With an IPv6 network.address the node fails at boot with netlink: configure: "<interface>" needs a valid IPv4 address/prefix. Use an IPv4 address.

Set nameservers when the revocation URL is a hostname

If pki.revocation_base_url names a host and network.nameservers is empty, cryptosctl config apply prints a WARNING: saying the node can resolve that host only if its DHCP lease supplies DNS servers. Without a resolver the revocation preflight fails and the node refuses all issuance. Set network.nameservers to servers that resolve the name.

A time server hostname needs nameservers too

If network.ntp_servers names a host and network.nameservers is empty, cryptosctl config apply prints a WARNING:: the node can look the host up only if its DHCP lease supplies DNS servers. If it can't, that server is never asked and, with no other server answering, the node refuses to sign until its clock syncs. Set network.nameservers, or name the server by IPv4 address. An empty ntp_servers also prints a WARNING:, which is expected on an offline Root.

๐Ÿ”‘ bootstrapโ€‹

The administrator certificate the node trusts on first boot. The first-boot ceremony must be run by this certificate (or over the node's local socket), and the ceremony then promotes it to the node's administrator. cryptosctl bootstrap makes one; see Bootstrap.

FieldTypeDefaultMeaning
bootstrap.admin_cert_pemstringemptyThe full certificate, as one PEM CERTIFICATE block.
bootstrap.admin_cert_sha256stringemptyThe certificate's SHA-256 fingerprint, as 64 hex characters.

Set exactly one of the two.

RuleError
Both or neither setconfig: bootstrap: exactly one of admin_cert_pem or admin_cert_sha256 is required
Fingerprint not 64 charactersconfig: bootstrap.admin_cert_sha256: must be 64 hex characters, got <n>
Fingerprint not hexconfig: bootstrap.admin_cert_sha256: not hex: <reason>
No PEM blockconfig: bootstrap.admin_cert_pem: no PEM block found
Wrong PEM typeconfig: bootstrap.admin_cert_pem: PEM type "<type>", want CERTIFICATE
More than one blockconfig: bootstrap.admin_cert_pem: must contain exactly one PEM block
Not a parseable certificateconfig: bootstrap.admin_cert_pem: parse: <reason>
Use admin_cert_pem on a node you manage over the network

A fingerprint-only config validates, but the node's mTLS listener needs the full certificate to verify clients against. With only admin_cert_sha256 set, the node stops at boot with init: ServerTLSConfig: the mTLS listener needs the full bootstrap admin certificate (PEM), not just a fingerprint, and PID 1 reboots. Put the whole certificate in admin_cert_pem.

๐Ÿ’ฝ installโ€‹

FieldTypeDefaultMeaning
install.diskstringemptyThe block device the maintenance-mode installer writes CryptOS to, for example /dev/nvme0n1. An installed node ignores it.

The validator doesn't check this field, but a maintenance-mode install refuses without it: apply-config: install.disk is required for a maintenance-mode install. See Install to disk.

The install disk is wiped

The installer erases the whole device named in install.disk. Check the name against the disks the node lists before you apply the config.

๐Ÿ”’ state_keyโ€‹

How the key for the encrypted state partition (and, with it, the CA key) is protected. See TPM-sealed keys.

FieldTypeDefaultMeaning
state_key.modestringemptytpm, nodeid, kms, or empty for the build-time default of the image that installs the node. Fixed at install. Standard images default to tpm; the nodeID image variant defaults to nodeid.
state_key.kms.endpointstringnoneThe base URL of the seal and unseal KMS. Required when mode is kms.
state_key.kms.trust_pemstringemptyA PEM CA bundle that verifies the KMS server's TLS certificate.
ModeState-partition keyCA key
tpmSealed by the TPMCreated and held inside the TPM. The TPM must support ECDSA P-384, and for an RSA CA key, that RSA size.
nodeidDerived from the machine's SMBIOS UUIDSoftware key, stored on the encrypted state partition.
kmsWrapped by the external KMSSoftware key, stored on the encrypted state partition.

Set the mode before the install. The node needs it before it can unlock the state partition, so on its first boot it reads state_key from the config the installer stages on the boot partition, and falls back to the image's build-time default when none is staged. Every later boot reads the mode from the state partition's LUKS2 header, which records how the partition was sealed, so the mode survives reboots and upgrades to an image built with a different STATEKEY. For kms, the endpoint also comes from the header rather than from the config.

RuleError
Unknown modeconfig: state_key.mode: must be one of "nodeid"/"tpm"/"kms" (empty = build default), got "<value>"
kms mode without a kms blockconfig: state_key.kms: required when state_key.mode is "kms"
Endpoint not an http or https URL with a hostconfig: state_key.kms.endpoint: must be an http(s) URL
A different mode on an installed node (FailedPrecondition, nothing stored)config: state_key.mode "<value>": this node's state volume is sealed in "<mode>" mode, which is fixed at install; reinstall the node to change it
nodeid mode is for development only

A machine's SMBIOS UUID is not a secret. In nodeid mode the state partition is tied to the machine, but anyone who can read the UUID and the disk can derive its key, and the CA key is a software key on that partition. Don't run a production CA in nodeid mode.

An RSA CA key in tpm mode needs a TPM with that RSA size

With mode: tpm and an RSA pki.root_key_alg, the CA key is created in the TPM, with the same protections as the ECDSA key. The TPM 2.0 spec only requires RSA-2048, and many TPMs implement nothing larger. If yours lacks RSA-3072 (or RSA-4096), the root ceremony fails with FailedPrecondition and an error containing tpm: key algorithm not supported by this TPM: RSA-3072, before any key is created, and you can run it again with another algorithm. An intermediate or issuing node creates its key at boot, so its boot stops on the same error. The node never falls back to a smaller size or a software key. The boot log line init: TPM capabilities: ... RSA key sizes [...] lists the sizes the TPM accepted. In nodeid and kms mode an RSA CA key is a software key and is not hardware-protected.

๐Ÿ›ฐ๏ธ managementโ€‹

Set by the FleetOS Manager when it links the node (the node's SetManagement RPC writes it). Leave it out on an unmanaged node. It takes effect on the next reboot.

FieldTypeDefaultMeaning
management.manager_cnstringnoneThe subject CN of the managing FleetOS Manager's operator identity. Required when the block is set.
management.trust_pemstringnoneThe PEM trust anchor (operator CA) for the FleetOS Manager. Required when the block is set.
management.operator_surface_readonlybooleanfalseMarks the node's own operator surface as read-only.
RuleError
manager_cn emptyconfig: management.manager_cn: required when management is set
trust_pem emptyconfig: management.trust_pem: required when management is set
Stored, not enforced

In this release the node validates and keeps the management block, but its mTLS listener still verifies clients against the bootstrap admin certificate only.

๐Ÿ” Applying a changeโ€‹

Apply a new config with cryptosctl config apply -f machine.yaml (see Apply a config) or from the FleetOS Manager. The reply names the new generation and whether the change needs a reboot:

Expected output

A successful apply prints one line. requires_reboot=false means the change is already live.

applied: generation=<n> requires_reboot=<true|false> digest=<sha256 of the stored YAML>
  • Live, no reboot: a change to pki.profiles, pki.root_leaf_issuance, pki.allow_unverified_revocation_url or pki.allow_unsynced_clock. The signer reads them from the stored config on every request, and the apply reports requires_reboot=false.
  • Reboot needed: everything else, including network (ntp_servers too), role, the revocation base URL and port, management, and switching ACME or EST on or off or changing their settings. These are read once at boot. Until the reboot, cryptosctl status prints Reboot: pending.
  • Refused: a different state_key.mode. The mode is fixed at install; see state_key.

cryptosctl config apply also prints a WARNING: line for each profile whose validity_days already runs past the node's own CA certificate. It's a warning; the config is still applied.

What an apply can't carry

cryptosctl config apply and the FleetOS Manager send the config as the API's MachineConfig message, which doesn't cover every YAML field. A profile's subject.province and subject.locality are dropped, because the profile subject in the message carries only common_name, organization and country. The CA's own root_subject keeps all five.

cryptosctl ceremony start --config machine.yaml sends your file as-is, so those fields survive there.

pki.acme and pki.est travel in the message, with their secrets write-only: cryptosctl config get prints them blank and an apply that leaves them blank keeps the stored ones. See Machine config: ACME and EST.

The MachineConfig message also has a storage field (state_partition_label, first_boot) that the node ignores. For the message itself, see the gRPC API reference.