diff --git a/helper/activationflags/activation_flags.go b/helper/activationflags/activation_flags.go index fb031cae8399..66579b3da85d 100644 --- a/helper/activationflags/activation_flags.go +++ b/helper/activationflags/activation_flags.go @@ -61,14 +61,32 @@ func (f *FeatureActivationFlags) Initialize(ctx context.Context, storage logical // actual values from storage, then updates the in-memory cache of the activation-flags. It // returns a slice of the feature names which have already been activated. func (f *FeatureActivationFlags) Get(ctx context.Context) ([]string, error) { + // Don't use nil slice declaration, we want the JSON to show "[]" instead of null + activated := []string{} + + _, err := f.ReloadFlagsFromStorage(ctx) + if err != nil { + return activated, err + } + f.activationFlagsLock.Lock() defer f.activationFlagsLock.Unlock() - // Don't use nil slice declaration, we want the JSON to show "[]" instead of null - activated := []string{} + for flag, set := range f.activationFlags { + if set { + activated = append(activated, flag) + } + } + + return activated, nil +} + +func (f *FeatureActivationFlags) ReloadFlagsFromStorage(ctx context.Context) (map[string]bool, error) { + f.activationFlagsLock.Lock() + defer f.activationFlagsLock.Unlock() if f.storage == nil { - return activated, nil + return map[string]bool{}, nil } entry, err := f.storage.Get(ctx, storagePathActivationFlags) @@ -76,24 +94,51 @@ func (f *FeatureActivationFlags) Get(ctx context.Context) ([]string, error) { return nil, fmt.Errorf("failed to get activation flags from storage: %w", err) } if entry == nil { - return activated, nil + return map[string]bool{}, nil } - var activationFlags map[string]bool - if err := entry.DecodeJSON(&activationFlags); err != nil { + var storageActivationFlags map[string]bool + if err := entry.DecodeJSON(&storageActivationFlags); err != nil { return nil, fmt.Errorf("failed to decode activation flags from storage: %w", err) } - // Update the in-memory flags after loading the latest values from storage - f.activationFlags = activationFlags - - for flag, set := range activationFlags { - if set { - activated = append(activated, flag) + // State Change Logic for Flags + // + // This logic determines changes to flags, but it does NOT account for flags that have been deleted. + // As of this writing, flag removal is not supported for activation flags. + // + // Valid State Transitions: + // 1. Unset (new flag) -> Active + // 2. Active -> Inactive + // 3. Inactive -> Active + // + // Behavior notes: + // - If a flag does not exist in-memory (`!ok`), it is treated as a new flag. + // Nodes should only react to the new flag if its state is being set to "Active". + // - If a flag exists in-memory, any change in its value (e.g., Active -> Inactive) is considered valid + // and is marked as a state change. + // + // The resulting `changedFlags` map will store the flags with their new values if they meet the above criteria. + changedFlags := map[string]bool{} + for flg, v := range storageActivationFlags { + oldValue, ok := f.activationFlags[flg] + + switch { + // New flag: handle only if transitioning to "Active (true)" + case !ok && v: + changedFlags[flg] = v + default: + // Existing flag: detect state change + if oldValue != v { + changedFlags[flg] = v + } } } - return activated, nil + // Update the in-memory flags after loading the latest values from storage + f.activationFlags = storageActivationFlags + + return changedFlags, nil } // Write is the helper function called by the activation-flags API write endpoint. This stores diff --git a/website/content/docs/platform/k8s/injector/annotations.mdx b/website/content/docs/platform/k8s/injector/annotations.mdx index 1f52b4044bb4..da38b72f4629 100644 --- a/website/content/docs/platform/k8s/injector/annotations.mdx +++ b/website/content/docs/platform/k8s/injector/annotations.mdx @@ -28,7 +28,7 @@ them, optional commands to run, etc. - `vault.hashicorp.com/agent-image` - name of the Vault docker image to use. This value overrides the default image configured in the injector and is usually - not needed. Defaults to `hashicorp/vault:1.18.1`. + not needed. Defaults to `hashicorp/vault:1.18.2`. - `vault.hashicorp.com/agent-init-first` - configures the pod to run the Vault Agent init container first if `true` (last if `false`). This is useful when other init diff --git a/website/content/docs/platform/k8s/injector/index.mdx b/website/content/docs/platform/k8s/injector/index.mdx index b7426fa6538a..69914c82f739 100644 --- a/website/content/docs/platform/k8s/injector/index.mdx +++ b/website/content/docs/platform/k8s/injector/index.mdx @@ -189,6 +189,33 @@ The configuration map must contain either one or both of the following files: An example of mounting a Vault Agent configmap [can be found here](/vault/docs/platform/k8s/injector/examples#configmap-example). +### Injector telemetry + + + +Set [`injector.metrics.enabled`](/vault/docs/platform/k8s/helm/configuration#metrics) +to `true` in the Helm chart to start collecting injector metrics. + + + +Vault Agent injector collects the following Prometheus metrics in addition to +the default set of `golang` metrics: + +- `vault_agent_injector_request_queue_length` - The number of pending webhook requests for the injector. + +- `vault_agent_injector_request_processing_duration_ms` - A histogram of webhook + request processing times in milliseconds. + +- `vault_agent_injector_injections_by_namespace_total` - The total count of + Agent container injections, grouped by Kubernetes `namespace` and `injection_type`. + Vault Agent injector counts the following injection types: + - `init_only` + - `sidecar_only` + - `init_and_sidecar` + +- `vault_agent_injector_failed_injections_by_namespace_total` - The total count + of failed Agent sidecar injections, grouped by Kubernetes `namespace`. + ## Tutorial Refer to the [Injecting Secrets into Kubernetes Pods via Vault Helm