netbirdio/netbird · error

failed to parse artifact private key: %w

Error message

failed to parse artifact private key: %w

What it means

Thrown by `signer sign-artifact` when reposign.ParseArtifactKey cannot decode the key supplied via the NB_ARTIFACT_PRIV_KEY env var or --artifact-key-file. The parser expects a single custom PEM block of type "ARTIFACT PRIVATE KEY" whose payload is JSON ({key, id, created_at, expires_at}) with a 64-byte Ed25519 private key (reposign/key.go:131-156). It fails on PEM decode failure, trailing PEM data, a wrong PEM type tag, bad JSON, or a wrong key length.

Source

Thrown at client/cmd/signer/artifactsign.go:136

	var privKeyPEM []byte
	var err error

	if envKey := os.Getenv(envArtifactPrivateKey); envKey != "" {
		// Use key from environment variable
		privKeyPEM = []byte(envKey)
	} else if privKeyFile != "" {
		// Fall back to file
		privKeyPEM, err = os.ReadFile(privKeyFile)
		if err != nil {
			return fmt.Errorf("read private key file: %w", err)
		}
	} else {
		return fmt.Errorf("artifact private key must be provided via %s environment variable or --artifact-key-file flag", envArtifactPrivateKey)
	}

	privateKey, err := reposign.ParseArtifactKey(privKeyPEM)
	if err != nil {
		return fmt.Errorf("failed to parse artifact private key: %w", err)
	}

	artifactData, err := os.ReadFile(artifactFile)
	if err != nil {
		return fmt.Errorf("read artifact file: %w", err)
	}

	signature, err := reposign.SignData(privateKey, artifactData)
	if err != nil {
		return fmt.Errorf("sign artifact: %w", err)
	}

	sigFile := artifactFile + ".sig"
	if err := os.WriteFile(artifactFile+".sig", signature, 0o600); err != nil {
		return fmt.Errorf("write signature file (%s): %w", sigFile, err)
	}

	cmd.Printf("✅ Artifact signed successfully.\n")

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Confirm the input is the artifact key produced by `signer generate-artifact-key`: first line must be -----BEGIN ARTIFACT PRIVATE KEY----- and the file must contain exactly one PEM block
  2. If using NB_ARTIFACT_PRIV_KEY, export it preserving line breaks (e.g. NB_ARTIFACT_PRIV_KEY="$(cat artifact-key.pem)") or switch to --artifact-key-file
  3. Check the PEM body is intact: base64 lines unmodified and the embedded JSON parses (jq on the decoded block)
  4. Regenerate the key pair with generate-artifact-key if the file is suspected corrupted

Example fix

// before
NB_ARTIFACT_PRIV_KEY=LS0tLS1CRUdJTi... ./signer sign-artifact --artifact-file netbird

// after
./signer sign-artifact --artifact-key-file artifact-key.pem --artifact-file netbird
Defensive patterns

Strategy: type-guard

Validate before calling

pemData, err := os.ReadFile(keyFile)
if err != nil {
    log.Fatalf("read key: %v", err)
}

Type guard

func isArtifactPrivateKeyPEM(data []byte) bool {
    block, rest := pem.Decode(data)
    return block != nil && block.Type == "ARTIFACT PRIVATE KEY" && len(rest) == 0
}

Prevention

When it happens

Trigger: Passing a ROOT PRIVATE KEY or a standard OpenSSL/SSH PEM instead of an artifact key; newlines stripped from the PEM when it is stored as a single-line CI secret in NB_ARTIFACT_PRIV_KEY (pem.Decode then fails); concatenating two PEM blocks (trailing-data check); hand-editing the JSON body inside the PEM.

Common situations: CI secret manager flattens the multi-line PEM to one line; release engineer picks the root key from the vault instead of the artifact key; an editor appends a second key block to the file.

Understand the failure class

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/9dc3396afa93591d. Report an issue: GitHub.