Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions .github/tests/codeql-workflow.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env zsh

# Validate the observable structure of the zredis CodeQL workflow.

emulate -LR zsh
setopt errexit nounset pipefail

readonly script_dir="${0:A:h}"
readonly workflow="${1:-${script_dir:h}/workflows/codeql-analysis.yml}"
readonly yq_bin="${YQ_BIN:-yq}"

fail() {
print -u2 -r -- "FAIL: $1"
exit 1
}

assert_yq() {
local description="$1"
local expression="$2"

if ! "$yq_bin" eval --exit-status "$expression" "$workflow" >/dev/null; then
fail "$description"
fi
}

[[ -r "$workflow" ]] || fail "workflow is not readable: $workflow"
if [[ "$yq_bin" == */* ]]; then
[[ -x "$yq_bin" ]] || fail "YQ_BIN is not executable: $yq_bin"
else
command -v "$yq_bin" >/dev/null || fail "yq is not available: $yq_bin"
fi

assert_yq \
"expected exactly the Actions/none and C/C++/manual matrix entries" \
'(.jobs.codeql.strategy."fail-fast" == false) and
((.jobs.codeql.strategy.matrix.include | length) == 2) and
(([.jobs.codeql.strategy.matrix.include[] |
select(.language == "actions" and ."build-mode" == "none")] |
length) == 1) and
(([.jobs.codeql.strategy.matrix.include[] |
select(.language == "c-cpp" and ."build-mode" == "manual")] |
length) == 1)'

assert_yq \
"expected plain workflow and job names" \
'.name == "CodeQL" and
.jobs.codeql.name == "CodeQL (${{ matrix.language }})"'

assert_yq \
"expected push, pull request, weekly schedule, and manual triggers" \
'(.on.push.branches | length) == 1 and
.on.push.branches[0] == "main" and
(.on.pull_request.branches | length) == 1 and
.on.pull_request.branches[0] == "main" and
(.on.schedule | length) == 1 and
.on.schedule[0].cron == "15 21 * * 6" and
.on.workflow_dispatch.inputs."upload-results".type == "boolean" and
.on.workflow_dispatch.inputs."upload-results".default == false'

assert_yq \
"expected workflow/ref concurrency with cancellation" \
'.concurrency.group == "codeql-${{ github.workflow }}-${{ github.ref }}" and
.concurrency."cancel-in-progress" == true'

assert_yq \
"expected least-privilege workflow and job permissions" \
'(.permissions | length) == 1 and
.permissions.contents == "read" and
(.jobs.codeql.permissions | length) == 2 and
.jobs.codeql.permissions.contents == "read" and
.jobs.codeql.permissions."security-events" == "write"'

assert_yq \
"expected a 30-minute CodeQL timeout" \
'.jobs.codeql."timeout-minutes" == 30'

assert_yq \
"expected immutable checkout and CodeQL initialization" \
'(([.jobs.codeql.steps[] |
select(.uses ==
"actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1")] |
length) == 1) and
(([.jobs.codeql.steps[] |
select(.uses ==
"github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81" and
.with.languages == "${{ matrix.language }}" and
.with."build-mode" == "${{ matrix.build-mode }}" and
.with.queries == "security-extended")] |
length) == 1)'

assert_yq \
"expected the exact C/C++ dependency installation" \
'([.jobs.codeql.steps[] |
select(.name == "Install C/C++ dependencies" and
.if == "matrix.build-mode == '\''manual'\''" and
(.run | contains("build-essential")) and
(.run | contains("zsh")) and
(.run | contains("yodl")) and
(.run | contains("libhiredis-dev")) and
(.run | contains("libgdbm-dev")) and
(.run | contains("libncurses-dev")))] |
length) == 1'

assert_yq \
"expected the explicit module configure, capability, and output checks" \
'([.jobs.codeql.steps[] |
select(.name == "Build C/C++ modules" and
.if == "matrix.build-mode == '\''manual'\''" and
."working-directory" == "module" and
(.run | contains("./configure --enable-gdbm --without-tcsetpgrp")) and
(.run | contains("HAVE_HIREDIS_HIREDIS_H")) and
(.run | contains("HAVE_REDISCONNECT")) and
(.run | contains("HAVE_GDBM_H")) and
(.run | contains("HAVE_GDBM_OPEN")) and
(.run | contains("\nmake\n")) and
(.run | contains("name=zshell/db")) and
(.run | contains("name=zshell/zredis")) and
(.run | contains("name=zshell/zgdbm")) and
(.run | contains("Src/zshell/db.so")) and
(.run | contains("Src/zshell/zredis.so")) and
(.run | contains("Src/zshell/zgdbm.so")))] |
length) == 1'

assert_yq \
"expected mutually exclusive upload and non-upload analysis steps" \
'(([.jobs.codeql.steps[] |
select(.name == "Analyze without upload" and
.uses ==
"github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81" and
.if ==
"github.event_name == '\''pull_request'\'' || (github.event_name == '\''workflow_dispatch'\'' && inputs.upload-results != '\''true'\'')" and
.with.upload == "never" and
.with.category == "/language:${{ matrix.language }}")] |
Comment thread
Copilot marked this conversation as resolved.
length) == 1) and
(([.jobs.codeql.steps[] |
select(.name == "Analyze and upload" and
.uses ==
"github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81" and
.if ==
"github.event_name == '\''push'\'' || github.event_name == '\''schedule'\'' || (github.event_name == '\''workflow_dispatch'\'' && inputs.upload-results == true)" and
.with.category == "/language:${{ matrix.language }}" and
(.with | has("upload") | not))] |
length) == 1)'

assert_yq \
"expected no CodeQL autobuild action" \
'([.jobs.codeql.steps[].uses // "" |
select(test("github/codeql-action/autobuild@"))] |
length) == 0'

assert_yq \
"expected every action reference to use a full immutable SHA" \
'([.jobs.codeql.steps[].uses // "" |
select(. != "" and (test("@[0-9a-f]{40}$") | not))] |
length) == 0'

print -r -- "PASS: CodeQL workflow contract"
99 changes: 80 additions & 19 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
@@ -1,35 +1,96 @@
---
name: 👾 CodeQL
name: CodeQL

on:
push:
branches: [ main ]
branches:
- main
pull_request:
branches: [ main ]
branches:
- main
schedule:
- cron: '15 21 * * 6'
- cron: "15 21 * * 6"
workflow_dispatch:
inputs:
upload-results:
description: Upload CodeQL results
required: false
type: boolean
default: false

permissions:
contents: read

concurrency:
group: codeql-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
codeql:
name: 🏭 Scanning
name: CodeQL (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
include:
- language: actions
build-mode: none
- language: c-cpp
build-mode: manual

steps:
- name: ⤵️ Check out code from GitHub
uses: actions/checkout@v6
- name: 🏗 Initialize CodeQL
uses: github/codeql-action/init@v4
- name: 🏗 Dependencies
run: sudo apt-get update && sudo apt-get install zsh -yq
- name: 🏗 Configure
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Initialize CodeQL
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
queries: security-extended

- name: Install C/C++ dependencies
if: matrix.build-mode == 'manual'
run: |
CPPFLAGS="$cppf" CFLAGS="$cf${macro:+ $macro}" LDFLAGS="$ldf" ./module/configure --enable-gdbm --without-tcsetpgrp ${=cfgopts}
shell: zsh {0}
- name: 🏗 Autobuild
uses: github/codeql-action/autobuild@v4
- name: 🚀 Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
sudo apt-get update
sudo apt-get install --yes --no-install-recommends \
build-essential \
zsh \
yodl \
libhiredis-dev \
libgdbm-dev \
libncurses-dev

- name: Build C/C++ modules
if: matrix.build-mode == 'manual'
working-directory: module
run: |
./configure --enable-gdbm --without-tcsetpgrp
grep -Eq '^#define HAVE_HIREDIS_HIREDIS_H 1$' config.h
grep -Eq '^#define HAVE_REDISCONNECT 1$' config.h
grep -Eq '^#define HAVE_GDBM_H 1$' config.h
grep -Eq '^#define HAVE_GDBM_OPEN 1$' config.h
make
grep -Eq '^name=zshell/db .*link=dynamic' config.modules
grep -Eq '^name=zshell/zredis .*link=dynamic' config.modules
grep -Eq '^name=zshell/zgdbm .*link=dynamic' config.modules
test -s Src/zshell/db.so
test -s Src/zshell/zredis.so
test -s Src/zshell/zgdbm.so

- name: Analyze without upload
if: github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && inputs.upload-results != 'true')
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
Comment thread
Copilot marked this conversation as resolved.
with:
upload: never
category: /language:${{ matrix.language }}

- name: Analyze and upload
if: github.event_name == 'push' || github.event_name == 'schedule' || (github.event_name == 'workflow_dispatch' && inputs.upload-results == true)
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
with:
category: /language:${{ matrix.language }}
Loading