slackhq/nebula · error
ca-key is encrypted and must be decrypted interactively
Error message
ca-key is encrypted and must be decrypted interactively
What it means
When the CA key PEM is encrypted (UnmarshalSigningPrivateKeyFromPEM returns cert.ErrPrivateKeyEncrypted), signCert prompts up to 5 times for a passphrase. If the password reader reports ErrNoTerminal (stdin/stdout is not an interactive TTY, e.g. piped input or a CI job), it cannot prompt and returns this error immediately instead of looping.
Source
Thrown at cmd/nebula-cert/sign.go:154
var rawCAKey []byte
rawCAKey, err = readInput("ca-key", *sf.caKeyPath, &claims)
if err != nil {
return fmt.Errorf("error while reading ca-key: %s", err)
}
// naively attempt to decode the private key as though it is not encrypted
caKey, _, curve, err = cert.UnmarshalSigningPrivateKeyFromPEM(rawCAKey)
if errors.Is(err, cert.ErrPrivateKeyEncrypted) {
var passphrase []byte
passphrase = []byte(os.Getenv("NEBULA_CA_PASSPHRASE"))
if len(passphrase) == 0 {
// ask for a passphrase until we get one
for i := 0; i < 5; i++ {
errOut.Write([]byte("Enter passphrase: "))
passphrase, err = pr.ReadPassword()
if errors.Is(err, ErrNoTerminal) {
return fmt.Errorf("ca-key is encrypted and must be decrypted interactively")
} else if err != nil {
return fmt.Errorf("error reading password: %s", err)
}
if len(passphrase) > 0 {
break
}
}
if len(passphrase) == 0 {
return fmt.Errorf("cannot open encrypted ca-key without passphrase")
}
}
curve, caKey, _, err = cert.DecryptAndUnmarshalSigningPrivateKey(passphrase, rawCAKey)
if err != nil {
return fmt.Errorf("error while parsing encrypted ca-key: %s", err)
}
} else if err != nil {
return fmt.Errorf("error while parsing ca-key: %s", err)View on GitHub (pinned to dd8f660c0a)
Solutions
- Decrypt the CA key once, interactively, and sign with the plaintext key file
- Provide a pty: run under `ssh -t` or use a terminal for the command
- Use a version/mechanism that accepts the passphrase non-interactively (e.g. environment/flag supported by your build) rather than prompting
- Store an unencrypted CA key in a secret manager and mount it for automated signing
Example fix
// before nebula-cert sign -ca-key encrypted-ca.key < input.txt # no TTY, cannot prompt // after nebula-cert sign -ca-key encrypted-ca.key # run in an interactive terminal, enter passphrase when prompted
Defensive patterns
Strategy: fallback
Validate before calling
data, _ := os.ReadFile(caKeyPath)
if _, _, _, err := cert.UnmarshalSigningPrivateKeyFromPEM(data); errors.Is(err, cert.ErrPrivateKeyEncrypted) && !isATTY(os.Stdin) {
return fmt.Errorf("encrypted CA key but no TTY; decrypt the key first")
} Type guard
func requiresInteractiveDecrypt(err error) bool {
return strings.Contains(err.Error(), "must be decrypted interactively")
} Try / catch
if err := signCert(args, out, errOut, StdinPasswordReader{}); err != nil {
if strings.Contains(err.Error(), "must be decrypted interactively") {
log.Fatalf("allocate a TTY (e.g. ssh -t) or use an unencrypted CA key for automation")
}
} Prevention
- Decrypt the CA key once before automated signing runs
- Allocate a pty (ssh -t, script -c) when prompting is expected
- Store an unencrypted CA key in a secrets manager for CI
- Detect non-TTY environments up front and fail fast with guidance
When it happens
Trigger: signing with an encrypted CA key while stdin is not a terminal: `cat pass.txt | nebula-cert sign -ca-key encrypted-ca.key ...`, running from a systemd unit/CI runner with no TTY, or invoking via exec without a pty
Common situations: automation scripts feeding input via pipes; containerized signing jobs; SSH sessions without -t so no pty is allocated; scheduled jobs attempting interactive decryption
Related errors
- error reading password: %s
- invalid passphrase or corrupt private key
- out-key must be encrypted interactively
- error reading passphrase: %s
- error while encrypting out-key: %s
AI-assisted analysis of slackhq/nebula@dd8f660c0a (2026-09-03).
Data as JSON: /api/errors/e2cff91fbe9a2d4e.
Report an issue: GitHub.