hashicorp/nomad · error

root key algorithm cannot be changed after a key is created

Error message

root key algorithm cannot be changed after a key is created

What it means

Nomad root keys are immutable in one respect: once created, the encryption algorithm of a key cannot change. validateUpdate compares the stored key's Algorithm with the submitted one and rejects any mismatch, because existing variables were encrypted with the old algorithm and cannot be re-encrypted in place.

Source

Thrown at nomad/keyring_endpoint.go:255

	if err != nil {
		return err
	}
	if len(args.RootKey.Key) == 0 {
		return fmt.Errorf("root key material is required")
	}

	// lookup any existing key and validate the update
	snap, err := k.srv.fsm.State().Snapshot()
	if err != nil {
		return err
	}
	ws := memdb.NewWatchSet()
	rootKey, err := snap.RootKeyByID(ws, args.RootKey.Meta.KeyID)
	if err != nil {
		return err
	}
	if rootKey != nil && rootKey.Algorithm != args.RootKey.Meta.Algorithm {
		return fmt.Errorf("root key algorithm cannot be changed after a key is created")
	}

	return nil
}

// Get retrieves an existing key from the keyring, including both the
// key material and metadata. It is used only for replication.
func (k *Keyring) Get(args *structs.KeyringGetRootKeyRequest, reply *structs.KeyringGetRootKeyResponse) error {
	aclObj, err := k.srv.AuthenticateServerOnly(k.ctx, args)
	k.srv.MeasureRPCRate("keyring", structs.RateMetricRead, args)

	if err != nil || !aclObj.AllowServerOp() {
		return structs.ErrPermissionDenied
	}

	if done, err := k.srv.forward("Keyring.Get", args, args, reply); done {
		return err
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Create a new key with the desired algorithm via Rotate or Upsert instead of updating the existing key ID.
  2. Keep the original Algorithm value when updating metadata of an existing key.
  3. List existing keys (keyring Get/list) and check the stored algorithm before constructing the update request.

Example fix

// before
req.RootKey.Meta.Algorithm = structs.EncryptionAlgorithmAES256GCM // changing existing key
// after
req.RootKey.Meta.Algorithm = existingKey.Algorithm // preserve algorithm, rotate to a new key if a different one is needed
Defensive patterns

Strategy: validation

Validate before calling

existing, _, err := client.Keyring().Get(&structs.KeyringGetRootKeyRequest{KeyID: req.RootKey.Meta.KeyID}, nil)
if err == nil && existing != nil && existing.Meta.Algorithm != req.RootKey.Meta.Algorithm {
    return fmt.Errorf("cannot change algorithm for key %s; rotate to a new key instead", req.RootKey.Meta.KeyID)
}

Type guard

func algorithmUnchanged(existing, incoming *structs.RootKeyMeta) bool {
    return existing == nil || existing.Algorithm == incoming.Algorithm
}

Try / catch

if err := updateKey(req); err != nil {
    if strings.Contains(err.Error(), "algorithm cannot be changed") {
        return rotateToNewKey(newAlgorithm)
    }
    return err
}

Prevention

When it happens

Trigger: Calling the Keyring.Update RPC with a RootKey whose Meta.Algorithm differs from the algorithm recorded for the existing KeyID in state store (snap.RootKeyByID).

Common situations: Operator 'updates' an existing key to switch from aes128-gcm to aes256-gcm instead of rotating to a new key; tooling copies a key spec and hand-edits the algorithm; a default algorithm change in deployment config is applied to old key IDs.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/38b61e683bd0f018. Report an issue: GitHub.