zester

nkeys

nkeys are Ed25519 key pairs that provide cryptographic identity for every entity in Zester. The pkg/auth package wraps the nats-io/nkeys library and organizes keys into a role-based hierarchy.

Key Roles

Every nkey belongs to one of three roles, each with a distinct single-character prefix on its public key:

RolePrefixPurpose
OperatorORoot of trust. Signs account JWTs. One per Zester deployment.
AccountANamespace boundary. Signs user JWTs. Typically one per environment.
UserUIndividual identity. One per peel (and one for the master).

The role is encoded in the KeyRole type:

const (
    RoleOperator KeyRole = iota  // prefix O
    RoleAccount                   // prefix A
    RoleUser                      // prefix U
)

The KeyBundle

A KeyBundle groups the key pair with its metadata:

FieldTypeDescription
RoleKeyRoleThe role this key serves (operator, account, or user)
KeyPairnkeys.KeyPairThe underlying Ed25519 key pair (sign, verify, seed)
PublicKeystringThe encoded public key with role prefix (e.g., OABC...)
Seed[]byteThe encoded private seed (e.g., SOABC...)

Generating Keys

Use GenerateKeyBundle to create a new key pair for any role:

import "github.com/ptorbus/zester/pkg/auth"

// Generate an operator key pair
operatorKP, err := auth.GenerateKeyBundle(auth.RoleOperator)
// operatorKP.PublicKey => "OAIVQ2..."
// operatorKP.Seed     => "SOAIVQ2..."

// Generate an account key pair
accountKP, err := auth.GenerateKeyBundle(auth.RoleAccount)
// accountKP.PublicKey => "ABJK7..."

// Generate a user key pair
userKP, err := auth.GenerateKeyBundle(auth.RoleUser)
// userKP.PublicKey => "UCXP3..."

Seed Files

The seed is the private key material. It must be stored securely and never shared.

Saving a Seed

err := operatorKP.SaveSeedToFile("/etc/zester/keys/operator.seed")

The file is written with mode 0600 (owner read/write only). The contents look like:

SOAIVQ2JDMRJSGBAMJBKFWQ5B5XQKJJVFL5HNCEPKXE4CM7GH...

Protect your seeds

The seed file contains the private key. Anyone with access to an operator seed can sign account JWTs and take full control of the deployment. Store operator and account seeds in a secrets manager or hardware security module when possible.

Loading a Seed

Load a key bundle from a previously saved seed file:

operatorKP, err := auth.LoadKeyBundleFromFile(auth.RoleOperator, "/etc/zester/keys/operator.seed")

LoadKeyBundleFromFile supports both plain seed files (raw seed string) and decorated .creds format (extracts the seed from the -----BEGIN USER NKEY SEED----- block).

Or from raw seed bytes:

operatorKP, err := auth.LoadKeyBundle(auth.RoleOperator, seedBytes)

Both methods reconstruct the full KeyBundle including the public key.

Public Key Format

Public keys are Base32-encoded Ed25519 public keys with a role prefix:

O  AIVQ2JDMRJSGBAMJBKFWQ5B5XQKJJVFL5HNCEPKXE4CM7GH...
^  ^
|  |-- Base32-encoded public key
|-- Role prefix (O=Operator, A=Account, U=User)

Seeds follow the same pattern but with an S prefix before the role character:

SO AIVQ2...   (Operator seed)
SA BJK7...    (Account seed)
SU CXP3...    (User seed)

Validating Public Keys

Check that a public key is well-formed and matches an expected role:

err := auth.ValidatePublicKey("OAIVQ2...", auth.RoleOperator)  // nil if valid
err := auth.ValidatePublicKey("OAIVQ2...", auth.RoleAccount)   // error: wrong prefix

Extracting a Public Key from a Seed

When you only have a seed, extract the public key without building a full bundle:

pub, err := auth.PublicKeyFromSeed(seedBytes)
// pub => "OAIVQ2..." (prefix reveals the role)

Curve Keys for Encryption

Zester derives X25519 curve keys from the same Ed25519 seed for NaCl box encryption. This allows a single identity to serve both signing and encryption purposes.

// Derive the X25519 curve key pair
curveKP, err := userKP.DeriveCurveKeyPair()

// Get just the curve public key (prefix: X)
curvePub, err := userKP.CurvePublicKey()
// curvePub => "XCXP3..."

Curve keys are used internally by the encryption system to protect sensitive settings values so that only the target peel can read them.

Key TypePrefixAlgorithmUse
Signing (public)O, A, UEd25519JWT signing, authentication
Signing (seed)SO, SA, SUEd25519Private key material
Curve (public)XX25519NaCl box encryption

CLI Examples

Generate a full key hierarchy for bootstrapping using your NATS key-management tooling (for example nsc) or directly via the Go API (auth.GenerateFullHierarchy).

This creates:

/etc/zester/keys/
  operator.seed      # Operator private seed
  account.seed       # Account private seed
  master.seed        # Master user private seed
  operator.pub       # Operator public key
  account.pub        # Account public key

Key naming convention

Name seed files by their role and purpose: operator.seed, account.seed, web-server-01.seed. The public key can always be derived from the seed, so you only need to store the seed file.

On this page