Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ export SB_KEY=/path/to/sb.key SB_CERT=/path/to/sb.crt
task image PLATFORM=vmware STATEKEY=nodeid # or: task iso PLATFORM=vmware STATEKEY=nodeid
```

Keep `SB_CERT` set for the whole run: `rootfs:build` stamps it into the image as the upgrade anchor, and `uki:sign` signs the UKI and writes the detached `.uki.sig` with the same key. A node only stages later images signed by that key, so losing the key means re-provisioning to change anchors. Enroll the certificate in firmware `db` (vSphere, firmware UI, `sbctl`, or `efitools`) to boot with Secure Boot on, or run with Secure Boot off and rely on the stamped anchor for upgrades.
> [!IMPORTANT]
> Keep `SB_CERT` set for the whole run: `rootfs:build` stamps it into the image as the upgrade anchor, and `uki:sign` signs the UKI and writes the detached `.uki.sig` with the same key. A node only stages later images signed by that key, so losing the key means re-provisioning to change anchors. Enroll the certificate in firmware `db` (vSphere, firmware UI, `sbctl`, or `efitools`) to boot with Secure Boot on, or run with Secure Boot off and rely on the stamped anchor for upgrades.

Public release assets are unsigned, or a build recipe only. [`docs/secure-boot.md`](docs/secure-boot.md) is the full guide: key generation, enrollment, building, verifying with `sbverify` and openssl, upgrades, and key custody.

Expand All @@ -85,7 +86,8 @@ Each `v*` tag attaches these to its GitHub Release, all built by `task iso:unsig
| `cryptosctl-{linux,darwin}-{amd64,arm64}` | the static CLI, stamped with the release version |
| `SHA256SUMS` | SHA-256 of every asset above |

They are for evaluation with Secure Boot off. A node installed from one cannot be upgraded in place (it has no anchor), so for real use build with your own key as above. `task image:unsigned` and `task iso:unsigned` reproduce the assets locally; they clear `SB_CERT` for `rootfs:build` and never sign, even if `SB_KEY`/`SB_CERT` are exported.
> [!IMPORTANT]
> They are for evaluation with Secure Boot off. A node installed from one cannot be upgraded in place (it has no anchor), so for real use build with your own key as above. `task image:unsigned` and `task iso:unsigned` reproduce the assets locally; they clear `SB_CERT` for `rootfs:build` and never sign, even if `SB_KEY`/`SB_CERT` are exported.

## 🤖 Continuous integration

Expand Down Expand Up @@ -123,6 +125,9 @@ Issuing LDAPS and KDC certificates to Active Directory domain controllers, inclu

Most `config apply` changes report `requires_reboot=true`. Restart the node through its orderly shutdown rather than a hypervisor hard reset:

> [!NOTE]
> `cryptosctl` runs on Linux and macOS today. A Windows build is coming.

```sh
cryptosctl --endpoint pki-root.example:443 reboot --confirm "Example Root CA G1"
cryptosctl --endpoint pki-root.example:443 reboot --confirm "Example Root CA G1" --power-off
Expand Down
54 changes: 32 additions & 22 deletions build/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ The build host needs the kernel build deps (`build-essential`, `bc`, `flex`,
`cryptsetup`), and the UKI tooling: `systemd-ukify` + `systemd-boot-efi` (the
EFI stub). See `.github/workflows/ci-image.yml` for the exact apt list.

> [!IMPORTANT]
> **`ukify` needs the Python `pefile` module.** `systemd-ukify` does not depend
> on it, so `uki:assemble` fails with `ModuleNotFoundError: No module named
> 'pefile'` unless `pefile` is installed. On CI/system Python install the
Expand Down Expand Up @@ -165,10 +166,11 @@ TPM at all: the state-partition LUKS key is derived from the SMBIOS product UUID
stored on the encrypted state partition. `cryptosctl status` reports
`TPM: UNAVAILABLE` on such a node so the weaker posture is never hidden.

**Dev tier only.** A UUID is not secret, so the Root key's confidentiality rests
on the UUID: an attacker with both the disk image and the node UUID can recover
the Root key. Use `nodeid` to run CryptOS where a vTPM is unavailable, never for
a CA guarding real trust.
> [!CAUTION]
> **Dev tier only.** A UUID is not secret, so the Root key's confidentiality rests
> on the UUID: an attacker with both the disk image and the node UUID can recover
> the Root key. Use `nodeid` to run CryptOS where a vTPM is unavailable, never for
> a CA guarding real trust.

## Machine config delivery

Expand All @@ -186,12 +188,30 @@ config reaches a node exclusively via the install path:

## Maintenance install: operator workflow

> [!CAUTION]
> Security note: the maintenance API accepts unauthenticated clients by design
> (the Talos maintenance model). In this mode `config apply` erases the target
> disk and installs a config the caller supplies, then reboots into that
> configuration. Anyone who can reach the maintenance node on port 443 before the
> operator can therefore take over the node. Only expose a maintenance node on a
> trusted, isolated provisioning network, and complete the install before moving
> it onto a general network.

> [!IMPORTANT]
> The `--insecure` flag disables server-certificate verification and sends no
> client identity. It must only be used against a maintenance endpoint; running
> it against an established node's mTLS port would succeed only if the server
> also accepts unauthenticated clients, which it does not.

When a bare-metal node boots the CryptOS UKI for the first time it has no
state partition, so init enters maintenance mode and listens on a temporary
gRPC endpoint (default port 443) with a self-signed server certificate and
no client authentication. The operator sends the machine config from a
workstation on the same network:

> [!NOTE]
> `cryptosctl` runs on Linux and macOS today. A Windows build is coming.

```sh
cryptosctl \
--insecure \
Expand All @@ -204,24 +224,14 @@ cryptosctl \
device (for example `/dev/sda` or `/dev/nvme0n1`). The maintenance node reads
that field from the `ApplyConfig` RPC, partitions the disk, writes the UKI to
the ESP, copies the config to the state partition, and reboots into the
installed system. A successful apply prints:

