apiVersion: v1
kind: Pod
metadata:
  name: s3-encrypted-file-credentials
  # setup.sh fills in the namespace, the Region, the bucket, the object key, the KMS key
  # ARN and the wrapped data key at apply time.
  labels:
    app.kubernetes.io/name: s3-encrypted-file-credentials
    app.kubernetes.io/managed-by: s3-encrypted-file
  annotations:
    karpenter.sh/do-not-disrupt: "true"
spec:
  # No runtimeClassName: an ordinary pod. It stands for anyone who holds the pod's AWS
  # credentials without being the attested sandbox, which in confidential mode includes
  # the node itself: the parent instance can obtain the same Pod Identity credentials the
  # workload uses, either by reading them as they are delivered or by minting them from
  # the kubelet's own credentials and the node role (Amazon EKS User Guide, "Security
  # model and parent instance trust assumptions").
  #
  # It uses the SAME service account as the sandboxed pod, so EKS Pod Identity hands it the
  # SAME IAM role. It reads the SAME encrypted object, which the bucket policy permits, and
  # then asks AWS KMS to decrypt the SAME wrapped data key. The only thing it does not have
  # is an attestation document.
  serviceAccountName: s3-encrypted-file-pod
  automountServiceAccountToken: false
  enableServiceLinks: false
  restartPolicy: Never
  terminationGracePeriodSeconds: 0
  securityContext:
    runAsUser: 65534
    runAsGroup: 65534
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  volumes:
    # The AWS CLI writes a cache under HOME, and the root filesystem is read-only.
    - name: home
      emptyDir: {}
  containers:
    - name: main
      # The AWS CLI image, because this pod has to make a real S3 read and a real KMS
      # request. It is not the sample's application image: nothing here decrypts a file,
      # and nothing here can.
      image: public.ecr.aws/aws-cli/aws-cli:2.31.19
      imagePullPolicy: IfNotPresent
      env:
        - name: AWS_REGION
          value: <AWS_REGION>
        - name: AWS_DEFAULT_REGION
          value: <AWS_REGION>
        - name: HOME
          value: /home/aws
        # Fail a call that cannot reach AWS quickly, so a retry below comes round again in
        # seconds rather than minutes. The answer this pod is after is an authorization
        # decision, and the CLI's long default retry budget only delays reaching it.
        - name: AWS_MAX_ATTEMPTS
          value: "2"
        - name: S3_BUCKET
          value: <S3_BUCKET>
        - name: S3_CIPHERTEXT_KEY
          value: <S3_CIPHERTEXT_KEY>
        - name: KMS_KEY_ARN
          value: <KMS_KEY_ARN>
        # The base64 wrapped data key, read out of the manifest that sits beside the
        # ciphertext in S3. Anyone the bucket policy lets read the object already holds it;
        # the sample's claim is that it is useless without an attestation document the key
        # policy accepts.
        - name: WRAPPED_DATA_KEY
          value: <WRAPPED_DATA_KEY>
      # Reads the object, then asks for the data key. If KMS ever returned the key it would
      # be written to a file and deleted without being read, so a data key could not reach
      # the logs even if the gate failed. What the page reads is the exit status and the
      # error.
      #
      # The pod waits before it asks, and the request is retried, because a pod's datapath
      # can take a few seconds to settle after it starts and a connection that never reaches
      # KMS is not an answer about the key policy. The loop stops as soon as the answer is a real one: either an
      # authorization denial, or a success, which would mean the gate had failed. Five
      # attempts with these timeouts take about six minutes at worst, which is inside the
      # window setup.sh waits for this pod's marker before it replaces the pod: a pod that
      # was still deciding when the script gave up on it would be reported as having reached
      # no answer when it had not finished asking.
      #
      # The sed expressions are there so nothing in the output identifies the account or
      # the cluster: KMS names the full role session in its error, and an EKS Pod Identity
      # session name is built from the cluster and pod names.
      #
      # The account id is matched between the colons of an ARN, and not as a bare run of
      # twelve digits: this pod prints a SHA-256 of the object it read, and twelve
      # consecutive digits inside a digest would otherwise be rewritten into the
      # placeholder, which would publish a digest that is not the one it computed.
      #
      # The substitution runs twice because it consumes the colons it matched, and sed does
      # not rescan a replacement: two account ids sharing one colon would otherwise leave
      # the second one in place. Python's lookarounds do not have that problem, which is
      # why src/app.py needs only one pass.
      command:
        - /bin/sh
        - -c
        - >-
          redact() { sed -e 's/:[0-9]\{12\}:/:<ACCOUNT_ID>:/g' -e 's/:[0-9]\{12\}:/:<ACCOUNT_ID>:/g' -e 's|\(assumed-role/[^/]*/\)[^ "]*|\1<session>|g'; };
          aws_fast() { aws --cli-connect-timeout 5 --cli-read-timeout 15 "$@"; };
          sleep 20;
          echo "role:    $(aws_fast sts get-caller-identity --query Arn --output text 2>/dev/null | redact)";
          aws_fast s3api get-object --bucket "$S3_BUCKET" --key "$S3_CIPHERTEXT_KEY" "$HOME/object" > /dev/null 2> "$HOME/s3error" || true;
          if [ -s "$HOME/object" ]; then
          echo "s3:      read $(wc -c < "$HOME/object") bytes of ciphertext, sha256 $(sha256sum "$HOME/object" | cut -d' ' -f1)";
          else echo "s3:      could not read the object: $(redact < "$HOME/s3error")"; fi;
          echo "request: kms:Decrypt on the file's wrapped data key, with no attestation document";
          printf '%s' "$WRAPPED_DATA_KEY" | base64 -d > "$HOME/wrapped-data-key.bin";
          rc=0;
          for attempt in $(seq 5); do
          rc=0;
          aws_fast kms decrypt --key-id "$KMS_KEY_ARN" --ciphertext-blob "fileb://$HOME/wrapped-data-key.bin"
          --query Plaintext --output text > "$HOME/plaintext" 2> "$HOME/error" || rc=$?;
          rm -f "$HOME/plaintext";
          if [ "$rc" = 0 ]; then break; fi;
          if grep -Eiq 'AccessDenied|not authorized|explicit deny' "$HOME/error"; then break; fi;
          echo "retry:   attempt $attempt reached no decision, retrying";
          sleep 10;
          done;
          echo "exit:    $rc";
          redact < "$HOME/error";
          echo s3-encrypted-file-credentials-done
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL
      volumeMounts:
        - name: home
          mountPath: /home/aws
      resources:
        requests:
          cpu: 100m
          memory: 256Mi
