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
5 changes: 3 additions & 2 deletions cmd/cryptosctl/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ func newConfigGetCmd(opts *globalOpts) *cobra.Command {
"hmac_key_base64 and password_sha256 are write-only. Leave a blank secret " +
"as it is and `config apply` keeps the one the node stores for that key_id " +
"or username; set it to replace it. A new key_id or username needs its " +
"secret. Switching a protocol on or off, or changing its settings, takes " +
"effect at the next reboot.",
"secret. Switching a protocol on or off, or changing the settings of one " +
"that is on, takes effect at the next reboot. A block with enabled: false " +
"keeps its settings; changing only those needs no reboot.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
client, closeConn, err := dial(opts)
Expand Down
38 changes: 29 additions & 9 deletions docs/acme.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,21 +140,41 @@ workflow, and accepting the request would imply one.
## Switching it on, changing it and switching it off

ACME runs on an intermediate or issuing node only. A Root serves no enrolment
protocol, so a config that sets `pki.acme` on a Root is refused by
protocol, so a config that switches `pki.acme` on at a Root is refused by
`config apply`, by the maintenance install and by `ceremony start --config`.

> [!CAUTION]
> Don't add `pki.acme` to a Root's config. It is refused, and nothing is
> stored. Serve ACME from an issuing node under that Root instead.
> Don't switch `pki.acme` on in a Root's config. It is refused, and nothing
> is stored. Serve ACME from an issuing node under that Root instead. A
> Root accepts a block with `enabled: false`, and never serves it.

To switch ACME on, add the `pki.acme` block and run `config apply`. To
switch it off, remove the block and apply again. Over the API the block is
`Pki.acme` with an explicit `enabled` flag: `enabled: false` switches it
off, and leaving the block out of an `ApplyConfig` keeps what the node has.
switch it off and keep its settings, set `enabled: false` in the block and
apply again; to switch it back on, set `enabled: true` (or remove the line).
A block without `enabled` is on. Removing the block switches ACME off and
drops its settings.

```yaml
pki:
acme:
enabled: false
# the rest of the block stays as it was
```

The node stores a switched-off block's settings and `config get` prints the
block with `enabled: false`, so flipping `enabled` alone is enough to switch
it back on. The Fleet Manager switches a protocol the same way. Nothing reads
a switched-off block at boot, so changing only its settings does not need a
reboot.

Over the API the block is `Pki.acme` with an explicit `enabled` flag.
`enabled: false` switches it off and keeps the settings sent with it, an
`enabled: false` block with no settings drops them, and leaving the block out
of an `ApplyConfig` keeps what the node has, on or off.

> [!WARNING]
> Every ACME change, switching it on or off included, takes effect at the
> next reboot, not when you apply it. `config apply` stores the change and
> Switching ACME on or off, or changing its settings while it is on, takes
> effect at the next reboot, not when you apply it. `config apply` stores the change and
> prints `requires_reboot=true`; the listener starts, stops or picks up the new
> settings only when the node boots again. Plan the reboot for a maintenance
> window, because the node stops issuing while it restarts, then use
Expand All @@ -167,7 +187,7 @@ Protocols: ACME on (not running, reboot pending), EST off, SCEP off
Reboot: pending (the stored config changes take effect at the next boot)
```

`hmac_key_base64` is write-only. `cryptosctl config get` prints it blank, with its
`hmac_key_base64` is write-only, in a switched-off block too. `cryptosctl config get` prints it blank, with its
`key_id`. Leave a blank value as it is and `config apply` keeps the one the
node stores for that `key_id`; set a value to replace it. A new `key_id`
needs its value, and an apply that leaves it blank is refused. Removing an
Expand Down
38 changes: 29 additions & 9 deletions docs/est.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,21 +202,41 @@ is routing rather than decoration.
## Switching it on, changing it and switching it off

EST runs on an intermediate or issuing node only. A Root serves no enrolment
protocol, so a config that sets `pki.est` on a Root is refused by
protocol, so a config that switches `pki.est` on at a Root is refused by
`config apply`, by the maintenance install and by `ceremony start --config`.

> [!CAUTION]
> Don't add `pki.est` to a Root's config. It is refused, and nothing is
> stored. Serve EST from an issuing node under that Root instead.
> Don't switch `pki.est` on in a Root's config. It is refused, and nothing
> is stored. Serve EST from an issuing node under that Root instead. A
> Root accepts a block with `enabled: false`, and never serves it.

To switch EST on, add the `pki.est` block and run `config apply`. To
switch it off, remove the block and apply again. Over the API the block is
`Pki.est` with an explicit `enabled` flag: `enabled: false` switches it
off, and leaving the block out of an `ApplyConfig` keeps what the node has.
switch it off and keep its settings, set `enabled: false` in the block and
apply again; to switch it back on, set `enabled: true` (or remove the line).
A block without `enabled` is on. Removing the block switches EST off and
drops its settings.

```yaml
pki:
est:
enabled: false
# the rest of the block stays as it was
```

The node stores a switched-off block's settings and `config get` prints the
block with `enabled: false`, so flipping `enabled` alone is enough to switch
it back on. The Fleet Manager switches a protocol the same way. Nothing reads
a switched-off block at boot, so changing only its settings does not need a
reboot.

Over the API the block is `Pki.est` with an explicit `enabled` flag.
`enabled: false` switches it off and keeps the settings sent with it, an
`enabled: false` block with no settings drops them, and leaving the block out
of an `ApplyConfig` keeps what the node has, on or off.

> [!WARNING]
> Every EST change, switching it on or off included, takes effect at the
> next reboot, not when you apply it. `config apply` stores the change and
> Switching EST on or off, or changing its settings while it is on, takes
> effect at the next reboot, not when you apply it. `config apply` stores the change and
> prints `requires_reboot=true`; the listener starts, stops or picks up the new
> settings only when the node boots again. Plan the reboot for a maintenance
> window, because the node stops issuing while it restarts, then use
Expand All @@ -229,7 +249,7 @@ Protocols: ACME off, EST on (not running, reboot pending), SCEP off
Reboot: pending (the stored config changes take effect at the next boot)
```

`password_sha256` is write-only. `cryptosctl config get` prints it blank, with its
`password_sha256` is write-only, in a switched-off block too. `cryptosctl config get` prints it blank, with its
`username`. Leave a blank value as it is and `config apply` keeps the one the
node stores for that `username`; set a value to replace it. A new `username`
needs its value, and an apply that leaves it blank is refused. Removing an
Expand Down
29 changes: 23 additions & 6 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,14 @@ type PKI struct {
// SCEP configures the RFC 8894 enrolment endpoint (see SCEP). Nil is
// off, the default. Unlike ACME and EST it is carried in the proto.
SCEP *SCEP `yaml:"scep"`
// DisabledACME and DisabledEST keep the settings of a protocol block
// that is switched off (enabled: false), so the same block can be
// switched back on without re-entering them. Nothing reads them at boot:
// a protocol runs only from ACME or EST. At most one of ACME and
// DisabledACME is set, and likewise for EST. In YAML a disabled block is
// the acme or est block with enabled: false.
DisabledACME *ACME `yaml:"-" json:",omitempty"`
DisabledEST *EST `yaml:"-" json:",omitempty"`
}

// EST configures the node's RFC 7030 server.
Expand Down Expand Up @@ -521,12 +529,17 @@ func decode(raw []byte) (*Config, error) {
if len(raw) == 0 {
return nil, errors.New("config: empty input")
}
raw, flags, err := popProtocolFlags(raw)
if err != nil {
return nil, err
}
var c Config
dec := yaml.NewDecoder(strings.NewReader(string(raw)))
dec.KnownFields(true)
if err := dec.Decode(&c); err != nil {
return nil, fmt.Errorf("config: parse YAML: %w", err)
}
c.applyProtocolFlags(flags)
return &c, nil
}

Expand Down Expand Up @@ -1088,9 +1101,13 @@ func marshalSorted(v interface{}) ([]byte, error) {
}
}

