shdctl architecture and security
Learn about shdctl trust boundaries, three usage modes, and what shdctl touches on the network and in your cluster
읽는 시간 12분최근 업데이트: 하루 전
This information helps security teams review shdctl. It describes what shdctl touches on the network, in your cluster, and on disk, and the three ways to use it. Each mode has different trust-boundary implications, so pick the one that matches your security posture.
What shdctl does (full capability map)
shdctl consists of five phases. Only Render () is required; every other phase is optional and you can replace it with your existing tooling. There are also two optional steps outside the phases: , which verifies your cluster meets the deployment prerequisites, and , which rebuilds your secrets import file from a running cluster when the original is lost.
release generatecluster checksecret exportThe three usage modes that follow are common combinations of these phases.
You can run at any point after the Setup phase. It reads from the cluster and doesn't change it, unless you pass , which creates and deletes temporary test PVCs. For more information, refer to shdctl cluster command.
cluster check--probe-storageThe following are the three usage modes:
- Mode A (recommended) uses every phase.
- Mode B is Mode A with the Mirror phase made explicit for air-gapped deployments.
- Mode C stops after Render; shdctl hands its outputs to the CD tooling you already run.
Mode A — Recommended (CI)
Mode A runs shdctl only in your CI pipeline. It has two variants, depending on what performs the actual workload deployment:
- Mode A (ArgoCD variant) — recommended. shdctl renders ArgoCD app-of-apps charts, your pipeline commits them to Git, and shdctl applies a single bootstrap to the cluster. ArgoCD takes over from there. shdctl never holds a cluster-wide deployment credential — ArgoCD does.
Application - Mode A (Helm variant). shdctl renders Helm charts and runs directly for every chart in the release. There's no Git commit and no ArgoCD. The CI runner needs a cluster-wide kubeconfig, which is a weaker trust boundary. Use this variant only if you don't run ArgoCD.
helm upgrade --install
Mode A (ArgoCD variant)
Your lives in your Git repo, typically the same repo ArgoCD reads from. The pipeline does the following:
manifest.yaml- Checks out the manifest
- Validates the manifest and checks the cluster
- Pulls the release from the Unity registry
- Optionally mirrors artifacts to your registry
- Renders charts and Secret manifests
- Commits the rendered charts to Git
- Applies the Secrets and a single bootstrap ArgoCD to the cluster
Application
From that point on, ArgoCD pulls charts from your Git repo and deploys workloads. The step numbers below are those of the pipeline steps.
Apart from the read-only , shdctl's cluster touch is limited to two namespace-scoped applies: the Secrets and the bootstrap . All workload deployment after step 7 in the diagram is ArgoCD pulling from Git. ArgoCD holds the cluster-wide deployment credential, not the CI runner.
cluster checkApplicationMode A (Helm variant)
If you don't run ArgoCD, shdctl can deploy directly with Helm. Steps 5, 8, and 9 in the diagram from the ArgoCD variant don't apply because there's no Git commit and no ArgoCD reconcile. Step 3 drops , and step 7 becomes , which runs for every chart in the release.
--format argocdrelease deploy --format helmhelm upgrade --installshdctl runs every chart's from CI. The CI runner kubeconfig must have rights to deploy every chart in the release — cluster-wide, not namespace-scoped. ArgoCD is recommended because it keeps that credential out of CI.
helm upgrade --installMode B — Air-gapped mirror
A bastion or staging host runs shdctl once to mirror Unity release artifacts into your private registry. After the mirror completes, the production cluster, ArgoCD, and your Git repository never reach the public internet. Image references in the rendered charts are rewritten to your registry at generate time.
Only the bastion contacts the Unity registry, and only at pull and sync time. After the sync completes, the deployment is fully internal. Same choice as Mode A; ArgoCD recommended. A Helm deploy stays internal only when the host running it has no Helm chart repository registered; refer to What shdctl touches.
--format helm | argocdThe mirror is scoped to your manifest. copies the images your manifest's enabled features require, not the release's full image set — a deployment with Istio or monitoring turned off does not carry those images into your registry. Two consequences on an air-gapped bastion, where a missing image cannot be fetched at deploy time:
shdctl artifact sync images- Changing a feature toggle means re-running before you deploy. The images that feature needs were never mirrored.
shdctl artifact sync images - reports what a registry is missing for a given manifest, and exits non-zero when anything required is absent — so a pipeline can gate a deploy on it. Run it after a sync, and again after any manifest change, before you deploy.
shdctl artifact sync verify
Pass to to mirror the full image set instead: appropriate for a registry shared by deployments with different feature sets, or where a later feature change must not require another trip to Unity's registry.
--allshdctl artifact sync imagesMode C — Generate-only (bring your own CD)
shdctl renders Helm chart values and Kubernetes manifests from your and the extracted release package, and then stops. Whatever CD tooling you already use, such as , , Flux, or custom pipelines, can apply and . shdctl never holds a kubeconfig in this mode and never reaches your cluster. This is the minimum viable use of shdctl.
Secretmanifest.yamlhelmkubectlgenerated-charts/secrets.yamlIf you want shdctl to run the Helm deploys itself with , that isn't Mode C — it's the Mode A (Helm variant) flow described previously, because shdctl then needs a kubeconfig with cluster-wide deploy rights. Direct Helm deploy shows the commands; for the deploy flags, refer to the release command reference.
shdctl release deploy --format helmWhat shdctl touches
- Outbound network:
- Unity registry () — required for
uccmpprivatecloud.azurecr.ioand the source side ofrelease pull.artifact sync - Your registry — required for the target side of .
artifact syncreads it only: each release manifest the release lists, then one image-manifest lookup per image reference, writing and deleting nothing.artifact sync verify - Helm chart repositories — only for , and only the repositories registered on the host that runs it (
release deploy --format helm). Updating each chart's dependencies makes Helm refresh every registered repository, and withhelm repo listabove 1 shdctl runs--concurrencyup front. The release's charts resolve their dependencies from the package itself, so a host with no repository registered makes no such call.helm repo update - Kubernetes API — used only for ,
secret deploy,release deploy,release uninstall, andcluster check.secret export - : reads secret values —
shdctl secret exportacross the target namespace, to rebuild the import file from what is deployed. It is the only command that copies secret values out of your cluster into a file. It needs a kubeconfig with thekubectl get secretverb on secrets in that namespace — an unnamed collection read is alist, not alist, so a Role granting onlygetis refused withget. Either way it is more than thecannot list resource "secrets"/get/createa deployment pipeline needs, so run it as an operator rather than widening a CI role for it. It writes nothing to the cluster, andpatchprints the command without contacting it.--dry-run - : read-only cluster access, unless you pass
shdctl cluster check. It runs--probe-storagefor the server version, andkubectl versionagainst nodes, storage classes, namespaces, API services, custom resource definitions, and a cluster-wide pod list (kubectl get). The pod list confirms that the ArgoCD controller is running, and the check is reported as skipped if your credentials don't allow it. The command also attempts to read Karpenter node pools (-A), but only when a node pool has no running node, and it tolerates a failure such as a missing custom resource definition or denied access, because Karpenter is optional. Withnodepools.karpenter.shit additionally creates, waits on and deletes a labeled 1Gi test PVC in the target namespace for each class it probes — the cluster default,--probe-storagewhen that is a different class, anddefaultStorageClass— and, only on a probe bind failure, reads events in that namespace for diagnostics. A probe that binds provisions a real volume, which a class withreadWriteManyStorageClasskeeps after the PVC is gone. WithreclaimPolicy: Retainit touches nothing and prints every call the checks would make. For more information, refer to shdctl cluster command.--dry-run
- Unity registry (
- Credentials read:
- Unity registry credentials, for :
release pull, which~/.shdctl/config.jsonwrites. The file is readable only by your user but not encrypted. Ashdctl configureleft by vpctl, the tool's name before 1.0.0, is also read, never written — and~/.vpctl/config.jsonleaves it alone, so delete it yourself once its credentials have moved.shdctl configure delete - Docker credential store () for
~/.docker/config.json: theartifact syncCLI uses it for images,dockerfor ORAS artifacts and the release manifests thatoras-goreads from the source andsync imagesfrom the target.verify - kubeconfig (default discovery and the current context; and
secret deploytakesecret exportto name another — for every other command, choose it with--contextorKUBECONFIG).kubectl config use-context
- Unity registry credentials, for
- Files written (inside the working directory, unless you point an output flag elsewhere):
- — the release archive
unity-private-cloud-shd-<version>.tar.gzdownloads (release pull), kept after extraction.--output - — release archive contents.
extracted-release/ - — rendered Helm charts and ArgoCD
generated-charts/manifests.Application - — rendered Kubernetes
secrets.yamlmanifests, including the image pull secret named bySecretunless that block setsconfiguration.kubernetes.imagePullSecret. Written owner-readable only (generate: false), on first write and on every re-run.0600 - The persist file — created only when you pass to
--persist <path>, at the path you choose. Holds your secrets in the clear, owner-readable only (secret generate).0600 - 's output, at
secret export(default--output). Holds your secrets in the clear, owner-readable only (secrets.import.yaml); an existing file is never overwritten without0600.--force - — only from
manifest.yaml, atmanifest init.--output - The CUE schema — only from , at that path.
manifest schema --export <path>
- Outside the working directory — output flags aside — shdctl writes only the credential file keeps under your home directory (above), plus two temporary items in the system temp directory, each removed when its command finishes:
shdctl configure's download directory andrelease pull's PVC manifest. The tools it drives keep their own state as usual — Docker's local image store, Helm's cache.cluster check --probe-storage - What shdctl never does: send telemetry, call back to Unity, auto-update.