```
applied: generation=1 requires_reboot=true digest=<sha256>
```

The `--insecure` flag disables server-certificate verification and sends no
client identity. It must only be used against a maintenance endpoint; running
it against an established node's mTLS port would succeed only if the server
also accepts unauthenticated clients, which it does not.

Security note: the maintenance API accepts unauthenticated clients by design
(the Talos maintenance model). In this mode `config apply` erases the target
disk and installs a config the caller supplies, then reboots into that
configuration. Anyone who can reach the maintenance node on port 443 before the
operator can therefore take over the node. Only expose a maintenance node on a
trusted, isolated provisioning network, and complete the install before moving
it onto a general network.
installed system.

> [!TIP]
> A successful apply prints:
>
> ```text
> applied: generation=1 requires_reboot=true digest=<sha256>
> ```

## Not covered here (separate issues)

Expand Down
22 changes: 17 additions & 5 deletions docs/acme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ of an operator moving every CSR by hand through `cryptosctl`. The usual clients
all work: certbot, lego and acme.sh on Linux; win-acme, Posh-ACME and Certify
The Web on Windows — with no AD CS anywhere in the chain.

> [!NOTE]
> Scope: `http-01` only. `dns-01` and wildcard names are not implemented, and a
> wildcard identifier is refused at order time rather than half-supported.

Expand Down Expand Up @@ -45,10 +46,11 @@ pki:
hmac_key_base64: <32+ bytes, base64url, no padding>
```

`base_url` must be what clients dial, not what the node binds. Every URL the
server hands out is built from it, and every request carries a signature over
the URL it was sent to, so a `base_url` that does not match the front end
breaks every request rather than degrading quietly.
> [!IMPORTANT]
> `base_url` must be what clients dial, not what the node binds. Every URL the
> server hands out is built from it, and every request carries a signature over
> the URL it was sent to, so a `base_url` that does not match the front end
> breaks every request rather than degrading quietly.

`http_port` is the origin port behind your TLS terminator. It defaults to 8555
and is deliberately not 80: port 80 belongs to the CRL/OCSP listener, which has
Expand All @@ -60,10 +62,20 @@ Accounts require an External Account Binding by default. An open ACME endpoint
on an internal CA is a broad grant, so the binding is what decides *who* may
enrol, while the `http-01` challenge decides *what* they may enrol for.

```sh
**Linux / macOS**

```bash
head -c 32 /dev/urandom | basenc --base64url | tr -d '='
```

**Windows (PowerShell)**

```powershell
$bytes = New-Object byte[] 32
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
[Convert]::ToBase64String($bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_')
```

Put the result in `hmac_key_base64` and hand the same value, with its `key_id`,
to whoever runs the client. Keys shorter than 32 bytes are rejected: nothing
rate-limits an offline guess against a captured binding.
Expand Down
39 changes: 23 additions & 16 deletions docs/active-directory.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@ The profile decides every extension. The CSR contributes only the subject and
the public key; SANs or EKUs requested in the CSR are ignored. So the names on a
domain controller's certificate are whatever its profile lists.

Set `revocation_base_url` on the issuing node. Windows checks revocation on both
certificates, and a DC certificate whose CRL cannot be fetched fails
validation. With the base URL set, every certificate carries a CRL distribution
point at `<base>/crl`, an AIA OCSP pointer at `<base>/ocsp`, and an AIA
caIssuers pointer at `<base>/ca.cer`, from which a client that trusts only the
root can fetch the issuing CA's certificate. Use an `http://` URL. Windows
fetches CRLs and AIA over plain HTTP, and HTTPS would make revocation checking
depend on another certificate.
> [!IMPORTANT]
> Set `revocation_base_url` on the issuing node. Windows checks revocation on both
> certificates, and a DC certificate whose CRL cannot be fetched fails
> validation. With the base URL set, every certificate carries a CRL distribution
> point at `<base>/crl`, an AIA OCSP pointer at `<base>/ocsp`, and an AIA
> caIssuers pointer at `<base>/ca.cer`, from which a client that trusts only the
> root can fetch the issuing CA's certificate. Use an `http://` URL. Windows
> fetches CRLs and AIA over plain HTTP, and HTTPS would make revocation checking
> depend on another certificate.

