Skip to content

Commit

Permalink
feat(CLDX-78): add windows binary signing step (konflux-ci#541)
Browse files Browse the repository at this point in the history
This step is part of multiple commits for the sign-binaries task.
Specifically, this is the Windows signing step. The rest of the task
and steps will be implemented with CLDX Jiras as marked in the code
comments.

Currently, no pipelines call this task yet, so the TODO code will not
cause any failures as this time.

Signed-off-by: Scott Wickersham <[email protected]>
  • Loading branch information
swickersh authored Sep 12, 2024
1 parent a6323b9 commit 9a9b19d
Show file tree
Hide file tree
Showing 5 changed files with 363 additions and 0 deletions.
13 changes: 13 additions & 0 deletions tasks/sign-binaries/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# sign-binaries

Tekton task to sign windows and mac binaries before they are pushed to the Red Hat Developer Portal

## Parameters

| Name | Description | Optional | Default value |
|------|-------------|----------|---------------|
| quayUrl | Quay URL of the repo where content will be shared | No | |
| quaySecret | Secret to interact with Quay | No | |
| windowsCredentials | Secret to interact with the Windows signing host | No | |
| windowsSSHKey | Secret containing private key and fingerprint for Windows signing host | Yes | windows-ssh-key |
| pipelineRunUid | Unique ID of the pipelineRun | No | |
172 changes: 172 additions & 0 deletions tasks/sign-binaries/sign-binaries.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
---
apiVersion: tekton.dev/v1
kind: Task
metadata:
name: sign-binaries
labels:
app.kubernetes.io/version: "0.1.0"
annotations:
tekton.dev/pipelines.minVersion: "0.12.1"
tekton.dev/tags: release
spec:
description: >-
Tekton task to sign windows and mac binaries before they are pushed to the Red Hat Developer Portal
params:
- name: quayURL
type: string
description: quay URL of the repo where content will be shared
- name: quaySecret
type: string
description: Secret to interact with Quay
- name: windowsCredentials
type: string
description: Secret to interact with the Windows signing host
- name: windowsSSHKey
type: string
description: Secret containing SSH private key for the Windows signing host
default: windows-ssh-key
- name: pipelineRunUid
type: string
description: Unique identifier for the pipeline run
volumes:
- name: windows-ssh-key-vol
secret:
secretName: $(params.windowsSSHKey)
workspaces:
- name: data
description: Workspace to save the results to
results:
- name: unsignedWindowsDigest
type: string
description: |
Digest used by signing host to pull unsignged content via ORAS
- name: signedWindowsDigest
type: string
description: |
Digest used to pull signed content back to pipeline via ORAS
steps:
- name: push-unsigned-using-oras
image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
script: |
#!/usr/bin/env bash
# TODO CLDX-134
output=$(oras push "$(params.quayURL)/unsigned" .)
windows_digest=$(echo "$output" | grep 'Digest:' | awk '{print $2}')
echo "Digest for windows content: $windows_digest"
echo -n "$windows_digest" > "$(results.unsignedWindowsDigest.path)"
# - name: sign-mac-binaries
# image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
# script: |
# #!/usr/bin/env bash
# # TODO CLDX-79
- name: sign-windows-binaries
image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
volumeMounts:
- name: windows-ssh-key-vol
mountPath: "/etc/secrets"
readOnly: true
env:
- name: WINDOWS_USER
valueFrom:
secretKeyRef:
name: $(params.windowsCredentials)
key: username
- name: WINDOWS_PORT
valueFrom:
secretKeyRef:
name: $(params.windowsCredentials)
key: port
- name: WINDOWS_HOST
valueFrom:
secretKeyRef:
name: $(params.windowsCredentials)
key: host
- name: QUAY_USER
valueFrom:
secretKeyRef:
name: $(params.quaySecret)
key: username
- name: QUAY_PASS
valueFrom:
secretKeyRef:
name: $(params.quaySecret)
key: password
script: |
#!/usr/bin/env bash
set -eux
mkdir -p /root/.ssh
chmod 700 /root/.ssh
cp "/etc/secrets/id_rsa" /root/.ssh/id_rsa
cp "/etc/secrets/fingerprint" /root/.ssh/known_hosts
chmod 600 root/.ssh/known_hosts /root/.ssh/id_rsa
SSH_OPTS="-i /root/.ssh/id_rsa -o UserKnownHostsFile=/root/.ssh/known_hosts -p ${WINDOWS_PORT} \
${WINDOWS_USER}@${WINDOWS_HOST}"
SCP_OPTS="-i /root/.ssh/id_rsa -o UserKnownHostsFile=/root/.ssh/known_hosts -P ${WINDOWS_PORT}"
unsigned_digest=$(cat "$(results.unsignedWindowsDigest.path)")
# Create the batch script
signing_script_file="/tmp/signing_script.bat"
set +x
cat << EOF > "$signing_script_file"
mkdir %TEMP%\$(params.pipelineRunUid) && cd /d %TEMP%\$(params.pipelineRunUid)
@echo off
oras login quay.io -u ${QUAY_USER} -p ${QUAY_PASS}
@echo on
oras pull $(params.quayURL)/unsigned@${unsigned_digest}
signtool sign /v /n "Red Hat" /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 ^
%TEMP%\$(params.pipelineRunUid)\*
if errorlevel 1 (
echo Signing of binaries failed
exit /B %ERRORLEVEL%
)
signtool verify /v /pa %TEMP%\$(params.pipelineRunUid)\*
if errorlevel 1 (
echo Verification of binaries failed
exit /B %ERRORLEVEL%
)
echo [%DATE% %TIME%] Signing of Windows binaries completed successfully
oras push $(params.quayURL)/signed:$(params.pipelineRunUid) %TEMP%\$(params.pipelineRunUid) \
> oras_push_output.txt 2>&1
for /f "tokens=2,3 delims=: " %%a in ('findstr "Digest:" oras_push_output.txt') do @echo %%a:%%b > digest.txt
EOF
set -x
scp "$SCP_OPTS" "$signing_script_file" \
"${WINDOWS_USER}@${WINDOWS_HOST}:C:/Users/Administrator/AppData/Local/Temp/signing_script.bat"
# Execute the script on the Windows host
ssh "$SSH_OPTS" "C:/Users/Administrator/AppData/Local/Temp/signing_script.bat"
# disable shellcheck for escaping the pipelineRunUid as we want that evaluated on client side
# shellcheck disable=SC2029
scp "$SCP_OPTS" "${WINDOWS_USER}@${WINDOWS_HOST}:\
C:\\Users\\Administrator\\AppData\\Local\\Temp\\$(params.pipelineRunUid)\\digest.txt" \
"$(results.signedWindowsDigest.path)"
# Clean up the windows host now that we are done
# shellcheck disable=SC2029
ssh "$SSH_OPTS" "rmdir /s /q C:\\Users\\Administrator\\AppData\\Local\\Temp\\$(params.pipelineRunUid)"
# - name: pull-signed-using-oras
# image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
# script: |
# #!/usr/bin/env bash
# # TODO CLDX-79
# - name: generate-checksums
# image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
# script: |
# #!/usr/bin/env bash
# # TODO CLDX-82
48 changes: 48 additions & 0 deletions tasks/sign-binaries/tests/mocks.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/bin/bash


count_file="/tmp/ssh_count.txt"
if [[ ! -f "$count_file" ]]; then
echo "0" > "$count_file"
fi


function ssh() {
# Read the current ssh_call_count from the file
ssh_call_count=$(cat "$count_file")
ssh_call_count=$((ssh_call_count + 1))
echo "$ssh_call_count" > "$count_file"

echo "$ssh_call_count" > $(workspaces.data.path)/ssh_calls.txt
if [[ "$*" == *"digest.txt"* ]]; then
# this is mocking oras via Windows ssh returning the signed digest
echo "mocked ssh $@" > $(workspaces.data.path)/mock_ssh_second_call.txt
echo "sha256:0c4355ee4ef8d9d3875d5421972aed405ce6d8f5262983eeea3f6cbf5740c6e2"
fi
}

scp_count_file="/tmp/scp_count.txt"
if [[ ! -f "$scp_count_file" ]]; then
echo "0" > "$scp_count_file"
fi
function scp() {
scp_call_count=$(cat "$scp_count_file")
scp_call_count=$((scp_call_count + 1))
echo "$scp_call_count" > "$scp_count_file"
if [[ "$scp_call_count" -eq 1 ]]; then
echo "$@" > $(workspaces.data.path)/mock_scp_1.txt
fi


if [[ "$scp_call_count" -eq 2 ]]; then
echo "$@" > "$(workspaces.data.path)/mock_scp_2.txt"
echo -n "sha256:0c4355ee4ef8d9d3875d5421972aed405ce6d8f5262983eeea3f6cbf5740c6e2" | \
tee "$(results.signedWindowsDigest.path)"
fi

}

function oras() {
# this is mocking the oras command to push unsigned binaries to the registry
echo "Digest: sha256:5ce6d8f5262983eeea3f6cbf5740c6e20c4355ee4ef8d9d3875d5421972aed40"
}
31 changes: 31 additions & 0 deletions tasks/sign-binaries/tests/pre-apply-task-hook.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env bash

# Add mocks to the beginning of task step script
TASK_PATH="$1"
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
yq -i '.spec.steps[0].script = load_str("'$SCRIPT_DIR'/mocks.sh") + .spec.steps[0].script' "$TASK_PATH"
yq -i '.spec.steps[1].script = load_str("'$SCRIPT_DIR'/mocks.sh") + .spec.steps[1].script' "$TASK_PATH"

# Delete existing secrets if they exist
kubectl delete secret windows-credentials --ignore-not-found
kubectl delete secret windows-ssh-key --ignore-not-found
kubectl delete secret quay-secret --ignore-not-found
# Create the windows-credentials secret
kubectl create secret generic windows-credentials \
--from-literal=username=myusername \
--from-literal=password=mypass \
--from-literal=port=22 \
--from-literal=host=myserver.com \
--namespace=default

# Create the windows-ssh-key secret
kubectl create secret generic windows-ssh-key \
--from-literal=id_rsa="some private key" \
--from-literal=fingerprint="some fingerprint" \
--namespace=default

# Create the quay-secret secret
kubectl create secret generic quay-secret \
--from-literal=username=myusername \
--from-literal=password=mypass \
--namespace=default
99 changes: 99 additions & 0 deletions tasks/sign-binaries/tests/test-sign-binaries.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
apiVersion: tekton.dev/v1
kind: Pipeline
metadata:
name: test-sign-binaries
spec:
description: |
Run the sign-binaries task and verify the results
workspaces:
- name: tests-workspace
tasks:
- name: run-task
taskRef:
name: sign-binaries
workspaces:
- name: data
workspace: tests-workspace
params:
- name: windowsCredentials
value: windows-credentials
- name: ssh_key_secret
value: windows-ssh-key
- name: quaySecret
value: quay-secret
- name: quayURL
value: quay.io/konflux-artifacts
- name: pipelineRunUid
value: 12345678
- name: check-result
workspaces:
- name: data
workspace: tests-workspace
params:
- name: signedWindowsDigest
value: $(tasks.run-task.results.signedWindowsDigest)
taskSpec:
workspaces:
- name: data
params:
- name: signedWindowsDigest
description: The digest of the signed content pushed using ORAS for signing hosts
type: string
steps:
- name: check-result
image: quay.io/konflux-ci/release-service-utils:e633d51cd41d73e4b3310face21bb980af7a662f
script: |
#!/usr/bin/env bash
expected_calls="2"
actual_content=$(cat "$(workspaces.data.path)/ssh_calls.txt")
if [[ "$actual_content" == "$expected_calls" ]]; then
echo "Test passed: SSH called two times as expected."
else
echo "Test failed: SSH not called expected number of times."
echo "Expected: '$expected_calls'"
echo "Actual: '$actual_content'"
exit 1
fi
expected_signed_digest="sha256:0c4355ee4ef8d9d3875d5421972aed405ce6d8f5262983eeea3f6cbf5740c6e2"
if [ "$(params.signedWindowsDigest)" != "$expected_signed_digest" ]; then
echo Error: signedWindowsDigest was expected to be $expected_signed_digest.
echo "Actual: $(params.signedWindowsDigest)"
exit 1
else
echo signedWindowsDigest matches expected value
fi
mock_scp_1=$(cat "$(workspaces.data.path)/mock_scp_1.txt")
expected_scp="-i /root/.ssh/id_rsa -o UserKnownHostsFile=/root/.ssh/known_hosts -P 22 \
/tmp/signing_script.bat \
[email protected]:C:/Users/Administrator/AppData/Local/Temp/signing_script.bat"
if [[ "$mock_scp_1" == "$expected_scp" ]]; then
echo "Test passed: First SCP command is correct."
else
echo "Test failed: First SCP command is incorrect."
echo "Expected: '$expected_scp'"
echo "Actual: '$mock_scp_1'"
exit 1
fi
# check second scp command
mock_scp_2=$(cat "$(workspaces.data.path)/mock_scp_2.txt")
expected_scp_2="-i /root/.ssh/id_rsa -o UserKnownHostsFile=/root/.ssh/known_hosts -P 22 \
[email protected]:C:\Users\Administrator\AppData\Local\Temp\12345678\digest.txt \
/tekton/results/signedWindowsDigest"
if [[ "$mock_scp_2" == "$expected_scp_2" ]]; then
echo "Test passed: Second SCP command is correct."
else
echo "Test failed: Second SCP command is incorrect."
echo "Expected: '$expected_scp_2'"
echo "Actual: '$mock_scp_2'"
exit 1
fi
runAfter:
- run-task

0 comments on commit 9a9b19d

Please sign in to comment.