Open-Source Wikis

/

Vault

/

How to contribute

/

Patterns and conventions

hashicorp/vault

Patterns and conventions

Reading Vault becomes much easier once you know its house style. These are the patterns that recur in nearly every package.

File suffixes

Suffix Meaning
_ce.go Open-source (Community Edition) stub. Replaced by _ent.go in Enterprise builds.
_oss.go Same idea, older convention.
_util.go Helper functions for the file with the same prefix.
_stubs_oss.go Empty stub functions that Enterprise overrides. Don't delete them.
_testing.go, _testing_util.go Helpers for tests in the same package.
_test.go Standard Go test file.
_enumer.go Generated by dmarkham/enumer. Don't hand-edit; regenerate via go generate.

The entExtend... and entAdd... function names follow the same OSS/Enterprise split. If you find yourself writing one, follow the existing pattern: an OSS file with func entExtendFoo() {} and an Enterprise file with the real body.

Backends use framework

Almost every auth/secret backend is a *framework.Backend constructed with a list of *framework.Path entries. The pattern (e.g., from builtin/logical/transit/backend.go):

func Backend(conf *logical.BackendConfig) *backend {
    var b backend
    b.Backend = &framework.Backend{
        Help:        "...",
        PathsSpecial: &logical.Paths{...},
        Paths:       framework.PathAppend(b.paths(), b.batchPaths(), ...),
        Secrets:     []*framework.Secret{b.secret()},
        BackendType: logical.TypeLogical,
    }
    return &b
}

Each *framework.Path has a regex Pattern, a Fields map, and Operations/Callbacks that handle CRUD. The framework auto-generates OpenAPI specs, help text, and capability checks.

Logging

  • Use the package-scoped hclog.Logger injected via BackendConfig.Logger or core.logger. Don't call log.Println.
  • Levels: Trace is fine-grained tracing, Debug is per-request, Info is lifecycle events, Warn is a recoverable problem, Error is something operators should investigate.
  • Log keys are camelCase: b.logger.Info("rotated key", "mount", mountPath, "version", version).
  • Don't log secrets. Audit devices already record requests; don't duplicate.

Errors

  • Wrap with fmt.Errorf("...: %w", err) so errors.Is works.
  • Surface user-friendly messages by returning a logical.ErrorResponse(...) for 4xx-class errors; reserve actual Go errors for internal/5xx-class problems.
  • Status mapping happens in sdk/logical/response_util.go — read it before adding new error categories.

Locking

vault.Core uses an RWMutex for the seal/unseal lifecycle. Most subsystems own their own mutex. Some patterns to know:

  • Core.stateLock (in vault/core.go) — guards the unseal state. Take it read-locked for normal operations; write-locked only during seal/unseal/HA transitions.
  • Mounts.lock (in vault/mount.go) — guards the mount table.
  • ExpirationManager.pendingLock — guards the leases map.
  • tokenStore uses fine-grained locks per token to avoid contention.

The codebase intentionally uses helper/locking/ wrappers in some hot paths to instrument lock waits.

Request handling

Every HTTP request flows through http/handler.govault.HandleRequest (in vault/request_handling.go) → router → backend. The contract is:

  • Inputs: *logical.Request (path, operation, data, namespace, client info).
  • Outputs: *logical.Response (data, secret, auth, warnings) plus error.

If a backend returns a Secret, the expiration manager will register a lease. If it returns an Auth, the token store will mint a token.

Backends should never reach into vault.Core. They get a system view (logical.SystemView) for limited access to mount info, namespace, and license features.

Tests with TestCluster

vault.TestCluster (and vault.NewTestCluster) instantiates a 3-node in-memory cluster. Use it whenever you need real HA / forwarding behavior; use raw vault.TestCoreUnsealed for single-node, no-forwarding tests.

OSS-vs-Enterprise build tags

Build tags appear at the top of file:

//go:build !enterprise

package vault

The OSS tree is built with -tags= empty; Enterprise sets -tags=enterprise. Don't add new tags without consulting the team — the matrix is already complex.

Generated code

These files are generated and checked in. Never edit them by hand:

  • *.pb.go and *.pb.goclient/*_grpc.pb.go (regenerated by make proto).
  • *_enumer.go (go generate).
  • http/web_ui/ (UI bundle, regenerated by make ember-dist).
  • helper/builtinplugins/registry_full.go is hand-maintained but lists every Enterprise-built plugin.

Copywrite

Every file has a // Copyright IBM Corp. 2016, 2025 line followed by // SPDX-License-Identifier: BUSL-1.1. The copywrite tool (.copywrite.hcl) verifies and inserts these. Run copywrite headers --plan to preview, copywrite headers to apply. CI fails if a file is missing the header.

Sensitive data

  • Never write tokens, passwords, or unwrap responses to stdout/stderr.
  • The audit broker HMACs sensitive fields based on audit/headers.go and helper/identity/ allow-lists.
  • For tests, use t.Setenv or t.TempDir so secrets don't leak between tests.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Vault wiki | Factory