semaphoreui/semaphore · error

jwt: re-encrypt signing key

Error message

jwt: re-encrypt signing key: %w

What it means

After successfully decrypting the JWT signing key plaintext, RekeyJWTSigningKey re-encrypts it with Config.EncryptOption, which uses the option keyring primary key (falling back to the access key). This error means the re-encryption step failed, typically because no encryption key material is configured.

Solutions

  1. Ensure the option keyring is configured (option key env/config, or a valid access key fallback) before running the rekey.
  2. Initialize Config/keyring before calling RekeyJWTSigningKey in custom tooling.
  3. Re-run the rekey once encryption keys are present - decryption already succeeded, so only the encrypt step needs a valid key.

Example fix

// before
util.RekeyJWTSigningKey(store, oldKey) // keyring not configured in this process
// after
util.LoadConfig() // sets up option/access keyrings from config/env
if !util.Config.HasOptionKey() {
    return errors.New("configure option or access key before rekey")
}
util.RekeyJWTSigningKey(store, oldKey)
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure encryption is configured before rekey
if util.Config == nil || !util.Config.HasEncryptionKey() {
    return errors.New("option/access encryption key not configured")
}

Try / catch

if err := util.RekeyJWTSigningKey(store, oldKey); err != nil {
    if strings.Contains(err.Error(), "re-encrypt signing key") {
        return fmt.Errorf("no primary encryption key available for option keyring; set option key or access key fallback: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling RekeyJWTSigningKey when the option keyring has no primary key and no access-key fallback is available - e.g. missing/unset option-key or access-key configuration at rekey time.

Common situations: Option key env/config removed during a rotation; running the rekey command in an environment where Config encryption keys were never set up; keyring initialized after the rekey call.

Understand the failure class

Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/c0ddf3699254d1f4. Report an issue: GitHub.

Appendix: source

Thrown at util/jwt.go:142

	if err != nil {
		return fmt.Errorf("read jwt signing key option: %w", err)
	}
	if stored == "" {
		return nil
	}

	pemBytes, err := Config.DecryptOption(stored)
	if err != nil && oldKey != "" {
		_, ct, _ := parseEnvelope(stored)
		pemBytes, err = DecryptAESGCM(ct, oldKey)
	}
	if err != nil {
		return fmt.Errorf("jwt: decrypt signing key for rekey: %w", err)
	}

	reEncrypted, err := Config.EncryptOption(pemBytes)
	if err != nil {
		return fmt.Errorf("jwt: re-encrypt signing key: %w", err)
	}

	if reEncrypted == stored {
		return nil
	}

	if err := store.SetOption(jwtSigningKeyOption, reEncrypted); err != nil {
		return fmt.Errorf("jwt: persist re-encrypted signing key: %w", err)
	}
	return nil
}

// encryptJWTKey encrypts pemBytes using the option keyring primary key (which
// falls back to the access key when no separate option key is configured).
func encryptJWTKey(pemBytes []byte) (string, error) {
	return Config.EncryptOption(pemBytes)
}

View on GitHub (pinned to 1774ccb71a)