Skip to content

Example - Outdated Secret Data

Introduction

There is an easy way to identify unhealthy Kubernetes resources with pods with outdated secrets.

Example - Pod with Outdated Secret Data

The below Cleaner instance finds all Pods in all namespaces mounting Secrets. It identifies and reports any pods that are accessing Secrets that have been modified since the Pod's creation.

This is crucial for maintaining data integrity and security, as it prevents Pods from accessing potentially outdated or compromised secrets.

By identifying and reporting pods that are accessing modified secrets, the Cleaner instance helps to safeguard applications and sensitive data.

---
apiVersion: apps.projectsveltos.io/v1alpha1
kind: Cleaner
metadata:
  name: list-pods-with-outdated-secret-data
spec:
  action: Scan 
  schedule: "0 * * * *"
  notifications:
  - name: report
    type: CleanerReport
  resourcePolicySet:
    resourceSelectors:
    - kind: Pod
      group: ""
      version: v1
    - kind: Secret
      group: ""
      version: v1
    aggregatedSelection: |
      function getKey(namespace, name)
        return namespace .. ":" .. name
      end

      --  Convert creationTimestamp "2023-12-12T09:35:56Z"
      function convertTimestampString(timestampStr)
        local convertedTimestamp = string.gsub(
          timestampStr,
          '(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)Z',
          function(y, mon, d, h, mi, s)
            return os.time({
              year = tonumber(y),
              month = tonumber(mon),
              day = tonumber(d),
              hour = tonumber(h),
              min = tonumber(mi),
              sec = tonumber(s)
            })
          end
        )
        return convertedTimestamp
      end

      function getLatestTime(times)
        local latestTime = nil
        for _, time in ipairs(times) do
          if latestTime == nil or os.difftime(tonumber(time), tonumber(latestTime)) > 0 then
            latestTime = time
          end
        end
        return latestTime
      end

      function getSecretUpdateTime(secret)
        local times = {}
        if secret.metadata.managedFields ~= nil then
          for _, mf in ipairs(secret.metadata.managedFields) do
            if mf.time ~= nil then
              table.insert(times, convertTimestampString(mf.time))
            end
          end
        end

        return getLatestTime(times)
      end

      function isPodOlderThanSecret(podTimestamp, secretTimestamp)
        timeDifference = os.difftime(tonumber(podTimestamp), tonumber(secretTimestamp))
        return  timeDifference < 0
      end

      function getPodTimestamp(pod)
        if pod.status ~= nil and pod.status.conditions ~= nil then
          for _,condition in ipairs(pod.status.conditions) do
            if condition.type == "PodReadyToStartContainers" and condition.status == "True" then
              return convertTimestampString(condition.lastTransitionTime)
            end
          end
        end
        return convertTimestampString(pod.metadata.creationTimestamp)
      end

      function hasOutdatedSecret(pod, secrets)
        podTimestamp = getPodTimestamp(pod)

        if pod.spec.containers ~= nil then
          for _, container in ipairs(pod.spec.containers) do

            if container.env ~= nil then
              for _, env in ipairs(container.env) do
                if env.valueFrom ~= nil and env.valueFrom.secretKeyRef ~= nil then
                  key = getKey(pod.metadata.namespace, env.valueFrom.secretKeyRef.name)
                  if isPodOlderThanSecret(podTimestamp, secrets[key]) then
                    return true, "secret " .. key .. " has been updated after pod creation"
                  end
                end
              end
            end  

            if  container.envFrom ~= nil then
              for _, envFrom in ipairs(container.envFrom) do
                if envFrom.secretRef ~= nil then
                  key = getKey(pod.metadata.namespace, envFrom.secretRef.name)
                  if isPodOlderThanSecret(podTimestamp, secrets[key]) then
                    return true, "secret " .. key .. " has been updated after pod creation"
                  end
                end
              end  
            end
          end
        end

        if pod.spec.initContainers ~= nil then
          for _, initContainer in ipairs(pod.spec.initContainers) do
            if initContainer.env ~= nil then
              for _, env in ipairs(initContainer.env) do
                if env.valueFrom ~= nil and env.valueFrom.secretKeyRef ~= nil then
                  key = getKey(pod.metadata.namespace, env.valueFrom.secretKeyRef.name)
                  if isPodOlderThanSecret(podTimestamp, secrets[key]) then
                    return true, "secret " .. key .. " has been updated after pod creation"
                  end
                end
              end
            end
          end
        end

        if pod.spec.volumes ~= nil then  
          for _, volume in ipairs(pod.spec.volumes) do
            if volume.secret ~= nil then
              key = getKey(pod.metadata.namespace, volume.secret.secretName)
              if isPodOlderThanSecret(podTimestamp, secrets[key]) then
                return true, "secret " .. key .. " has been updated after pod creation"
              end
            end

            if volume.projected ~= nil and volume.projected.sources ~= nil then
              for _, projectedResource in ipairs(volume.projected.sources) do
                if projectedResource.secret ~= nil then
                  key = getKey(pod.metadata.namespace, projectedResource.secret.name)
                  if isPodOlderThanSecret(podTimestamp, secrets[key]) then
                    return true, "secret " .. key .. " has been updated after pod creation"
                  end
                end
              end
            end
          end
        end

        return false
      end      

      function evaluate()
        local hs = {}
        hs.message = ""

        local pods = {}
        local secrets = {}

        -- Separate secrets and pods
        for _, resource in ipairs(resources) do
          local kind = resource.kind
          if kind == "Secret" then
            key = getKey(resource.metadata.namespace, resource.metadata.name)
            updateTimestamp = getSecretUpdateTime(resource)
            secrets[key] = updateTimestamp
          elseif kind == "Pod" then
            table.insert(pods, resource)
          end
        end

        local podsWithOutdatedSecret = {}

        for _, pod in ipairs(pods) do
          outdatedData, message = hasOutdatedSecret(pod, secrets)
          if outdatedData then
            table.insert(podsWithOutdatedSecret, {resource= pod, message = message})
          end
        end

        if #podsWithOutdatedSecret > 0 then
          hs.resources = podsWithOutdatedSecret
        end
        return hs
      end