hashicorp/nomad · error

error reading

Error message

error reading %s: %w

What it means

Fires in getCassPrivateKey when os.ReadFile fails on the OIDC client assertion's configured PemKeyFile — the file path is wrong, missing, or unreadable, so the private key for the client assertion JWT cannot be loaded.

Solutions

  1. Verify the PemKeyFile path exists on every Nomad server and is readable: `ls -l <path> && sudo -u nomad cat <path>`.
  2. Use an absolute path and ensure the key is provisioned/templated (e.g. Vault agent, volume mount) on server nodes.
  3. Alternatively embed the key inline via PemKey instead of a file path.

Example fix

// before
key := &structs.OIDCClientAssertionKey{PemKeyFile: "client.key"}
// after
key := &structs.OIDCClientAssertionKey{PemKeyFile: "/etc/nomad.d/tls/oidc-client.key"}
Defensive patterns

Strategy: validation

Validate before calling

if k.PemKeyFile != "" {
  if _, err := os.ReadFile(k.PemKeyFile); err != nil {
    return fmt.Errorf("PemKeyFile unreadable before assertion build: %w", err)
  }
}

Try / catch

key, err := getCassPrivateKey(k)
if err != nil && strings.Contains(err.Error(), "error reading") {
  return fmt.Errorf("check PemKeyFile path/permissions on Nomad servers: %w", err)
}

Prevention

When it happens

Trigger: BuildClientAssertionJWT → getCassPrivateKey when OIDCClientAssertionKey.PemKeyFile is set and the file cannot be read (missing path, wrong permissions, unreadable volume).

Common situations: Path typo or relative path that doesn't resolve on Nomad servers; file only mounted on clients, not servers; permissions preventing the nomad user from reading; secret not provisioned by the deployment tool.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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

Appendix: source

Thrown at lib/auth/oidc/client_assertion.go:128

		return cass.NewJWTWithRSAKey(clientID, as.Audience, algo, rsaKey, opts...)

	default: // this shouldn't happen, but just in case
		return nil, fmt.Errorf("unknown OIDC KeySource %q", as.KeySource)
	}
}

// getCassPrivateKey parses the structs.OIDCClientAssertionKey PemKeyFile
// or PemKey, depending on which is set.
func getCassPrivateKey(k *structs.OIDCClientAssertionKey) (key *rsa.PrivateKey, err error) {
	var bts []byte
	var source string // for informative error messages

	// pem file on disk
	if k.PemKeyFile != "" {
		source = "PemKeyFile"
		bts, err = os.ReadFile(k.PemKeyFile)
		if err != nil {
			return nil, fmt.Errorf("error reading %s: %w", source, err)
		}
	}
	// or pem string
	if k.PemKey != "" {
		source = "PemKey"
		bts = []byte(k.PemKey)
	}

	// ensure newlines around pem header/footer
	bts = newlineHeaders(bts)

	key, err = gojwt.ParseRSAPrivateKeyFromPEM(bts)
	if err != nil {
		return nil, fmt.Errorf("error parsing %s: %w", source, err)
	}
	if err := key.Validate(); err != nil {
		return nil, fmt.Errorf("error validating %s: %w", source, err)
	}

View on GitHub (pinned to 482b49bf1a)