Class: RKSeal::SealedSecret

Inherits:
Object
  • Object
show all
Defined in:
lib/rkseal/sealed_secret.rb

Overview

Domain model for the SealedSecret resource – the encrypted, on-disk counterpart of Secret.

Unlike a Secret, a SealedSecret cannot be decrypted client-side: its ‘spec.encryptedData` values are opaque ciphertext. What is readable is the set of data keys (the map keys are plaintext), the sealing scope (a metadata annotation), and the template `type`. That readable surface is exactly what powers the offline `edit –local` flow: rkseal can show the user which keys exist and let them keep / replace / add / remove keys without ever seeing the current values.

This model is the single place that knows how to:

- parse a local `<name>.yaml` SealedSecret into that readable surface;
- render a *redacted* editor buffer -- a Secret manifest in which every
  existing key is shown under `stringData` as {REDACTED_PLACEHOLDER}, so a
  value left untouched means "keep the current ciphertext".

No method here shells out, touches the cluster, or decrypts anything; it is pure data transformation and trivially unit-testable.

Constant Summary collapse

API_VERSION =

apiVersion/kind this model represents.

"bitnami.com/v1alpha1"
KIND =
"SealedSecret"
REDACTED_PLACEHOLDER =

Placeholder shown for every existing key in the ‘edit –local` buffer. Because the ciphertext cannot be decrypted, the current value is never revealed; leaving this token in place means “keep the sealed value as-is”.

"<redacted>"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, namespace:, scope:, type:, encrypted_keys:) ⇒ SealedSecret

Returns a new instance of SealedSecret.

Parameters:

  • name (String)
  • namespace (String, nil)
  • scope (Symbol)
  • type (String)
  • encrypted_keys (Array<String>)


149
150
151
152
153
154
155
# File 'lib/rkseal/sealed_secret.rb', line 149

def initialize(name:, namespace:, scope:, type:, encrypted_keys:)
  @name = name
  @namespace = namespace
  @scope = scope
  @type = type
  @encrypted_keys = encrypted_keys.freeze
end

Instance Attribute Details

#encrypted_keysArray<String> (readonly)

Returns the data keys present in ‘spec.encryptedData` (plaintext keys; the values are ciphertext and are not held here).

Returns:

  • (Array<String>)

    the data keys present in ‘spec.encryptedData` (plaintext keys; the values are ciphertext and are not held here).



47
48
49
# File 'lib/rkseal/sealed_secret.rb', line 47

def encrypted_keys
  @encrypted_keys
end

#nameString (readonly)

Returns the SealedSecret name.

Returns:

  • (String)

    the SealedSecret name.



37
38
39
# File 'lib/rkseal/sealed_secret.rb', line 37

def name
  @name
end

#namespaceString? (readonly)

Returns the namespace.

Returns:

  • (String, nil)

    the namespace.



39
40
41
# File 'lib/rkseal/sealed_secret.rb', line 39

def namespace
  @namespace
end

#scopeSymbol (readonly)

Returns sealing scope (:strict, :namespace_wide, :cluster_wide), derived from the metadata annotation (see RKSeal::Secret.scope_from_sealed_json).

Returns:



42
43
44
# File 'lib/rkseal/sealed_secret.rb', line 42

def scope
  @scope
end

#typeString (readonly)

Returns the template Secret ‘type` (e.g. “Opaque”).

Returns:

  • (String)

    the template Secret ‘type` (e.g. “Opaque”).



44
45
46
# File 'lib/rkseal/sealed_secret.rb', line 44

def type
  @type
end

Class Method Details

.diverged?(local, cluster) ⇒ Boolean

Whether the sealed payload (‘spec.encryptedData` + `spec.template`) of two SealedSecret documents differs. `kubectl apply` stores the manifest verbatim, so right after a deploy the local `<name>.yaml` and the cluster object share an identical payload; an unequal payload therefore means the local file is *ahead of* (or absent from) the cluster – i.e. it carries un-deployed changes. Re-sealing is non-deterministic, so equal payload is only ever produced by the exact same applied file – there are no false “equal” verdicts that could mask drift. Tolerant of JSON (kubectl) and YAML (the local file) alike, and of malformed input (treated as drift, so the user’s local file is never silently overwritten).

Parameters:

  • local (String, Hash)

    the local SealedSecret (YAML text or Hash).

  • cluster (String, Hash)

    the cluster SealedSecret (JSON/YAML or Hash).

Returns:

  • (Boolean)


86
87
88
# File 'lib/rkseal/sealed_secret.rb', line 86

def diverged?(local, cluster)
  sealed_payload(local) != sealed_payload(cluster)
end

.parse(yaml) ⇒ RKSeal::SealedSecret

Parse a SealedSecret manifest (the local ‘<name>.yaml`) into the model.

Parameters:

  • yaml (String, Hash)

    raw YAML/JSON text or a pre-parsed Hash.

Returns:

Raises:

  • (RKSeal::InvalidInputError)

    if the document is empty, not valid YAML, not a SealedSecret, or carries a non-mapping ‘encryptedData`.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/rkseal/sealed_secret.rb', line 56

def parse(yaml)
  doc = yaml.is_a?(Hash) ? yaml : load_yaml(yaml)
  unless doc.is_a?(Hash)
    raise InvalidInputError, "not a SealedSecret manifest (expected a YAML mapping)"
  end

  validate_kind!(doc)
  new(
    name: fetch_name(doc),
    namespace: doc.dig("metadata", "namespace"),
    scope: Secret.scope_from_sealed_json(doc),
    type: doc.dig("spec", "template", "type") || Secret::DEFAULT_TYPE,
    encrypted_keys: encrypted_keys(doc)
  )
end

Instance Method Details

#to_buffer(commented: true, string_data: false) ⇒ String

Render the redacted editor buffer for the offline local edit: a Kubernetes Secret manifest in which every existing key is shown as REDACTED_PLACEHOLDER. The operator keeps a value by leaving the placeholder, replaces it by typing a new value, adds keys by adding lines, and removes keys by deleting lines.

By default the keys sit under ‘data` (so replacements are base64, matching the rest of the tool). With `string_data: true` they sit under `stringData`, so replacements/new keys are entered as plaintext.

Parameters:

  • commented (Boolean) (defaults to: true)

    include the explanatory header comment.

  • string_data (Boolean) (defaults to: false)

    place the keys under ‘stringData` (plaintext) instead of `data` (base64).

Returns:

  • (String)

    YAML suitable to hand to Editor.



171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/rkseal/sealed_secret.rb', line 171

def to_buffer(commented: true, string_data: false)
  body = {
    "apiVersion" => Secret::API_VERSION,
    "kind" => Secret::KIND,
    "metadata" => { "name" => name, "namespace" => namespace },
    "type" => type,
    (string_data ? "stringData" : "data") =>
      encrypted_keys.to_h { |key| [key, REDACTED_PLACEHOLDER] }
  }

  yaml = YAML.dump(body).delete_prefix("---\n")
  commented ? "#{buffer_header(string_data)}#{yaml}" : yaml
end