Verify before you push¶
The first transfer copied the file without looking at it. Anything you transfer, you also
distribute, so in this step the workflow checks the file before pushing it: it compares a checksum and scans it for
viruses. If a check fails, the file never reaches the destination.
All files are in examples/walk-through/02-verify.
This step builds on the resources of the first transfer. The Secret, the Endpoints and the ArtifactType stay
as they are, only the WorkflowTemplate changes and a new Order asks for the checksum.
WorkflowTemplate¶
The new blob-transfer replaces the one from the first transfer. Instead of a single script, it runs four
steps one after another: pull, checksum,
virus-scan and push.
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: blob-transfer # same name as in step 1: this replaces the template
spec:
serviceAccountName: arc-workflow
# ARC passes these parameters to every workflow it creates. All values are strings.
arguments:
parameters:
# from the source Endpoint
- name: srcType
- name: srcRemoteURL
- name: srcSecret # "true" if the Endpoint has a secretRef, otherwise "false"
# from the destination Endpoint
- name: dstType
- name: dstRemoteURL
- name: dstSecret
# from the artifact spec in the Order: spec.key -> specKey, ...
- name: specKey
- name: specOverride
- name: specSha256 # no default: orders without a checksum fail
# Also passed by ARC, but not used by this template:
# - name: cron # "true" if the artifact is scheduled by a cron, otherwise "false"
# - name: <name> # every parameter of the ArtifactType, overriding the ones above
# Steps run in pods of their own, so they share files through a volume. Argo creates
# this PersistentVolumeClaim for every workflow and deletes it when the workflow is done.
# The size is fixed here, examples/blob sizes the volume from the blob instead.
volumeClaimTemplates:
- metadata:
name: work
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 1Gi
entrypoint: transfer
templates:
# Every step runs after the previous one succeeded. If a check fails, the
# workflow stops and the blob never reaches the destination.
- name: transfer
steps:
- - name: pull
template: pull
- - name: checksum
template: checksum
- - name: virus-scan
template: virus-scan
- - name: push
template: push
- name: pull
script:
image: amazon/aws-cli:2.37.9
command: [bash]
volumeMounts:
- name: work
mountPath: /work
env: # parameters as environment variables, see step 1
- name: SPEC_KEY
value: "{{workflow.parameters.specKey}}"
- name: SRC_REMOTE_URL
value: "{{workflow.parameters.srcRemoteURL}}"
source: |
set -euo pipefail
echo "Pulling ${SRC_REMOTE_URL}/${SPEC_KEY}"
curl -fsSL -o /work/blob "${SRC_REMOTE_URL}/${SPEC_KEY}"
- name: checksum
script:
image: amazon/aws-cli:2.37.9
command: [bash]
volumeMounts:
- name: work
mountPath: /work
readOnly: true # checks must not change what they check
env:
- name: SPEC_SHA256
value: "{{workflow.parameters.specSha256}}"
source: |
set -euo pipefail
echo "${SPEC_SHA256} /work/blob" | sha256sum --check
- name: virus-scan
script:
image: clamav/clamav-debian:1.5.4 # ships with a virus database
command: [bash]
volumeMounts:
- name: work
mountPath: /work
readOnly: true
source: |
set -euo pipefail
clamscan /work/blob
# More checks go here as further steps, e.g. creating an SBOM or verifying a signature.
- name: push
script:
image: amazon/aws-cli:2.37.9
command: [bash]
volumeMounts:
- name: work
mountPath: /work
- name: dst-secret-vol
mountPath: /secrets/dst
env:
- name: SPEC_OVERRIDE
value: "{{workflow.parameters.specOverride}}"
- name: DST_REMOTE_URL
value: "{{workflow.parameters.dstRemoteURL}}"
source: |
set -euo pipefail
export AWS_ACCESS_KEY_ID=$(cat /secrets/dst/AWS_ACCESS_KEY_ID)
export AWS_SECRET_ACCESS_KEY=$(cat /secrets/dst/AWS_SECRET_ACCESS_KEY)
echo "Pushing to ${DST_REMOTE_URL}/${SPEC_OVERRIDE}"
aws --endpoint-url "${DST_REMOTE_URL}" \
s3 cp --no-progress /work/blob "s3://${SPEC_OVERRIDE}"
echo "Done."
kubectl apply -f examples/walk-through/02-verify/1-workflow-template.yaml
A few things are new compared to the first transfer:
- Steps. Each step runs in a pod of its own, and a step only starts when the previous one succeeded. That is what
makes the checks a gate: a failing check stops the workflow before
push. Steps in the same list item run in parallel, see Steps. - A shared volume. Since the steps run in different pods, they pass the file through a volume. Argo creates the
PersistentVolumeClaimfromvolumeClaimTemplatesfor every workflow and deletes it afterwards, see Volumes. The checks mount it read-only. Artifacts are the alternative, but need an artifact repository. - A checksum from the
Order. The expected SHA-256 is part of the artifactspecand reaches the workflow asspecSha256. The parameter has no default, so from now on everybloborder without asha256fails. This includesfirst-transfer, if you force a reconciliation of it. - A virus scan. The ClamAV image ships with a virus database,
clamscanexits with an error if it finds something.
More checks fit in as further steps, e.g. creating an SBOM or verifying a signature. The Blob example also sizes the volume from the file instead of using a fixed size.
Order¶
The Order adds the checksum to the artifact spec. In practice, it comes from the publisher of the file, e.g. a
SHA256SUMS file next to a release, not from whoever writes the Order. Quote it: YAML reads a checksum that
consists only of digits as a number.
apiVersion: arc.opendefense.cloud/v1alpha1
kind: Order
metadata:
name: verified-transfer
labels:
walk-through: verify # copied to the ArtifactWorkflows and Workflows of this Order
spec:
defaults:
srcRef:
name: github-raw
dstRef:
name: seaweedfs
artifacts:
- type: blob
spec:
key: opendefensecloud/artifact-conduit/refs/tags/v0.3.0/examples/blob/file.txt
override: test/walk-through/verified.txt
# quoted, YAML would read a checksum of only digits as a number
sha256: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9" # -> specSha256
kubectl apply -f examples/walk-through/02-verify/2-order.yaml
The Order has a label. ARC copies the labels of an Order to its ArtifactWorkflows and Argo Workflows, which
makes it easy to find them among the workflows of the first transfer.
Watch it run¶
Wait for the workflow to finish and read its logs:
export WORKFLOW="$(kubectl get workflows -l walk-through=verify -o jsonpath='{.items[0].metadata.name}')"
kubectl wait workflow "${WORKFLOW}" --for jsonpath='{.status.phase}'=Succeeded --timeout 5m
kubectl logs -l workflows.argoproj.io/workflow="${WORKFLOW}" -c main --prefix --tail -1
The logs of all steps are printed, but not necessarily in the order the steps ran. As in the first transfer,
lines with argo=true come from the Argo executor.
argo watch @latest
argo logs @latest
The virus scan takes a moment, clamscan loads its database first. Check that the file arrived in the bucket:
kubectl run s3-check --rm -it --restart Never --image amazon/aws-cli:2.37.9 \
--env AWS_ACCESS_KEY_ID=admin --env AWS_SECRET_ACCESS_KEY=adminadmin -- \
--endpoint-url http://dst.seaweedfs:8333 s3 cp s3://test/walk-through/verified.txt -
Watch it fail¶
Now order the same file with a checksum that does not match, as if the file had been tampered with:
kubectl apply -f - <<EOF
apiVersion: arc.opendefense.cloud/v1alpha1
kind: Order
metadata:
name: tampered-transfer
labels:
walk-through: tampered
spec:
defaults:
srcRef:
name: github-raw
dstRef:
name: seaweedfs
artifacts:
- type: blob
spec:
key: opendefensecloud/artifact-conduit/refs/tags/v0.3.0/examples/blob/file.txt
override: test/walk-through/tampered.txt
sha256: "0000000000000000000000000000000000000000000000000000000000000000"
EOF
The checksum step fails, and so does the workflow:
export WORKFLOW="$(kubectl get workflows -l walk-through=tampered -o jsonpath='{.items[0].metadata.name}')"
kubectl wait workflow "${WORKFLOW}" --for jsonpath='{.status.phase}'=Failed --timeout 5m
kubectl logs -l workflows.argoproj.io/workflow="${WORKFLOW}" -c main --prefix --tail -1
argo watch @latest
argo logs @latest
The Order reports the failure as well, including the logs of the failed step:
kubectl get orders.arc.opendefense.cloud tampered-transfer -o jsonpath='{.status.artifactWorkflows}'
push never ran, so there is no tampered.txt in the bucket. This command fails with a 404:
kubectl run s3-check --rm -it --restart Never --image amazon/aws-cli:2.37.9 \
--env AWS_ACCESS_KEY_ID=admin --env AWS_SECRET_ACCESS_KEY=adminadmin -- \
--endpoint-url http://dst.seaweedfs:8333 s3 cp s3://test/walk-through/tampered.txt -
ARC keeps failed workflows for troubleshooting and does not retry them on its own. Deleting the Order also deletes
its workflows:
kubectl delete orders.arc.opendefense.cloud tampered-transfer