k3s-io/k3s · error

failed to normalize server token; must be in format K10<CA-H

Error message

failed to normalize server token; must be in format K10<CA-HASH>::<USERNAME>:<PASSWORD> or <PASSWORD>

What it means

NormalizeToken validates a server/node token with clientaccess.ParseUsernamePassword. Accepted shapes are the full bootstrap form 'K10<CA-HASH>::<USERNAME>:<PASSWORD>' or a bare '<PASSWORD>'. Any other shape — wrong segment count, missing '::' separator between CA hash and username, stray characters — returns this error with an empty password.

Source

Thrown at pkg/util/token.go:45

// try to use provided serverToken value, instead.
func ReadTokenFromFile(serverToken, certs, dataDir string) (string, error) {
	tokenFile := filepath.Join(dataDir, "token")

	b, err := os.ReadFile(tokenFile)
	b = bytes.TrimSpace(b)

	if os.IsNotExist(err) || len(b) == 0 {
		return clientaccess.FormatToken(serverToken, certs)
	}

	return string(b), err
}

// NormalizeToken will normalize the token read from file or passed as a cli flag
func NormalizeToken(token string) (string, error) {
	_, password, ok := clientaccess.ParseUsernamePassword(token)
	if !ok {
		return password, errors.New("failed to normalize server token; must be in format K10<CA-HASH>::<USERNAME>:<PASSWORD> or <PASSWORD>")
	}

	return password, nil
}

func GetTokenHash(config *config.Control) (string, error) {
	token := config.Token
	if token == "" {
		tokenFromFile, err := ReadTokenFromFile(config.Runtime.ServerToken, config.Runtime.ServerCA, config.DataDir)
		if err != nil {
			return "", err
		}
		token = tokenFromFile
	}
	normalizedToken, err := NormalizeToken(token)
	if err != nil {
		return "", err
	}

View on GitHub (pinned to 6ba341e396)

Solutions

  1. Use the exact format: K10<CA-HASH>::<USERNAME>:<PASSWORD>, or just the password portion alone
  2. Re-copy the token from /var/lib/rancher/k3s/server/token (or the node's node-token file) without truncation
  3. Trim whitespace and newlines when reading from a file: strings.TrimSpace(string(b))
  4. Verify the '::' separator sits between the CA hash and the username:password part

Example fix

// before
token := "K10f2e0c1d4b7a::admin secret" // space instead of colon
_, err := util.NormalizeToken(token)

// after
token := "K10f2e0c1d4b7a::admin:secret"
_, err := util.NormalizeToken(strings.TrimSpace(token))
Defensive patterns

Strategy: validation

Validate before calling

var tokenRe = regexp.MustCompile(`^(K10[^:]+::[^:]+:.+|[^:]+)$`)
tok := strings.TrimSpace(rawToken)
if !tokenRe.MatchString(tok) {
    return fmt.Errorf("token must be K10<CA-HASH>::<USERNAME>:<PASSWORD> or <PASSWORD>")
}
_, err := util.NormalizeToken(tok)

Try / catch

if _, err := util.NormalizeToken(tok); err != nil {
    // surface the format error to the operator; never fall back to a default token
    return fmt.Errorf("invalid token, re-copy from server token file: %w", err)
}

Prevention

When it happens

Trigger: Passing a token that is neither 'K10<hash>::<user>:<pass>' nor a single password segment: 'K10abc:admin:secret' (single colon instead of '::'), 'user:pass' alone, truncated pastes, or whitespace/CRLF-padded values read from a token file.

Common situations: Copy/paste truncation when moving tokens between nodes; hand-editing /var/lib/rancher/k3s/server/token or node-token files; CRLF line endings in Windows-edited token files; using a kubeconfig token where a k3s token is expected.

Related errors


AI-assisted analysis of k3s-io/k3s@6ba341e396 (2026-08-15). Data as JSON: /api/errors/710293b6e5cb10c6. Report an issue: GitHub.