Class: RKSeal::Kubeseal
- Inherits:
-
Object
- Object
- RKSeal::Kubeseal
- Defined in:
- lib/rkseal/kubeseal.rb
Overview
Thin adapter over the ‘kubeseal` binary.
Owns everything kubeseal-flag-shaped: scope, certificate source, and the controller’s name/namespace. Each public method maps to one kubeseal invocation and returns its stdout (the produced SealedSecret YAML or a PEM certificate). Nothing here parses YAML or knows about the domain model – callers pass in manifest text and get sealed text back.
Developed against kubeseal v0.36.6; flag names below assume that CLI.
All process execution funnels through one private runner so that unit tests stub a single seam (or stub the public methods directly). The runner must never echo stdin (the plaintext Secret) into logs or error messages.
rubocop:disable Metrics/ClassLength – the inline CertCache is co-located here by design (the cert cache is intrinsic to this adapter and must not add a new top-level require); that nested class accounts for the extra lines.
Defined Under Namespace
Classes: CertCache
Constant Summary collapse
- BINARY =
"kubeseal"- SCOPES =
Allowed sealing scopes, mapped to their kubeseal ‘–scope` argument.
{ strict: "strict", namespace_wide: "namespace-wide", cluster_wide: "cluster-wide" }.freeze
- DEFAULT_CONTROLLER_NAME =
kubeseal’s own defaults for the controller’s identity. Used to name the cache entry consistently when the caller does not override them, so a run with implicit defaults and a run with explicit-but-identical flags share one cached cert.
"sealed-secrets-controller"- DEFAULT_CONTROLLER_NAMESPACE =
"kube-system"- VALIDATION_FAILURE_MARKER =
Substring kubeseal prints to stderr when the controller could decrypt-test the SealedSecret but it is NOT valid. Anything else on a non-zero ‘–validate` exit is treated as operational (CommandError), not a verdict.
"unable to decrypt"
Instance Method Summary collapse
-
#ensure_available! ⇒ void
Verify the kubeseal binary is present and executable; raise otherwise.
-
#ensure_cert! ⇒ void
Resolve the encryption certificate up front so a flow fails fast before any editor opens.
-
#fetch_cert ⇒ String
Fetch the controller’s public certificate (‘kubeseal –fetch-cert`) so it can be cached and reused, avoiding an API round-trip per seal.
-
#initialize(binary: BINARY, controller_name: nil, controller_namespace: nil, cert: nil, refresh_cert: false) ⇒ Kubeseal
constructor
A new instance of Kubeseal.
-
#merge_into(manifest_yaml, file:, scope: :strict) ⇒ void
Blind-append freshly-encrypted items to an existing SealedSecret file (‘kubeseal –merge-into <file>`).
-
#re_encrypt(sealed_yaml) ⇒ String
Upgrade an existing SealedSecret to the controller’s newest key without exposing plaintext (‘kubeseal –re-encrypt`).
-
#seal(manifest_yaml, scope: :strict) ⇒ String
Seal a Secret manifest into a SealedSecret.
-
#validate(sealed_secret_yaml) ⇒ true
Validate that a SealedSecret can be decrypted by the controller (‘kubeseal –validate`, SealedSecret piped on stdin).
Constructor Details
#initialize(binary: BINARY, controller_name: nil, controller_namespace: nil, cert: nil, refresh_cert: false) ⇒ Kubeseal
Returns a new instance of Kubeseal.
54 55 56 57 58 59 60 61 |
# File 'lib/rkseal/kubeseal.rb', line 54 def initialize(binary: BINARY, controller_name: nil, controller_namespace: nil, cert: nil, refresh_cert: false) @binary = binary @controller_name = controller_name @controller_namespace = controller_namespace @cert = cert @refresh_cert = refresh_cert end |
Instance Method Details
#ensure_available! ⇒ void
This method returns an undefined value.
Verify the kubeseal binary is present and executable; raise otherwise. Called early so the flow fails fast on a missing dependency.
68 69 70 71 72 73 74 |
# File 'lib/rkseal/kubeseal.rb', line 68 def ensure_available! return if executable_on_path?(@binary) raise DependencyMissingError, "kubeseal not found on PATH (looked for #{@binary.inspect}). " \ "Install it from https://github.com/bitnami-labs/sealed-secrets/releases." end |
#ensure_cert! ⇒ void
This method returns an undefined value.
Resolve the encryption certificate up front so a flow fails fast before any editor opens. When an offline cert is configured (‘–cert` or the `SEALED_SECRETS_CERT` env var) nothing is contacted. Otherwise the cert is resolved through the on-disk cache: a cached PEM is reused as-is, otherwise it is fetched from the live controller and written to the cache (which is what makes a subsequent #seal offline). `refresh_cert: true` skips the cached copy and refetches.
87 88 89 90 91 92 |
# File 'lib/rkseal/kubeseal.rb', line 87 def ensure_cert! return if offline_cert? resolve_cached_cert_path nil end |
#fetch_cert ⇒ String
Fetch the controller’s public certificate (‘kubeseal –fetch-cert`) so it can be cached and reused, avoiding an API round-trip per seal.
NOTE: unlike #seal, this method contacts the cluster API by design.
151 152 153 |
# File 'lib/rkseal/kubeseal.rb', line 151 def fetch_cert run("--fetch-cert", *controller_flags) end |
#merge_into(manifest_yaml, file:, scope: :strict) ⇒ void
This method returns an undefined value.
Blind-append freshly-encrypted items to an existing SealedSecret file (‘kubeseal –merge-into <file>`). Does NOT decrypt anything: it appends or overwrites the items in the input Secret while leaving every other sealed entry untouched. This is what powers the offline `edit –local` flow, where kept keys must stay byte-for-byte unchanged.
The certificate is resolved offline-first, exactly like #seal: an explicit ‘–cert` or the cached controller PEM is passed via `–cert` (no API round-trip); only when neither is available does kubeseal fall back to the env var or the controller. The output format is inherited from the existing file, so `-o` is NOT forced here.
172 173 174 175 176 177 178 179 180 |
# File 'lib/rkseal/kubeseal.rb', line 172 def merge_into(manifest_yaml, file:, scope: :strict) argv = ["--merge-into", file, "--scope", scope_flag(scope)] cert_path = resolved_cert_path argv += ["--cert", cert_path] if cert_path argv += controller_flags run(*argv, stdin: manifest_yaml) nil end |
#re_encrypt(sealed_yaml) ⇒ String
Upgrade an existing SealedSecret to the controller’s newest key without exposing plaintext (‘kubeseal –re-encrypt`). Out of scope for the initial create/edit flows but part of the adapter surface.
NOTE: contacts the cluster API by design.
191 192 193 |
# File 'lib/rkseal/kubeseal.rb', line 191 def re_encrypt(sealed_yaml) run("--re-encrypt", "-o", "yaml", *controller_flags, stdin: sealed_yaml) end |
#seal(manifest_yaml, scope: :strict) ⇒ String
Seal a Secret manifest into a SealedSecret.
Pipes ‘manifest_yaml` to kubeseal on stdin with `-o yaml` and the resolved `–scope`. The certificate is resolved offline-first: an explicit `–cert` or the cached controller PEM is passed via `–cert` (no API round-trip); only when neither is available does kubeseal fall back to the env var or the controller itself. Returns the SealedSecret YAML on stdout.
109 110 111 112 113 114 115 116 117 118 |
# File 'lib/rkseal/kubeseal.rb', line 109 def seal(manifest_yaml, scope: :strict) # `-o yaml` is mandatory: kubeseal defaults to JSON, so without it the # output written to `<name>.yaml` would actually contain JSON. argv = ["--scope", scope_flag(scope), "-o", "yaml"] cert_path = resolved_cert_path argv += ["--cert", cert_path] if cert_path argv += controller_flags run(*argv, stdin: manifest_yaml) end |
#validate(sealed_secret_yaml) ⇒ true
Validate that a SealedSecret can be decrypted by the controller (‘kubeseal –validate`, SealedSecret piped on stdin). Contacts the cluster: the controller performs the decrypt-test.
kubeseal v0.36.6 exits 0 when valid and non-zero otherwise, printing the reason to stderr. A non-zero exit whose stderr names a decrypt failure is a validity verdict (ValidationError); any other non-zero exit (missing binary, unreachable cluster, controller service not found) is operational (CommandError) and says nothing about the SealedSecret itself.
134 135 136 137 138 139 140 141 142 |
# File 'lib/rkseal/kubeseal.rb', line 134 def validate(sealed_secret_yaml) run("--validate", *controller_flags, stdin: sealed_secret_yaml) true rescue CommandError => e raise unless validation_failure?(e.stderr) raise ValidationError, "SealedSecret failed validation: #{e.stderr.strip}" end |