Skip to content

Key Vault content standard

Standard scope

Applies to: Every secret, key, and certificate in every HCS-managed or HCS-accessed Key Vault, across all scopes Domain: keyvault Status: Active

This standard governs everything stored inside Azure Key Vault — secrets, keys, and certificates. It is not about the KV resource itself; see naming for resource naming (kv-<org>-<env>-<region>-<instance>).

This document is the authoritative multi-vault model for HCS. Nothing in this standard assumes a single vault — every rule below (naming, access control, the keyvault:// URI scheme) is written to work across any number of vaults owned by any number of organizations. Where kv-hcs-vault-01 appears as a default or an example, that default applies only within the hcs scope; other scopes (TierPoint, customers) use their own vault, resolved from config, never assumed. See C3 in the consolidation matrix for the reasoning.

Every object stored in any HCS-managed Key Vault must conform to this standard. No exceptions.


Scope

This standard applies to:

  • All secrets, keys, and certificates in kv-hcs-vault-01 and any future HCS-managed vault
  • All organisations — HCS, TierPoint, Country Cloud Boy, and any customer-managed vault
  • All automation that creates, reads, rotates, or deletes vault objects

What belongs in Key Vault

Store in KV:

  • Passwords and credentials for any service, account, or workload
  • API keys, PATs, and OAuth tokens
  • Service principal client secrets
  • Managed identity client IDs (for reference by scripts and pipelines)
  • Certificate private keys
  • Connection strings
  • Any value whose exposure would require a security response

Do not store in KV:

  • Account usernames or display names that have no associated secret (store those in config YAML)
  • Public endpoints, URLs, or region identifiers
  • Non-secret configuration — cluster names, subscription IDs, tenant IDs
  • Secrets fully managed by a purpose-built store with no other consumers

Object types

TypeUse forCLI noun
SecretPasswords, tokens, keys, connection strings, client secretssecret
KeyCryptographic operations — disk encryption, backup encryption, key wrappingkey
CertificateTLS/SSL certificates with automated renewal and private key managementcertificate

Naming standard

The pattern

<org>-<env-name>-<descriptor>-<field>
SegmentPurposeRules
orgWho owns it2–4 char lowercase identifier, unique across all vaults — always first
env-nameWhich specific environmentShort memorable name — never a generic type like prd or poc — see env-name rules below
descriptorWhat this secret is forAccount type, service, or resource — be specific: domain-admin, arc-registration-spn, azl-cl01-lcm-01
fieldWhat value the secret holdspassword, username, secret, client-id, pat, api-key, api-token, conn-str, private-key

Rules

  1. lowercase-kebab-case — no underscores, no camelCase, no PascalCase
  2. No region segment — region is a deployment detail, not an identifier
  3. No generic environment type (prd, poc, dev) as the env-name — use the specific name of the environment
  4. Usernames and passwords for the same account are separate secrets with matching names that differ only in the final <field> segment
  5. Never include the vault name in the object name
  6. Service principals: purpose-first — arc-registration-spn, not spn-arc-registration
  7. Managed identities: purpose-first — arc-management-mi, not mi-arc-management
  8. Account identifiers for human logins are derived from the full UPN/username, kebab-cased, with @ and . replaced by -. Never abbreviate: kris-hybridsolutions-cloud is correct; kjt, kris, or hcs are not.
  9. Every password secret must have a paired username secret with an identical name differing only in the <field> segment. Existing passwords without a matching username must have one created at the next rotation.
  10. The env-name must appear in the env-name registry table. No ad-hoc env-names.

Approved field values

The <field> segment must be one of the following. Do not invent new values.

Field valueUse for
usernameLogin name or UPN value
passwordPassword
patPersonal Access Token (GitHub, GitLab, ADO)
app-idGitHub App ID
app-private-keyGitHub App private key
client-idService principal or managed identity client ID
secretService principal client secret
api-keySaaS API key (Anthropic, OpenAI, etc.)
api-tokenAPI token distinct from a PAT (e.g. Cloudflare)
conn-strConnection string
private-keyTLS or certificate private key (PEM or base64)
recovery-codeEntra MFA or account recovery code
deploy-tokenStatic Web Apps or static host deploy token

Org identifier rules

  • 2–4 characters, lowercase, no hyphens
  • Must be short enough to be instantly recognisable
  • Must be unique across all vaults — agree the identifier before using it for the first time

Environment name rules

  • Short and memorable — someone who works in that environment must recognise it immediately
  • Names the specific environment, not its type — tplabs not tppoc, hcsprod not hcsprd
  • For shared platform services with no infrastructure target (PATs, API keys, SaaS tokens), use platform as the env-name

Env-name registry

Every env-name in use must appear in this table. An env-name not listed here is not valid. Adding a new env-name requires a PR and an ADO work item that updates this table.

env-nameMeaningExamples of secrets that belong here
platformHCS-wide automation and shared service credentials. No human logins.Master GitHub PAT, ADO platform PAT, Anthropic API key, Cloudflare token, SWA deploy token
personalAdmin credentials tied to a human identity. Always tagged lifecycle=temporary. Never use platform for a human credential.Kris's Entra logins, GitHub account password, MFA recovery codes
hcsprodHCS production workload environment (infrastructure, clusters, services).HCS production cluster admin passwords, domain join credentials
tplabsTierPoint lab environment workload secrets.Azure Local cluster LCM accounts, domain admin, deployment accounts
tppocTierPoint customer proof-of-concept environment.TierPoint PoC workload secrets
tpdemosTierPoint internal demo environment.TierPoint demo workload secrets
blog-thisismydemothisismydemo.cloud blog infrastructure secrets.Blog deploy tokens, GitHub repo deploy keys
phxProject Phoenix environment (projectphx.cloud).Phoenix cluster credentials, platform secrets
<repo-name>Per-repo scoped credentials. env-name matches the repo name exactly — e.g. azurelocal-ranger, azure-scout, azurelocal-surveyor.Per-repo PATs, per-repo deploy tokens
<org-name>Per-org scoped credentials when repo scope is not enough. env-name matches the GitHub/GitLab org name — e.g. azurelocal, thisismydemo, hybridcloudsolutions.Per-org PATs, per-org GitHub App private keys

Personal credential category

Use env-name personal for any credential tied to a single human identity — Entra UPNs, personal GitHub login passwords, MFA recovery codes.

Rules for personal secrets:

  1. Never use platform as the env-name for a human-tied credential. platform is reserved for service and automation accounts.
  2. Descriptor must be <platform>-<account-identifier>:
    • platform is the service the account belongs to: entra, github, gitlab, ado, microsoft-account, or azure-portal.
    • account-identifier is the full login, kebab-cased, with @ and . replaced by -. Never an abbreviation. kris-hybridsolutions-cloud is correct; kjt, kris, or hcs are not.
  3. Username and password must always be stored as a pair. Names differ only in the final <field> segment.
  4. lifecycle=temporary tag is required. Default expiry: 30 days.
  5. Long-term recommendation: migrate all personal secrets to a dedicated kv-hcs-personal-01 vault so personal logins are not co-mingled with automation credentials.

Examples:

Secret nameFieldWhat it is
hcs-personal-entra-kris-hybridsolutions-cloud-usernameusernameEntra UPN: kris@hybridsolutions.cloud
hcs-personal-entra-kris-hybridsolutions-cloud-passwordpasswordPassword for kris@hybridsolutions.cloud
hcs-personal-entra-kristopher-turner-azurelocal-cloud-usernameusernameEntra UPN: kristopher.turner@azurelocal.cloud
hcs-personal-entra-kristopher-turner-azurelocal-cloud-passwordpasswordPassword for kristopher.turner@azurelocal.cloud
hcs-personal-github-kristopherjturner-usernameusernameGitHub login: kristopherjturner
hcs-personal-github-kristopherjturner-passwordpasswordGitHub account password for kristopherjturner

Descriptor construction — Azure Local accounts

Azure Local secrets must include the cluster name in the descriptor so secrets from different clusters in the same environment never collide.

azl-<cluster-name>-<account-type>-<instance>
SegmentRules
azlAlways first for Azure Local workload secrets
cluster-nameThe actual name of the cluster — short, matches what is registered in Azure Arc
account-typelocal-admin, deployment, lcm, domain-admin
instanceZero-padded number for account types that have multiple instances (lcm-01, lcm-02). Omit for single-instance account types.

Examples:

  • tp-tplabs-azl-hci01-lcm-01-password — tplabs env, cluster hci01, LCM account 01, password
  • tp-tplabs-azl-hci01-lcm-02-password — same cluster, LCM account 02
  • tp-tplabs-azl-hci02-lcm-01-password — same env, different cluster hci02, LCM account 01
  • tp-tplabs-azl-hci01-local-admin-password — cluster hci01, local admin password
  • tp-tplabs-azl-hci01-deployment-password — cluster hci01, deployment account password

Vault assignment

Each organisation owns its own Key Vault. There is no single universal vault — kv-hcs-vault-01 is the hcs-scope default, used for the LLC, personal projects, blogs, and community work. It is not assumed for any other scope.

OrgVault
hcs, ccb, ktj, azl and other personal orgs (hcs scope)kv-hcs-vault-01
tp (TierPoint, tierpoint-prodtech scope)TierPoint's KV in their Azure subscription
Other clients or employersTheir own dedicated KV

The secret inventory in this repo is the authoritative registry for all secrets across all vaults. Every entry records which vault the secret lives in. The platform managed identity has read and write access to external vaults, so scripts and pipelines can resolve any registered secret regardless of which vault holds it.


Example secret names by account type

The examples below show real secret names as they would appear in the inventory. The naming pattern is the same regardless of which vault holds the secret — the inventory records the vault location for each entry.

Domain and Active Directory accounts

Secret nameWhat it is
tp-tplabs-domain-admin-usernameDomain administrator account name for tplabs AD
tp-tplabs-domain-admin-passwordDomain administrator password for tplabs AD
hcs-hcsprod-domain-admin-usernameDomain administrator account name for HCS production AD
hcs-hcsprod-domain-admin-passwordDomain administrator password for HCS production AD

Azure Local — node and deployment accounts

Secret nameWhat it is
tp-tplabs-azl-hci01-local-admin-usernameLocal admin account name on cluster hci01 nodes
tp-tplabs-azl-hci01-local-admin-passwordLocal admin password on cluster hci01 nodes
tp-tplabs-azl-hci01-deployment-usernameDeployment account name for cluster hci01
tp-tplabs-azl-hci01-deployment-passwordDeployment account password for cluster hci01
hcs-hcsprod-azl-hci01-local-admin-usernameHCS production cluster hci01 local admin — username
hcs-hcsprod-azl-hci01-local-admin-passwordHCS production cluster hci01 local admin — password

Azure Local — LCM accounts

Azure Local requires up to 15 LCM managed accounts per cluster. Each is numbered starting at 01.

Secret nameWhat it is
tp-tplabs-azl-hci01-lcm-01-usernameCluster hci01 LCM account 01 — username
tp-tplabs-azl-hci01-lcm-01-passwordCluster hci01 LCM account 01 — password
tp-tplabs-azl-hci01-lcm-02-usernameCluster hci01 LCM account 02 — username
tp-tplabs-azl-hci01-lcm-02-passwordCluster hci01 LCM account 02 — password
tp-tplabs-azl-hci02-lcm-01-usernameCluster hci02 LCM account 01 — username (different cluster, same env)
tp-tplabs-azl-hci02-lcm-01-passwordCluster hci02 LCM account 01 — password
(continue through lcm-15 per cluster as needed)
hcs-hcsprod-azl-hci01-lcm-01-usernameHCS production cluster hci01 LCM account 01 — username
hcs-hcsprod-azl-hci01-lcm-01-passwordHCS production cluster hci01 LCM account 01 — password

Service principals

The purpose comes first in the descriptor — you should know what it does before you know it is an SPN.

Secret nameWhat it is
tp-tplabs-arc-registration-spn-client-idSPN for Arc node registration in tplabs — client ID
tp-tplabs-arc-registration-spn-secretSPN for Arc node registration in tplabs — client secret
hcs-hcsprod-arc-registration-spn-client-idSPN for Arc node registration in HCS production — client ID
hcs-hcsprod-arc-registration-spn-secretSPN for Arc node registration in HCS production — client secret
hcs-platform-ado-pipelines-spn-client-idSPN used by ADO pipelines for Azure operations — client ID
hcs-platform-ado-pipelines-spn-secretSPN used by ADO pipelines for Azure operations — client secret
hcs-platform-swa-deploy-spn-client-idSPN for Static Web Apps deployment — client ID
hcs-platform-swa-deploy-spn-secretSPN for Static Web Apps deployment — client secret

Managed identities

Managed identities authenticate via Azure AD and do not have secrets. Store the client ID in KV so scripts and pipelines can resolve it without hardcoding.

Secret nameWhat it is
hcs-platform-platform-automation-mi-client-idClient ID of the platform automation managed identity
tp-tplabs-arc-management-mi-client-idClient ID of the Arc management managed identity in tplabs
hcs-hcsprod-backup-mi-client-idClient ID of the backup managed identity in HCS production

API keys and PATs

Secret nameWhat it is
hcs-platform-github-org-patGitHub PAT with access to all HCS GitHub organisations
hcs-platform-ado-platform-patADO PAT for the Platform Engineering org
hcs-platform-anthropic-api-keyAnthropic API key for Claude Code and SDK usage
hcs-platform-cloudflare-api-tokenCloudflare API token scoped to hybridsolutions.cloud DNS
tp-platform-github-org-patGitHub PAT scoped to TierPoint GitHub orgs
tp-platform-ado-platform-patADO PAT for TierPoint ADO org

Private keys and certificates

Private keys stored as KV Secrets (PEM or base64 encoded):

Secret nameWhat it is
hcs-platform-wildcard-hybridsolutions-cloud-private-keyPrivate key for *.hybridsolutions.cloud wildcard cert
hcs-platform-platform-site-private-keyPrivate key for platform.hybridsolutions.cloud cert
tp-tplabs-ado-agent-mtls-private-keyPrivate key for mTLS cert used by ADO self-hosted agents

Certificates managed natively in KV (preferred — KV handles renewal):

Certificate nameWhat it is
hcs-platform-wildcard-hybridsolutions-cloud-certWildcard TLS certificate for *.hybridsolutions.cloud
hcs-platform-platform-site-certTLS certificate for platform.hybridsolutions.cloud
tp-tplabs-ado-agent-mtls-certmTLS certificate for tplabs ADO self-hosted agents

Cryptographic keys (KV Key type)

Used for encryption operations only — not for storing credential values.

Key nameUse
hcs-hcsprod-disk-encryption-keyCustomer-managed key for managed disk encryption
hcs-hcsprod-backup-encryption-keyEncryption key for Azure Backup
hcs-hcsprod-storage-cmkCustomer-managed key for storage account encryption
tp-tplabs-disk-encryption-keyCustomer-managed key for tplabs managed disk encryption

Key type: RSA 4096 or EC P-256. RSA 2048 minimum. Never RSA 1024.


Required attributes

Expiry date

Every secret must be created with an expiry date. Key Vault will alert before expiry.

powershell
$expiry = (Get-Date).AddDays(90).ToString("yyyy-MM-ddTHH:mm:ssZ")

az keyvault secret set `
    --vault-name kv-hcs-vault-01 `
    --name hcs-platform-github-org-pat `
    --value $secretValue `
    --expires $expiry

Default expiry windows:

TypeExpiry
PATs and API tokens with broad access90 days
API keys for SaaS services180 days
Service principal secrets180 days
Infrastructure account passwords365 days or on change
Certificates365 days — renew before expiry

Tags

Every object must carry these five tags:

TagDescriptionExample values
ownerEmail of the person responsiblekris@hybridsolutions.cloud
projectProject or workload this belongs toplatform-engineering, tplabs-azurelocal
rotation-daysRotation frequency in days30, 90, 180, 365
managed-byHow it is created and updatedmanual, ado-pipeline, script
lifecycleLifetime intent of this secretpermanent, temporary

Any secret in env-name personal must have lifecycle=temporary. Any secret that is a permanent service credential must have lifecycle=permanent. Omitting lifecycle is prohibited.

powershell
az keyvault secret set `
    --vault-name kv-hcs-vault-01 `
    --name hcs-platform-github-org-pat `
    --value $secretValue `
    --expires $expiry `
    --tags owner=kris@hybridsolutions.cloud project=platform-engineering rotation-days=90 managed-by=manual lifecycle=permanent

Lifecycle

Adding a new secret

  1. Choose the naming pattern and confirm the name is unique and descriptive
  2. Create the secret in the correct vault with expiry and all four required tags
  3. Register it in secret-inventory.md — the entry must include the vault name
  4. If it feeds a pipeline, register it in the ADO variable group
  5. If it is needed in local sessions, add it to scripts/Load-HCSEnvironment.ps1
  6. Commit: docs(identity): register secret <secret-name>

Rotating a secret

  1. Generate the new value in the target system
  2. Update KV: az keyvault secret set --vault-name <vault> --name <name> with the new value and updated expiry
  3. Update the Last rotated date in secret-inventory.md
  4. Verify all consumers still function
  5. Commit the inventory update

Retiring a secret

  1. Confirm no consumers are using the secret
  2. Delete from KV: az keyvault secret delete --vault-name <vault> --name <name>
  3. Move the inventory row to the "Expired or revoked" table with deletion date and reason
  4. Remove from Load-HCSEnvironment.ps1 if present
  5. Commit: docs(identity): retire secret <secret-name>

Secret inventory requirements

The secret inventory at secret-inventory.md is the authoritative registry for all secrets across all vaults. Every entry must include:

ColumnRule
Secret nameFull name as it exists in KV
VaultThe Key Vault resource name where the secret lives (e.g. kv-hcs-vault-01, kv-tp-tplabs-01)
PurposeWhat the secret is for
ConsumersEvery script, pipeline, or service that reads this secret — if empty, candidate for deletion
Rotation scheduleHow often it must be rotated
Last rotatedDate last rotated — must be updated on every rotation

An unregistered secret found in any vault during an audit must be registered immediately or deleted.


Access control

Per-vault access

  • Access is controlled by Azure RBAC — not vault access policies
  • Minimum role to read: Key Vault Secrets User
  • Minimum role to create or update: Key Vault Secrets Officer
  • No SPN or pipeline should have Key Vault Administrator

Cross-vault access — platform managed identity

The platform managed identity is the mechanism by which HCS tooling and pipelines access secrets in vaults it does not own (TierPoint, customer vaults, etc.).

  • The platform MI must be granted Key Vault Secrets User on any external vault it needs to read
  • If platform tooling needs to write secrets to an external vault, it must be granted Key Vault Secrets Officer on that vault
  • The MI's access grants must be documented in managed-identities.md — each vault it has access to, the role, and the reason
  • No pipeline or script should be granted direct access to an external vault using a SPN secret — use the platform MI

keyvault:// URI across vaults

Config YAML references secrets using the keyvault:// URI scheme. The vault name in the URI must exactly match the vault where the secret lives:

yaml
# HCS vault secret
local_admin_password: keyvault://kv-hcs-vault-01/hcs-hcsprod-azl-hci01-local-admin-password

# TierPoint vault secret (resolved via platform MI access)
local_admin_password: keyvault://kv-tp-tplabs-01/tp-tplabs-azl-hci01-local-admin-password

The Resolve-KeyVaultRef function in platform scripts must support specifying the vault by name from the URI — it cannot assume all secrets live in kv-hcs-vault-01.


Prohibited practices

  • Names without an org prefix: github-pat, domain-admin-password
  • Generic names without a descriptor: password, secret, token, api-key
  • Including region in the name: tp-eus-tplabs-domain-admin-password
  • Using generic env type instead of env name: tp-poc-domain-admin-password
  • Naming SPNs with the type first: spn-arc-registration — purpose always comes first
  • camelCase or PascalCase: tpLabsDomainAdminPassword
  • Creating secrets without an expiry date
  • Creating secrets without the five required tags (including lifecycle)
  • Committing secret values to any file in any repo
  • Using an env-name not present in the env-name registry
  • Using platform as the env-name for any human-tied credential
  • Abbreviating a human account identifier — kjt, azl, hcs are never valid identifiers for a person
  • Storing a password without a paired username secret
  • Using a field value not in the approved field values table

Worked example: renaming personal credentials

The three secrets below were created under old ad-hoc naming. They have been renamed to comply with this standard. The table records the before/after for auditability; old names are listed as "Expired or revoked" in the secret inventory.

Old nameNew username secretNew password secret
hcs-platform-github-kjt-passwordhcs-personal-github-kristopherjturner-usernamehcs-personal-github-kristopherjturner-password
hcs-platform-entra-hcs-passwordhcs-personal-entra-kris-hybridsolutions-cloud-usernamehcs-personal-entra-kris-hybridsolutions-cloud-password
hcs-platform-entra-azl-passwordhcs-personal-entra-kristopher-turner-azurelocal-cloud-usernamehcs-personal-entra-kristopher-turner-azurelocal-cloud-password

Why the old names failed:

  • kjt, hcs, and azl are abbreviations — unrecognisable to anyone who did not create them.
  • platform is the wrong env-name for a human login — platform is reserved for automation credentials.
  • No username counterpart was created alongside any of the passwords.

Copyright © Hybrid Cloud Solutions LLC — Kristopher Turner