// Marshal renders c as the canonical machine.yaml document.
// Marshal renders c as the canonical machine.yaml document. A disabled
// protocol block is written as its block with enabled: false.
func (c *Config) Marshal() ([]byte, error) {
return yaml.Marshal(c)
if c.PKI.DisabledACME == nil && c.PKI.DisabledEST == nil {
return yaml.Marshal(c)
}
return marshalWithDisabledBlocks(c)
}

// FromProto converts a proto MachineConfig back to a Config. It is the
Expand Down Expand Up @@ -1146,8 +1163,8 @@ func FromProto(pb *cryptosv1.MachineConfig) (*Config, error) {
CACertSHA256: pb.Pki.Parent.CaCertSha256,
}
}
c.PKI.ACME = acmeFromProto(pb.Pki.Acme)
c.PKI.EST = estFromProto(pb.Pki.Est)
c.PKI.ACME, c.PKI.DisabledACME = acmeFromProto(pb.Pki.Acme)
c.PKI.EST, c.PKI.DisabledEST = estFromProto(pb.Pki.Est)
c.PKI.SCEP = scepFromProto(pb.Pki.Scep)
}
if pb.Install != nil {
Expand Down Expand Up @@ -1193,8 +1210,8 @@ func (c *Config) ToProto() *cryptosv1.MachineConfig {
CrlNextUpdateHours: c.PKI.CRLNextUpdateHours,
RevocationHttpPort: c.PKI.RevocationHTTPPort,
RootLeafIssuance: c.PKI.RootLeafIssuance,
Acme: acmeToProto(c.PKI.ACME),
Est: estToProto(c.PKI.EST),
Acme: acmeToProto(c.PKI.ACME, c.PKI.DisabledACME),
Est: estToProto(c.PKI.EST, c.PKI.DisabledEST),
AllowUnsyncedClock: c.PKI.AllowUnsyncedClock,
Scep: scepToProto(c.PKI.SCEP),
}
Expand Down
Loading