```yaml
pki:
Expand Down Expand Up @@ -126,6 +127,9 @@ certificate name the DC on the command line instead:
dns: [ad.example.org] # used only when no --dns is given
```

> [!NOTE]
> `cryptosctl` runs on Linux and macOS today. A Windows build is coming.

```sh
cryptosctl ca issue-leaf \
--endpoint 192.0.2.10:443 \
Expand Down Expand Up @@ -182,8 +186,9 @@ certreq -new dc01-ldaps.inf dc01-ldaps.req
certreq -new dc01-kdc.inf dc01-kdc.req
```

`certreq -new` keeps the new key as a pending request in the computer's store.
The certificate must be accepted on the same DC.
> [!IMPORTANT]
> `certreq -new` keeps the new key as a pending request in the computer's store.
> The certificate must be accepted on the same DC.

## Issuing

Expand Down Expand Up @@ -215,9 +220,10 @@ Check the extensions before installing:
openssl x509 -in dc01-kdc.cer -noout -ext subjectAltName,extendedKeyUsage,authorityInfoAccess
```

Expect `Signing KDC Response` and `Microsoft Smartcard Login` among the EKUs,
`othername: 1.3.6.1.5.2.2:<unsupported>` next to the DNS names, and the
`CA Issuers` URI under your `revocation_base_url`.
> [!TIP]
> Expect `Signing KDC Response` and `Microsoft Smartcard Login` among the EKUs,
> `othername: 1.3.6.1.5.2.2:<unsupported>` next to the DNS names, and the
> `CA Issuers` URI under your `revocation_base_url`.

## Trusting the chain

Expand Down Expand Up @@ -248,9 +254,10 @@ certutil -store My
certutil -verify -urlfetch dc01-kdc.cer
```

`certutil -verify -urlfetch` fetches the CRL, OCSP and AIA URLs and should end
with no errors. `certreq -accept` fails with a chain error if the root is not
yet trusted on the DC.
> [!TIP]
> `certutil -verify -urlfetch` fetches the CRL, OCSP and AIA URLs and should end
> with no errors. `certreq -accept` fails with a chain error if the root is not
> yet trusted on the DC.

To have AD DS load the LDAPS certificate without a reboot, apply an LDIF that
sets `renewServerCertificate` on the rootDSE. `renew.ldf`:
Expand Down
27 changes: 18 additions & 9 deletions docs/certificate-profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,12 @@ With `validity_policy: cap`, the default, the certificate is issued and ends at
the issuer's notAfter. `cryptosctl` prints a warning on stderr, and the
certificate on stdout is unchanged:

```
WARNING: requested validity ends 2046-09-22; capped to issuer notAfter 2041-09-21
```
> [!TIP]
> Expected output on stderr when the cap applies:
>
> ```text
> WARNING: requested validity ends 2046-09-22; capped to issuer notAfter 2041-09-21
> ```

The audit entry for the `IssueLeaf` or `SignSubordinateCSR` call records both
dates as `requested_not_after` and `effective_not_after`. ACME and EST issue the
Expand All @@ -63,19 +66,25 @@ With `validity_policy: reject`, the node refuses to issue instead. The call
fails with `FailedPrecondition` and a message naming both dates, before the CA
key is loaded:

```
node: profile "platform-sub-ca" has validity_policy reject: requested validity ends 2046-09-22, after issuer notAfter 2041-09-21
```
> [!TIP]
> Expected output when the profile refuses the request:
>
> ```text
> node: profile "platform-sub-ca" has validity_policy reject: requested validity ends 2046-09-22, after issuer notAfter 2041-09-21
> ```

Use `reject` where a shortened certificate would cause trouble later. One
example is a platform CA whose renewal is planned around a fixed lifetime.

`cryptosctl config apply` also warns when a profile's `validity_days` already
runs past the node's CA certificate, for example:

```
WARNING: profile "platform-sub-ca": validity_days 7300 runs past this CA's notAfter 2041-09-21; its certificates will be capped to that date
```
> [!TIP]
> Expected output from `config apply` when a profile outlives the CA:
>
> ```text
> WARNING: profile "platform-sub-ca": validity_days 7300 runs past this CA's notAfter 2041-09-21; its certificates will be capped to that date
> ```

This is a warning, not a validation error. The config is still applied,
because every profile eventually crosses the line as the CA's remaining
Expand Down
Loading