Skip to content

Migrated kCTF from Google Container Registry to Artifact Registry #823

Migrated kCTF from Google Container Registry to Artifact Registry

Migrated kCTF from Google Container Registry to Artifact Registry #823

name: Update docker images
on:
push:
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
env:
GKE_PROJECT: ${{ secrets.GKE_PROJECT }}
GKE_ZONE: us-east1-c
GKE_CLUSTER: github-ci
GKE_REGISTRY: us.gcr.io
jobs:
build-docker:
runs-on: ubuntu-latest
if: github.event_name == 'push'
outputs:
challenge-modified: ${{ steps.set-modified.outputs.challenge-modified }}
healthcheck-modified: ${{ steps.set-modified.outputs.healthcheck-modified }}
gcsfuse-modified: ${{ steps.set-modified.outputs.gcsfuse-modified }}
certbot-modified: ${{ steps.set-modified.outputs.certbot-modified }}
challenge-digest: ${{ steps.push.outputs.challenge-digest }}
healthcheck-digest: ${{ steps.push.outputs.healthcheck-digest }}
gcsfuse-digest: ${{ steps.push.outputs.gcsfuse-digest }}
certbot-digest: ${{ steps.push.outputs.certbot-digest }}
strategy:
matrix:
image: ["challenge", "healthcheck", "gcsfuse", "certbot"]
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- id: modified
name: Check for modified paths
run: |
PATHS=(".github/workflows/update-images.yaml" "docker-images/${{ matrix.image }}/**")
BASE_SHA="$(git log -n1 --grep='Automated commit: update images.' --format=%H || echo '')"
echo "BASE_SHA=${BASE_SHA}"
if [ -z "${BASE_SHA}" ]; then
# we couldn't find any existing robot commit, just rebuild everything
echo "::set-output name=modified::true"
exit 0
fi
CHANGED_FILES="$(git diff --name-only ${BASE_SHA} ${{ github.sha }})"
echo "CHANGED_FILES=${CHANGED_FILES}"
while IFS= read -r changed_file; do
for watched_path in "${PATHS[@]}"; do
if [[ $changed_file == $watched_path ]]; then
echo "modified=true: ${changed_file} matches ${watched_path}"
echo "::set-output name=modified::true"
exit 0
fi
done
done <<< "${CHANGED_FILES}"
- id: set-modified
name: Set modified
run: |
echo "::set-output name=${{ matrix.image }}-modified::${{ steps.modified.outputs.modified }}"
- name: Build image
if: steps.modified.outputs.modified
run: |
cd "docker-images/${{ matrix.image }}"
docker build . --tag "${{ matrix.image }}"
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.7'
- name: 'Set up Cloud SDK auth'
uses: 'google-github-actions/auth@v0'
with:
# not using workload identity because we use gsutil
# TODO(evn) switch when supported
# https://github.com/google-github-actions/setup-gcloud#authorization
credentials_json: '${{ secrets.GKE_KEY }}'
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v0'
with:
version: '319.0.0'
service_account_email: ${{ secrets.GKE_EMAIL }}
- name: Configure docker to use the gcloud command-line tool as a credential helper
if: steps.modified.outputs.modified
run: |
gcloud auth configure-docker
- id: push
name: Push images
if: steps.modified.outputs.modified
run: |
IMAGE_GCR="gcr.io/${{ secrets.GCR_PROJECT }}/${{ matrix.image }}"
docker tag "${{ matrix.image }}" "$IMAGE_GCR"
DIGEST="$(docker push "$IMAGE_GCR" | grep 'digest: ' | sed 's/.*\(sha256:[^ ]*\).*/\1/')"
echo "::set-output name=${{ matrix.image }}-digest::${DIGEST}"
build-operator:
runs-on: ubuntu-latest
needs:
- build-docker
if: github.event_name == 'push'
outputs:
kctf-operator-modified: ${{ steps.set-modified.outputs.kctf-operator-modified }}
kctf-operator-digest: ${{ steps.push.outputs.kctf-operator-digest }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
- id: modified
name: Check for modified paths
run: |
PATHS=(".github/workflows/update-images.yaml" "kctf-operator/**")
BASE_SHA="$(git log -n1 --grep='Automated commit: update images.' --format=%H || echo '')"
echo "BASE_SHA=${BASE_SHA}"
if [ -z "${BASE_SHA}" ]; then
# we couldn't find any existing robot commit, just rebuild everything
echo "::set-output name=modified::true"
exit 0
fi
CHANGED_FILES="$(git diff --name-only ${BASE_SHA} ${{ github.sha }})"
echo "CHANGED_FILES=${CHANGED_FILES}"
while IFS= read -r changed_file; do
for watched_path in "${PATHS[@]}"; do
if [[ $changed_file == $watched_path ]]; then
echo "modified=true: ${changed_file} matches ${watched_path}"
echo "::set-output name=modified::true"
exit 0
fi
done
done <<< "${CHANGED_FILES}"
- id: set-modified
name: Set modified
run: |
echo "::set-output name=kctf-operator-modified::${{ steps.modified.outputs.modified }}"
- name: 'Set up Cloud SDK auth'
uses: 'google-github-actions/auth@v0'
with:
# not using workload identity because we use gsutil
# TODO(evn) switch when supported
# https://github.com/google-github-actions/setup-gcloud#authorization
credentials_json: '${{ secrets.GKE_KEY }}'
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.7'
- name: Export gcloud related env variable
run: export CLOUDSDK_PYTHON="/usr/bin/python3"
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v0'
with:
version: '319.0.0'
service_account_email: ${{ secrets.GKE_EMAIL }}
- name: Configure docker to use the gcloud command-line tool as a credential helper
if: steps.modified.outputs.modified
run: |
gcloud auth configure-docker
- name: 'Setup go version necessary for operator'
uses: actions/setup-go@v2
with:
go-version: '1.16.0'
- name: Build image
if: steps.modified.outputs.modified
run: |
cd kctf-operator
curl -L https://github.com/operator-framework/operator-sdk/releases/download/v1.15.0/operator-sdk_linux_amd64 -o operator-sdk
chmod u+x operator-sdk
sudo mv operator-sdk /usr/local/bin/
make controller-gen
make manifests docker-build IMG=kctf-operator
- id: push
name: Push images
if: steps.modified.outputs.modified
run: |
IMAGE_GCR="gcr.io/${{ secrets.GCR_PROJECT }}/kctf-operator"
docker tag "kctf-operator" "$IMAGE_GCR"
DIGEST="$(docker push "$IMAGE_GCR" | grep 'digest: ' | sed 's/.*\(sha256:[^ ]*\).*/\1/')"
echo "::set-output name=kctf-operator-digest::${DIGEST}"
update-image-and-commit:
runs-on: ubuntu-latest
needs:
- build-docker
- build-operator
steps:
- uses: actions/checkout@v2
- name: Update challenge image
if: needs.build-docker.outputs.challenge-modified
run: |
IMAGE="gcr.io/${{ secrets.GCR_PROJECT }}/challenge@${{ needs.build-docker.outputs.challenge-digest }}"
for dir in dist/challenge-templates/*; do
if [[ -e "${dir}/challenge/Dockerfile" ]]; then
sed -i "s#FROM gcr.io/${{ secrets.GCR_PROJECT }}/challenge.*#FROM ${IMAGE}#" "${dir}/challenge/Dockerfile"
fi
done
- name: Update healthcheck image
if: needs.build-docker.outputs.healthcheck-modified
run: |
IMAGE="gcr.io/${{ secrets.GCR_PROJECT }}/healthcheck@${{ needs.build-docker.outputs.healthcheck-digest }}"
for dir in dist/challenge-templates/*; do
if [[ -e "${dir}/healthcheck/Dockerfile" ]]; then
sed -i "s#FROM gcr.io/${{ secrets.GCR_PROJECT }}/healthcheck.*#FROM ${IMAGE}#" "${dir}/healthcheck/Dockerfile"
fi
done
- name: Update gcsfuse image
if: needs.build-docker.outputs.gcsfuse-modified
run: |
IMAGE="gcr.io/${{ secrets.GCR_PROJECT }}/gcsfuse@${{ needs.build-docker.outputs.gcsfuse-digest }}"
sed -i 's%const DOCKER_GCSFUSE_IMAGE = .*%const DOCKER_GCSFUSE_IMAGE = "'${IMAGE}'"%' kctf-operator/resources/constants.go
- name: Update certbot image
if: needs.build-docker.outputs.certbot-modified
run: |
IMAGE="gcr.io/${{ secrets.GCR_PROJECT }}/certbot@${{ needs.build-docker.outputs.certbot-digest }}"
sed -i 's%const DOCKER_CERTBOT_IMAGE = .*%const DOCKER_CERTBOT_IMAGE = "'${IMAGE}'"%' kctf-operator/resources/constants.go
- name: Update operator
if: needs.build-operator.outputs.kctf-operator-modified
run: |
IMAGE="gcr.io/${{ secrets.GCR_PROJECT }}/kctf-operator@${{ needs.build-operator.outputs.kctf-operator-digest }}"
sed -i "s#image: .*kctf-operator.*#image: ${IMAGE}#" dist/resources/operator.yaml
- name: Download kubectl
run: |
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/kubectl
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.7'
- name: Export gcloud related env variable
run: export CLOUDSDK_PYTHON="/usr/bin/python3"
- name: 'Set up Cloud SDK auth'
uses: 'google-github-actions/auth@v0'
with:
# not using workload identity because we use gsutil
# TODO(evn) switch when supported
# https://github.com/google-github-actions/setup-gcloud#authorization
credentials_json: '${{ secrets.GKE_KEY }}'
- name: 'Set up Cloud SDK'
uses: 'google-github-actions/setup-gcloud@v0'
with:
service_account_email: ${{ secrets.GKE_EMAIL }}
install_components: 'gke-gcloud-auth-plugin'
- name: Configure docker to use the gcloud command-line tool as a credential helper
run: |
gcloud auth configure-docker
- name: Configure kCTF cluster
run: |
mkdir /tmp/samples
cp -R dist /tmp/samples/kctf
source /tmp/samples/kctf/activate
kctf cluster create --project $GKE_PROJECT --zone $GKE_ZONE --registry $GKE_REGISTRY --cluster-name $GKE_CLUSTER --domain-name kctf-ci.kctf.cloud test
# Try to delete any existing CRD in case this branch is older than what's running on the cluster
# Though, this is just a hotfix. We don't support downgrading and we should rather test using
# KIND in this workflow and then do a full cluster delete/create during merge.
kubectl delete crd/challenges.kctf.dev || true
kctf cluster start
- name: Deploy all tasks
run: |
source /tmp/samples/kctf/activate
cd /tmp/samples
for challenge_name in $(kubectl get challenges -o "jsonpath={.items[*].metadata.name}"); do
echo "deleting challenge ${challenge_name}"
kubectl delete "challenge/${challenge_name}"
done
for template in $(ls kctf/challenge-templates/); do
kctf chal create --template $template $template
echo "entering challenge directory ${template}"
pushd $template
if [[ -e "challenge/Makefile" ]]; then
make -C "challenge"
fi
sed -i "s/public: false/public: true/" challenge.yaml
CHALLENGE_NAME="$("${KCTF_BIN}/yq" eval '.metadata.name' challenge.yaml)"
echo "starting challenge ${CHALLENGE_NAME}"
kctf chal start
echo "challenge started, waiting for it to become available"
# We want to wait for the deployment to be available, but it
# might not have been created yet by the operator and wait will fail.
# So try to "kubectl get" the challenge a few times to make sure it exists.
# Ideally, we would expose the condition in the operator but I
# don't think that's currently possible.
for i in {1..5}; do
kubectl get "deployment/${CHALLENGE_NAME}" && break
echo "deployment/${CHALLENGE_NAME} doesn't exist yet, sleeping"
sleep 5
done
kubectl wait --for=condition=available --timeout=5m "deployment/${CHALLENGE_NAME}"
echo "challenge availble, stopping"
kctf chal stop
popd
done
- name: Commit
env:
SUBMITTER: ${{ github.event.head_commit.author.email }}
run: |
# git add returns success for files that exist and haven't been modified
git add kctf-operator/resources/constants.go
git add dist/resources/operator.yaml
git add kctf-operator/config/crd/bases/kctf.dev_challenges.yaml
git add dist/resources/kctf.dev_challenges.yaml
for dir in dist/challenge-templates/* samples/*; do
if [[ ! -e "${dir}/challenge.yaml" ]]; then
continue
fi
git add "${dir}/challenge/Dockerfile"
# not all challenges might have healthchecks, ignore errors
git add "${dir}/healthcheck/Dockerfile" 2>&1 || true
done
git status
git config user.email "$SUBMITTER"
git config user.name "GitHub Action"
if git commit -m "Automated commit: update images."; then
git push
fi