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.
The controller certificate is never cached on disk: an explicit ‘–cert` or `SEALED_SECRETS_CERT` is used offline, otherwise it is fetched fresh from the live controller on every invocation. This keeps a seal always bound to the current context’s controller key – no stale or cross-cluster cert can sneak in (see #ensure_cert!).
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
- 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
Confirm the encryption certificate is obtainable 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) ⇒ 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) ⇒ Kubeseal
Returns a new instance of Kubeseal.
45 46 47 48 49 50 51 |
# File 'lib/rkseal/kubeseal.rb', line 45 def initialize(binary: BINARY, controller_name: nil, controller_namespace: nil, cert: nil) @binary = binary @controller_name = controller_name @controller_namespace = controller_namespace @cert = 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.
58 59 60 61 62 63 64 |
# File 'lib/rkseal/kubeseal.rb', line 58 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.
Confirm the encryption certificate is obtainable 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 controller is probed with `–fetch-cert`; the fetched PEM is intentionally discarded – #seal re-fetches at seal time, so the freshest controller key is always used and nothing is persisted between invocations.
76 77 78 79 80 81 |
# File 'lib/rkseal/kubeseal.rb', line 76 def ensure_cert! return if offline_cert? fetch_cert 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.
140 141 142 |
# File 'lib/rkseal/kubeseal.rb', line 140 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 exactly like #seal: an explicit ‘–cert` is passed through, otherwise kubeseal resolves it itself (env var, else fresh from the controller). The output format is inherited from the existing file, so `-o` is NOT forced here.
160 161 162 163 164 165 166 167 168 |
# File 'lib/rkseal/kubeseal.rb', line 160 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.
179 180 181 |
# File 'lib/rkseal/kubeseal.rb', line 179 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`. An explicit `–cert` is passed straight through; otherwise no `–cert` is given and kubeseal resolves the cert itself – from `SEALED_SECRETS_CERT`, or failing that fresh from the live controller. Returns the SealedSecret YAML on stdout.
98 99 100 101 102 103 104 105 106 107 |
# File 'lib/rkseal/kubeseal.rb', line 98 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.
123 124 125 126 127 128 129 130 131 |
# File 'lib/rkseal/kubeseal.rb', line 123 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 |