From 48720849aedbb9981e6df44da072f0bc885e8aee Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Thu, 6 Jun 2024 23:29:44 +0200 Subject: [PATCH 1/3] Adding more integration tests and refactoring Signed-off-by: Daniel Liszka --- tools/Makefile | 23 +++--- tools/c8l | 154 ++++++++++++++++++++++++++++++++----- tools/src/bashly.yml | 10 ++- tools/src/cmd_command.sh | 1 + tools/src/lib/base.sh | 6 +- tools/src/lib/cache.sh | 1 + tools/src/lib/chainloop.sh | 44 +++++++---- tools/test/run.sh | 2 + tools/test/test.bats | 44 ++++++++++- 9 files changed, 228 insertions(+), 57 deletions(-) create mode 100644 tools/src/cmd_command.sh create mode 100755 tools/test/run.sh diff --git a/tools/Makefile b/tools/Makefile index 1afd6e8..6fc9edd 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -1,4 +1,6 @@ VERSION=$(shell git describe --tags --always) +# CHAINLOOP_TOKEN is required for testing and should be places in .env file +DOCKER_TEST_CMD=docker run --rm -it -w /code -v "${PWD}:/code" --env-file .env $(DOCKER_IMG) ./test/run.sh .PHONY: build # build @@ -10,19 +12,14 @@ build: test: ./test/bats/bin/bats --print-output-on-failure test -docker_test_on_ubuntu: - docker run --rm -it -w /code -v "${PWD}:/code" node ./test/bats/bin/bats --print-output-on-failure test - -docker_test_on_alpine: - docker run --rm -it -w /code -v "${PWD}:/code" chainguard/bash "./test/bats/bin/bats --print-output-on-failure test" - -docker_test_on_ubi: - docker run --rm -it -w /code -v "${PWD}:/code" redhat/ubi9 ./test/bats/bin/bats --print-output-on-failure test - -docker_test: docker_test_on_ubuntu - -docker_test_all: docker_test_on_ubuntu docker_test_on_alpine docker_test_on_ubi - +docker_test_on_ubuntu: DOCKER_IMG=node +docker_test_on_alpine: DOCKER_IMG=chainguard/bash +docker_test_on_ubi: DOCKER_IMG=redhat/ubi9 +docker_test_on_ubuntu docker_test_on_alpine docker_test_on_ubi: + @echo "\n\n### $@: Testing on $(DOCKER_IMG)" + $(DOCKER_TEST_CMD) +docker_test: build docker_test_on_ubuntu +docker_test_all: build docker_test_on_ubuntu docker_test_on_alpine docker_test_on_ubi docker_test_shell: docker run --rm -it -w /code -v "${PWD}:/code" node bash diff --git a/tools/c8l b/tools/c8l index 953a48f..2e8abfd 100755 --- a/tools/c8l +++ b/tools/c8l @@ -13,11 +13,11 @@ version_command() { c8l_usage() { if [[ -n $long_usage ]]; then - printf "c8l - [EXPERIMENTAL] Chainloop (c8) Labs CLI\n" + printf "c8l - [EXPERIMENTAL] (c8l) Chainloop Labs CLI\n" echo else - printf "c8l - [EXPERIMENTAL] Chainloop (c8) Labs CLI\n" + printf "c8l - [EXPERIMENTAL] (c8l) Chainloop Labs CLI\n" echo fi @@ -32,6 +32,7 @@ c8l_usage() { printf " %s Show help about a command\n" "help " printf " %s [i] Inspect.\n" "inspect" printf " %s Show the content of c8l script ready for sourcing.\n" "source " + printf " %s Run a command in the c8l environment.\n" "cmd " printf " %s Chainloop CLI UX improved\n" "cli " echo @@ -132,6 +133,41 @@ c8l_source_usage() { fi } +c8l_cmd_usage() { + if [[ -n $long_usage ]]; then + printf "c8l cmd - Run a command in the c8l environment.\n" + echo + + else + printf "c8l cmd - Run a command in the c8l environment.\n" + echo + + fi + + printf "Alias: r\n" + echo + + printf "%s\n" "Usage:" + printf " c8l cmd COMMAND\n" + printf " c8l cmd --help | -h\n" + echo + + if [[ -n $long_usage ]]; then + printf "%s\n" "Options:" + + printf " %s\n" "--help, -h" + printf " Show this help\n" + echo + + printf "%s\n" "Arguments:" + + printf " %s\n" "COMMAND" + printf " Command to run in the c8l environment.\n" + echo + + fi +} + c8l_cli_usage() { if [[ -n $long_usage ]]; then printf "c8l cli - Chainloop CLI UX improved\n" @@ -560,7 +596,7 @@ normalize_input() { done } -export CHAINLOOP_BIN_PATH="${CHAINLOOP_BIN_PATH:-/usr/local/bin/chainloop}" +export CHAINLOOP_BIN_PATH="${CHAINLOOP_BIN_PATH:-/usr/local/bin/chainloop_bin}" is_chainloop_in_path() { if command -v chainloop &>/dev/null; then @@ -605,11 +641,11 @@ prepare_tmp_file() { file_name=$1 mkdir -p "${tmp_dir}" t="${tmp_dir}/${file_name}" - if [ -f $t ]; then + if [ -f "$t" ]; then echo "Temporary file file $t already exists" return 1 fi - echo $t + echo "$t" } # chainloop_bin_cache_in_dir - it takes a path and copy there the CHAINLOOP_BIN_PATH @@ -632,6 +668,7 @@ chainloop_recreate_env_from_file() { file=$(basename $path) if [[ $file =~ ^\.env_.*$ ]]; then export $(echo $file | sed 's/\.env_//')=$(cat $path) + echo export $(echo $file | sed 's/\.env_//')=$(cat $path) else log_error "File $file is not in the format .env_NAME" return 1 @@ -761,6 +798,7 @@ generic_install() { mkdir -p $CHAINLOOP_BIN_PATH log "Installing $file" curl -sfL $url -o $file_path + if [ $? -ne 0 ]; then log_error "$file installation failed" return 1 @@ -820,25 +858,32 @@ chainloop_attestation_push() { export COSIGN_PASSWORD="$CHAINLOOP_SIGNING_PASSWORD" cosign generate-key-pair fi - if [ -z "${CHAINLOOP_SIGNING_KEY_PATH+x}" ]; then + if [ -n "${CHAINLOOP_SIGNING_KEY}" ]; then log " with CHAINLOOP_SIGNING_KEY" tmp_key="${CHAINLOOP_TMP_DIR}/key" mkdir -p "${CHAINLOOP_TMP_DIR}" - echo "${CHAINLOOP_SIGNING_KEY}" >$tmp_key - else + echo "${CHAINLOOP_SIGNING_KEY}" > "$tmp_key" + fi + if [ -n "${CHAINLOOP_SIGNING_KEY_PATH}" ]; then log " with CHAINLOOP_SIGNING_KEY_PATH" tmp_key="${CHAINLOOP_SIGNING_KEY_PATH}" fi + + tmp_key_value="" + if [ -n "$tmp_key" ]; then + tmp_key_value="--key $tmp_key" + fi + # chainloop attestation push --key env://CHAINLOOP_SIGNING_KEY - if chainloop attestation push --key $tmp_key --remote-state --attestation-id "${CHAINLOOP_ATTESTATION_ID}" &>c8-push.txt; then + if chainloop attestation push "$tmp_key_value" --output json --remote-state --attestation-id "${CHAINLOOP_ATTESTATION_ID}" > c8-push.txt; then log "Attestation Process Completed Successfully" cat c8-push.txt - rm $tmp_key + rm -f "$tmp_key" else exit_code=$? log_error "Attestation Process Failed" cat c8-push.txt - rm $tmp_key + rm -f "$tmp_key" return $exit_code fi } @@ -849,20 +894,24 @@ chainloop_summary() { log $tmpfile return 1 fi - echo -e "## Great job!\n\nYou are making SecOps and Compliance teams really happy. Keep up the good work!\n" >>$tmpfile + echo -e "## Great job!\n\nYou are making SecOps and Compliance teams really happy. Keep up the good work!\n" >> $tmpfile digest="" if [ -f c8-push.txt ]; then - digest=$(cat c8-push.txt | grep " Digest: " | awk -F'sha256:' '{print $2}') - echo "**[Chainloop Trust Report]( https://app.chainloop.dev/attestation/sha256:${digest} )**" >>$tmpfile - echo "\`\`\`" >>$tmpfile - fi - if [ -f c8-status.txt ]; then - cat c8-status.txt >>$tmpfile - echo "\`\`\`" >>$tmpfile + digest=$(cat c8-push.txt | jq -r '.digest') + if [ $? -ne 0 ]; then + log_error "Failed to get digest from c8-push.txt" + return 1 + fi + echo "**[Chainloop Trust Report]( https://app.chainloop.dev/attestation/${digest} )**" >> "$tmpfile" + echo "\`\`\`" >> "$tmpfile" + if [ -f c8-status.txt ] ; then + cat c8-status.txt >> "$tmpfile" + fi + echo "\`\`\`" >> "$tmpfile" fi - cat $tmpfile - rm $tmpfile + cat "$tmpfile" + rm "$tmpfile" } chainloop_summary_on_failure() { @@ -1076,6 +1125,11 @@ c8l_source_command() { } +c8l_cmd_command() { + eval "${args['command']}" + +} + c8l_cli_install_tools_command() { validate_env install_chainloop_tools @@ -1272,6 +1326,13 @@ parse_requirements() { shift $# ;; + cmd | r) + action="cmd" + shift + c8l_cmd_parse_requirements "$@" + shift $# + ;; + cli | c) action="cli" shift @@ -1432,6 +1493,56 @@ c8l_source_parse_requirements() { } +c8l_cmd_parse_requirements() { + + while [[ $# -gt 0 ]]; do + case "${1:-}" in + --help | -h) + long_usage=yes + c8l_cmd_usage + exit + ;; + + *) + break + ;; + + esac + done + + action="cmd" + + while [[ $# -gt 0 ]]; do + key="$1" + case "$key" in + + -?*) + printf "invalid option: %s\n" "$key" >&2 + exit 1 + ;; + + *) + + if [[ -z ${args['command']+x} ]]; then + args['command']=$1 + shift + else + printf "invalid argument: %s\n" "$key" >&2 + exit 1 + fi + + ;; + + esac + done + + if [[ -z ${args['command']+x} ]]; then + printf "missing required argument: COMMAND\nusage: c8l cmd COMMAND\n" >&2 + exit 1 + fi + +} + c8l_cli_parse_requirements() { while [[ $# -gt 0 ]]; do @@ -2044,6 +2155,7 @@ run() { "help") c8l_help_command ;; "inspect") c8l_inspect_command ;; "source") c8l_source_command ;; + "cmd") c8l_cmd_command ;; "cli") c8l_cli_command ;; "cli install-tools") c8l_cli_install_tools_command ;; "cli attestation-add-from-yaml") c8l_cli_attestation_add_from_yaml_command ;; diff --git a/tools/src/bashly.yml b/tools/src/bashly.yml index c995469..3a365a9 100644 --- a/tools/src/bashly.yml +++ b/tools/src/bashly.yml @@ -1,5 +1,5 @@ name: c8l -help: "[EXPERIMENTAL] Chainloop (c8) Labs CLI" +help: "[EXPERIMENTAL] (c8l) Chainloop Labs CLI" version: 0.3.0 commands: @@ -14,6 +14,14 @@ commands: - name: source help: "Show the content of c8l script ready for sourcing." + - name: cmd + help: "Run a command in the c8l environment." + alias: r + args: + - name: command + required: true + help: Command to run in the c8l environment. + - name: cli alias: c help: Chainloop CLI UX improved diff --git a/tools/src/cmd_command.sh b/tools/src/cmd_command.sh new file mode 100644 index 0000000..a050044 --- /dev/null +++ b/tools/src/cmd_command.sh @@ -0,0 +1 @@ +eval "${args['command']}" diff --git a/tools/src/lib/base.sh b/tools/src/lib/base.sh index 9218c36..16a1636 100644 --- a/tools/src/lib/base.sh +++ b/tools/src/lib/base.sh @@ -1,4 +1,4 @@ -export CHAINLOOP_BIN_PATH="${CHAINLOOP_BIN_PATH:-/usr/local/bin/chainloop}" +export CHAINLOOP_BIN_PATH="${CHAINLOOP_BIN_PATH:-/usr/local/bin/chainloop_bin}" is_chainloop_in_path() { if command -v chainloop &>/dev/null; then @@ -43,9 +43,9 @@ prepare_tmp_file() { file_name=$1 mkdir -p "${tmp_dir}" t="${tmp_dir}/${file_name}" - if [ -f $t ]; then + if [ -f "$t" ]; then echo "Temporary file file $t already exists" return 1 fi - echo $t + echo "$t" } \ No newline at end of file diff --git a/tools/src/lib/cache.sh b/tools/src/lib/cache.sh index 00c0f38..8d8b263 100644 --- a/tools/src/lib/cache.sh +++ b/tools/src/lib/cache.sh @@ -19,6 +19,7 @@ chainloop_recreate_env_from_file() { file=$(basename $path) if [[ $file =~ ^\.env_.*$ ]]; then export $(echo $file | sed 's/\.env_//')=$(cat $path) + echo export $(echo $file | sed 's/\.env_//')=$(cat $path) else log_error "File $file is not in the format .env_NAME" return 1 diff --git a/tools/src/lib/chainloop.sh b/tools/src/lib/chainloop.sh index 4d06362..aed6afa 100644 --- a/tools/src/lib/chainloop.sh +++ b/tools/src/lib/chainloop.sh @@ -64,6 +64,7 @@ generic_install() { mkdir -p $CHAINLOOP_BIN_PATH log "Installing $file" curl -sfL $url -o $file_path + if [ $? -ne 0 ]; then log_error "$file installation failed" return 1 @@ -125,25 +126,32 @@ chainloop_attestation_push() { export COSIGN_PASSWORD="$CHAINLOOP_SIGNING_PASSWORD" cosign generate-key-pair fi - if [ -z "${CHAINLOOP_SIGNING_KEY_PATH+x}" ]; then + if [ -n "${CHAINLOOP_SIGNING_KEY}" ]; then log " with CHAINLOOP_SIGNING_KEY" tmp_key="${CHAINLOOP_TMP_DIR}/key" mkdir -p "${CHAINLOOP_TMP_DIR}" - echo "${CHAINLOOP_SIGNING_KEY}" >$tmp_key - else + echo "${CHAINLOOP_SIGNING_KEY}" > "$tmp_key" + fi + if [ -n "${CHAINLOOP_SIGNING_KEY_PATH}" ]; then log " with CHAINLOOP_SIGNING_KEY_PATH" tmp_key="${CHAINLOOP_SIGNING_KEY_PATH}" fi + + tmp_key_value="" + if [ -n "$tmp_key" ]; then + tmp_key_value="--key $tmp_key" + fi + # chainloop attestation push --key env://CHAINLOOP_SIGNING_KEY - if chainloop attestation push --key $tmp_key --remote-state --attestation-id "${CHAINLOOP_ATTESTATION_ID}" &>c8-push.txt; then + if chainloop attestation push "$tmp_key_value" --output json --remote-state --attestation-id "${CHAINLOOP_ATTESTATION_ID}" > c8-push.txt; then log "Attestation Process Completed Successfully" cat c8-push.txt - rm $tmp_key + rm -f "$tmp_key" else exit_code=$? log_error "Attestation Process Failed" cat c8-push.txt - rm $tmp_key + rm -f "$tmp_key" return $exit_code fi } @@ -154,20 +162,24 @@ chainloop_summary() { log $tmpfile return 1 fi - echo -e "## Great job!\n\nYou are making SecOps and Compliance teams really happy. Keep up the good work!\n" >>$tmpfile + echo -e "## Great job!\n\nYou are making SecOps and Compliance teams really happy. Keep up the good work!\n" >> $tmpfile digest="" if [ -f c8-push.txt ]; then - digest=$(cat c8-push.txt | grep " Digest: " | awk -F'sha256:' '{print $2}') - echo "**[Chainloop Trust Report]( https://app.chainloop.dev/attestation/sha256:${digest} )**" >>$tmpfile - echo "\`\`\`" >>$tmpfile - fi - if [ -f c8-status.txt ]; then - cat c8-status.txt >>$tmpfile - echo "\`\`\`" >>$tmpfile + digest=$(cat c8-push.txt | jq -r '.digest') + if [ $? -ne 0 ]; then + log_error "Failed to get digest from c8-push.txt" + return 1 + fi + echo "**[Chainloop Trust Report]( https://app.chainloop.dev/attestation/${digest} )**" >> "$tmpfile" + echo "\`\`\`" >> "$tmpfile" + if [ -f c8-status.txt ] ; then + cat c8-status.txt >> "$tmpfile" + fi + echo "\`\`\`" >> "$tmpfile" fi - cat $tmpfile - rm $tmpfile + cat "$tmpfile" + rm "$tmpfile" } chainloop_summary_on_failure() { diff --git a/tools/test/run.sh b/tools/test/run.sh new file mode 100755 index 0000000..74b482f --- /dev/null +++ b/tools/test/run.sh @@ -0,0 +1,2 @@ +#!/bin/sh +./test/bats/bin/bats --print-output-on-failure test diff --git a/tools/test/test.bats b/tools/test/test.bats index 27c8ed2..85e6c6b 100644 --- a/tools/test/test.bats +++ b/tools/test/test.bats @@ -1,5 +1,4 @@ # setup_file() { -# # } setup() { @@ -8,7 +7,8 @@ setup() { load 'test_helper/bats-file/load' DIR="$( cd "$( dirname "${BATS_TEST_FILENAME}" )/.." >/dev/null 2>&1 && pwd )" - PATH="$DIR/:$PATH" + export PATH="$DIR/:$PATH:/usr/local/bin/chainloop_bin" + export DO_NOT_TRACK=1 } @test "can run c8l script" { @@ -28,7 +28,7 @@ setup() { } @test "can detect when chainloop is NOT in PATH" { - run bash -c "source <(./c8l source) > /dev/null; is_chainloop_in_path" + run bash -c "c8l r is_chainloop_in_path" assert_output --regexp ".*chainloop is not in PATH.*" # assert_line --index 0 --regexp ".*chainloop is not in PATH.*" } @@ -38,8 +38,11 @@ setup() { assert_success } + ### ### Integration tests +### + # TODO: move to integration tests @test "can run c8l_install.sh" { cd /tmp @@ -55,4 +58,39 @@ setup() { @test "can detect chainloop in PATH" { run bash -c "source <(./c8l source); is_chainloop_in_path" assert_success +} + +# Abats test_tags=bats:focus +@test "full attestation flow" { + export CHAINLOOP_WORKFLOW_NAME="chainloop-labs-tests" + cp ./c8l /tmp + cd /tmp + mkdir -p .c8l_cache + + c8l r "chainloop_install yq jq cosign chainloop_cli " + c8l r "chainloop_attestation_init ; chainloop_save_env_to_cache .c8l_cache CHAINLOOP_ATTESTATION_ID" + + echo -e "chainloop-labs-tests:\n - path: ./c8l" > .chainloop.yml + # yq -p yaml -o json .chainloop.yml > .chainloop.json + + source <(c8l r 'chainloop_restore_env_all_from_cache .c8l_cache | grep export') + # chainloop_attestation_add_from_yaml demo + chainloop attestation add --value ./c8l --kind ARTIFACT --remote-state --attestation-id ${CHAINLOOP_ATTESTATION_ID} + + c8l r chainloop_attestation_status + run c8l r chainloop_attestation_push + assert_success + + digest=$(cat c8-push.txt | jq -r '.digest') + run bash -c "c8l r chainloop_summary" + assert_output --regexp ".*attestation\/${digest}.*" + + ### + # use key pair + c8l r "chainloop_attestation_init ; chainloop_save_env_to_cache .c8l_cache CHAINLOOP_ATTESTATION_ID" + source <(c8l r 'chainloop_restore_env_all_from_cache .c8l_cache | grep export') + chainloop attestation add --value ./c8l --kind ARTIFACT --remote-state --attestation-id ${CHAINLOOP_ATTESTATION_ID} + CHAINLOOP_USE_INSECURE_KEY=true + run bash -c "c8l r chainloop_attestation_push" + assert_success } \ No newline at end of file From e4d9ee7d2532f0aaf735e9b0d3e7e545066970bb Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Thu, 6 Jun 2024 23:40:18 +0200 Subject: [PATCH 2/3] Adding more integration tests and refactoring Signed-off-by: Daniel Liszka --- tools/c8l | 6 +++--- tools/src/lib/chainloop.sh | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/c8l b/tools/c8l index 2e8abfd..2250928 100755 --- a/tools/c8l +++ b/tools/c8l @@ -904,10 +904,10 @@ chainloop_summary() { return 1 fi echo "**[Chainloop Trust Report]( https://app.chainloop.dev/attestation/${digest} )**" >> "$tmpfile" + fi + if [ -f c8-status.txt ] ; then echo "\`\`\`" >> "$tmpfile" - if [ -f c8-status.txt ] ; then - cat c8-status.txt >> "$tmpfile" - fi + cat c8-status.txt >> "$tmpfile" echo "\`\`\`" >> "$tmpfile" fi cat "$tmpfile" diff --git a/tools/src/lib/chainloop.sh b/tools/src/lib/chainloop.sh index aed6afa..096f40a 100644 --- a/tools/src/lib/chainloop.sh +++ b/tools/src/lib/chainloop.sh @@ -172,10 +172,10 @@ chainloop_summary() { return 1 fi echo "**[Chainloop Trust Report]( https://app.chainloop.dev/attestation/${digest} )**" >> "$tmpfile" + fi + if [ -f c8-status.txt ] ; then echo "\`\`\`" >> "$tmpfile" - if [ -f c8-status.txt ] ; then - cat c8-status.txt >> "$tmpfile" - fi + cat c8-status.txt >> "$tmpfile" echo "\`\`\`" >> "$tmpfile" fi cat "$tmpfile" From 58f2f12fed2cfd0132728df0c3355c18a58966cd Mon Sep 17 00:00:00 2001 From: Daniel Liszka Date: Thu, 6 Jun 2024 23:47:51 +0200 Subject: [PATCH 3/3] Adding more integration tests and refactoring Signed-off-by: Daniel Liszka --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3d2fecd..413f4a9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,8 @@ jobs: - name: ${{ matrix.key }} run: make -C tools ${{ matrix.key }} + env: + CHAINLOOP_TOKEN: ${{ secrets.CHAINLOOP_TOKEN }} test_build: name: Test if script generation works