Skip to content
Open
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
31 changes: 31 additions & 0 deletions .github/actions/setup-go-wolfssl/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Setup Go for go-wolfssl
description: >-
Select a Go toolchain baked into the go-wolfssl-ci image, keep it from
switching toolchains, and add the one module dependency (golang.org/x/term,
used by the aes-encrypt example) at a version that still supports the
oldest Go in CI. The image's module cache already holds it.

inputs:
go-version:
description: A Go minor baked into the image (1.21, 1.26), or stable or oldstable.
required: false
default: stable

runs:
using: composite
steps:
- name: Select Go ${{ inputs.go-version }}
shell: bash
env:
GO_VERSION: ${{ inputs.go-version }}
run: |
set -euo pipefail
goroot="/opt/go/$GO_VERSION"
if [ ! -x "$goroot/bin/go" ]; then
echo "::error::Go $GO_VERSION is not in the CI image; have: $(ls /opt/go | tr '\n' ' ')"
exit 1
fi
echo "$goroot/bin" >> "$GITHUB_PATH"
echo "GOTOOLCHAIN=local" >> "$GITHUB_ENV"
"$goroot/bin/go" get golang.org/x/term@v0.25.0
"$goroot/bin/go" version
140 changes: 140 additions & 0 deletions .github/actions/setup-wolfssl/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
name: Setup wolfSSL
description: >-
Build (or restore from cache) wolfSSL and install it under a prefix. A git
ref is resolved to its commit so the cache tracks the upstream tree, and
every build-affecting input is folded into the cache key. With
fips-ready-version set, the public FIPS Ready bundle is built instead.

inputs:
ref:
description: wolfSSL git ref (branch, tag or commit). Ignored for FIPS Ready.
required: false
default: master
configure-flags:
description: wolfSSL ./configure flags.
required: false
default: ""
cflags:
description: Extra CFLAGS, passed only when non-empty so wolfSSL keeps its default optimization.
required: false
default: ""
ldflags:
description: Extra LDFLAGS, passed only when non-empty.
required: false
default: ""
cc:
description: C compiler to build wolfSSL with.
required: false
default: gcc
prefix:
description: Install prefix.
required: true
fips-ready-version:
description: Build the wolfSSL FIPS Ready bundle of this version instead of a git ref.
required: false
default: ""
fips-ready-sha256:
description: Expected sha256 of the FIPS Ready zip.
required: false
default: ""

outputs:
sha:
description: The wolfSSL commit built, or fips-ready-<version>.
value: ${{ steps.key.outputs.sha }}

runs:
using: composite
steps:
- name: Resolve wolfSSL and compute the cache key
id: key
shell: bash
env:
REF: ${{ inputs.ref }}
FLAGS: ${{ inputs.configure-flags }}
XCFLAGS: ${{ inputs.cflags }}
XLDFLAGS: ${{ inputs.ldflags }}
XCC: ${{ inputs.cc }}
PREFIX: ${{ inputs.prefix }}
FIPS_READY: ${{ inputs.fips-ready-version }}
FIPS_READY_SHA256: ${{ inputs.fips-ready-sha256 }}
run: |
set -euo pipefail
if [ -n "$FIPS_READY" ]; then
if [ -z "$FIPS_READY_SHA256" ]; then
echo "::error::fips-ready-sha256 is required with fips-ready-version"
exit 1
fi
SHA="fips-ready-$FIPS_READY-${FIPS_READY_SHA256:0:16}"
else
URL=https://github.com/wolfSSL/wolfssl.git
# Peel annotated tags to their commit, then fall back to a branch,
# then treat the ref as a raw commit.
SHA=$(git ls-remote "$URL" "$REF^{}" | awk 'NR==1{print $1}')
[ -n "$SHA" ] || SHA=$(git ls-remote "$URL" "$REF" | awk 'NR==1{print $1}')
[ -n "$SHA" ] || SHA=$REF
fi
CFGHASH=$(printf '%s\037%s\037%s\037%s\037%s' "$FLAGS" "$XCFLAGS" "$XLDFLAGS" "$XCC" "$PREFIX" \
| sha256sum | cut -c1-16)
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "cache-key=go-wolfssl-ci-v1-wolfssl-${RUNNER_OS}-${SHA}-${CFGHASH}" >> "$GITHUB_OUTPUT"
echo "wolfSSL ${FIPS_READY:+FIPS Ready }${FIPS_READY:-$REF} -> $SHA (cfg $CFGHASH)"

- name: Restore the wolfSSL install
id: cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ inputs.prefix }}
key: ${{ steps.key.outputs.cache-key }}

- name: Fetch wolfSSL
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
env:
SHA: ${{ steps.key.outputs.sha }}
FIPS_READY: ${{ inputs.fips-ready-version }}
FIPS_READY_SHA256: ${{ inputs.fips-ready-sha256 }}
run: |
set -euo pipefail
rm -rf "$RUNNER_TEMP/wolfssl-src"
if [ -n "$FIPS_READY" ]; then
curl -fsSL -o "$RUNNER_TEMP/fips-ready.zip" \
"https://www.wolfssl.com/wolfssl-$FIPS_READY-gplv3-fips-ready.zip"
echo "$FIPS_READY_SHA256 $RUNNER_TEMP/fips-ready.zip" | sha256sum -c -
unzip -q "$RUNNER_TEMP/fips-ready.zip" -d "$RUNNER_TEMP/fips-ready"
mv "$RUNNER_TEMP/fips-ready/wolfssl-$FIPS_READY-gplv3-fips-ready" "$RUNNER_TEMP/wolfssl-src"
else
git init -q "$RUNNER_TEMP/wolfssl-src"
cd "$RUNNER_TEMP/wolfssl-src"
git fetch -q --depth 1 https://github.com/wolfSSL/wolfssl.git "$SHA"
git checkout -q --detach FETCH_HEAD
./autogen.sh
fi

- name: Build and install wolfSSL
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
env:
FLAGS: ${{ inputs.configure-flags }}
XCFLAGS: ${{ inputs.cflags }}
XLDFLAGS: ${{ inputs.ldflags }}
XCC: ${{ inputs.cc }}
PREFIX: ${{ inputs.prefix }}
FIPS_READY: ${{ inputs.fips-ready-version }}
run: |
set -euo pipefail
cd "$RUNNER_TEMP/wolfssl-src"
args=(--prefix="$PREFIX")
# Flags come from .github/scripts/run-config.sh and may carry
# CPPFLAGS=..., so split them the way a shell command line would.
eval "args+=($FLAGS)"
[ -z "$XCFLAGS" ] || args+=("CFLAGS=$XCFLAGS")
[ -z "$XLDFLAGS" ] || args+=("LDFLAGS=$XLDFLAGS")
CC="$XCC" ./configure "${args[@]}"
make -j"$(nproc)"
if [ -n "$FIPS_READY" ]; then
# FIPS Ready checks its in-core hash, which changes with every build.
./fips-hash.sh
make -j"$(nproc)"
fi
make install
42 changes: 42 additions & 0 deletions .github/docker/go-wolfssl-ci/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Shared dependencies for go-wolfssl Linux CI. Rebuild through publish-ci-image.yml.
FROM ubuntu:24.04

LABEL org.opencontainers.image.source="https://github.com/wolfSSL/go-wolfssl" \
org.opencontainers.image.description="go-wolfssl Linux CI toolchains and test dependencies" \
org.opencontainers.image.licenses="GPL-3.0-or-later"

SHELL ["/bin/bash", "-o", "pipefail", "-c"]

ENV DEBIAN_FRONTEND=noninteractive \
GOPATH=/opt/gopath \
GOTOOLCHAIN=local \
GOTELEMETRY=off \
PATH=/opt/go-tools/bin:/opt/go/stable/bin:$PATH

# Package installation belongs here, rather than in individual CI jobs.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential gcc-12 gcc-13 gcc-14 \
clang clang-16 clang-17 clang-18 libclang-rt-18-dev llvm \
autoconf automake libtool libtool-bin pkg-config m4 \
git curl ca-certificates jq openssl iproute2 procps \
nodejs python3 python3-yaml shellcheck \
zip unzip xz-utils zstd file \
&& rm -rf /var/lib/apt/lists/*

# Go 1.21 (the CI floor), the analysis toolchain, and the stable and oldstable
# releases, resolved at image build time rather than downloaded in every job.
ARG ANALYSIS_GO=1.26
COPY .github/docker/go-wolfssl-ci/install-go.sh /tmp/install-go.sh
RUN /tmp/install-go.sh 1.21 "$ANALYSIS_GO" && rm /tmp/install-go.sh

# staticcheck v0.8.1 reads export data only up to Go 1.26; bump both together.
RUN GOBIN=/opt/go-tools/bin "/opt/go/$ANALYSIS_GO/bin/go" install honnef.co/go/tools/cmd/staticcheck@v0.8.1 \
&& GOBIN=/opt/go-tools/bin "/opt/go/$ANALYSIS_GO/bin/go" install golang.org/x/vuln/cmd/govulncheck@v1.8.0 \
&& GOBIN=/opt/go-tools/bin go install github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 \
&& prefetch=$(mktemp -d) && cd "$prefetch" \
&& go mod init prefetch && go get golang.org/x/term@v0.25.0 \
&& cd / && rm -rf "$prefetch" "$(go env GOCACHE)"

COPY .github/docker/go-wolfssl-ci/smoke-test.sh /usr/local/bin/go-wolfssl-ci-smoke
RUN chmod +x /usr/local/bin/go-wolfssl-ci-smoke && go-wolfssl-ci-smoke
6 changes: 6 additions & 0 deletions .github/docker/go-wolfssl-ci/Dockerfile.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Only send the image assets to Docker.
**
!.github/
!.github/docker/
!.github/docker/go-wolfssl-ci/
!.github/docker/go-wolfssl-ci/**
48 changes: 48 additions & 0 deletions .github/docker/go-wolfssl-ci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# go-wolfssl CI image

Linux workflows pin `ghcr.io/wolfssl/go-wolfssl-ci` by its immutable image
digest. The image contains gcc-12 to 14, clang-16 to 18 with the sanitizer
runtimes, the autotools wolfSSL needs, OpenSSL, Go 1.21, the analysis Go
(1.26), the stable and oldstable Go releases, staticcheck, govulncheck,
actionlint, and shellcheck. Each Go toolchain lives under `/opt/go/<minor>`,
with `/opt/go/stable` and `/opt/go/oldstable` links, and
`.github/actions/setup-go-wolfssl` selects one by name.

From the repository root:

```sh
docker build --platform linux/amd64 \
-f .github/docker/go-wolfssl-ci/Dockerfile -t go-wolfssl-ci .
docker run --rm --platform linux/amd64 go-wolfssl-ci go-wolfssl-ci-smoke
```

The Dockerfile-specific ignore file restricts the build context to the image
assets. Add shared dependencies here instead of installing them in workflow
jobs. Go patch releases and the stable and oldstable minors are resolved when
the image is rebuilt, and each archive is checked against go.dev's sha256.

In `wolfSSL/go-wolfssl`, `publish-ci-image.yml` runs on relevant pushes to
`master` and manual dispatches targeting `master`. It builds and smoke-tests
the image, then publishes `latest` and `sha-<commit>` tags. Publication is
serialized and uses the repository's `GITHUB_TOKEN`. The GHCR package must be
public so fork PRs can pull anonymously; the publisher checks this.

After publishing, a container job uses the build's output digest to repeat the
smoke checks in the GitHub runner environment.

Consumers should never fall back to building the image or installing packages.

wolfSSL caches use the `go-wolfssl-ci-v1` prefix to separate them from
previous host builds. Bump this prefix if an image change makes cached builds
incompatible.

Container jobs trust the mounted GitHub workspace after checkout so Git, and
Go's VCS stamping, can read a repository owned by the host runner. Their
explicit Bash command keeps the previous host shell's error behavior. The
sanitizer jobs run privileged so they can lower `vm.mmap_rnd_bits`.

To adopt a rebuilt image, inspect its `sha-<commit>` tag with
`docker buildx imagetools inspect` and copy the reported image digest into the
consumer workflows in a reviewed change. The publisher's smoke job tests the
new digest immediately, while consumer jobs keep using their reviewed digest.
To roll back, restore the previous digest in those workflows.
31 changes: 31 additions & 0 deletions .github/docker/go-wolfssl-ci/install-go.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash
# Install the newest patch of each requested Go minor, plus the current stable
# and oldstable minors, under /opt/go/<minor>, checked against go.dev's sha256.
set -euo pipefail

releases=$(mktemp)
trap 'rm -f "$releases"' EXIT
curl --fail --silent --show-error --location \
'https://go.dev/dl/?mode=json&include=all' -o "$releases"

mapfile -t latest < <(jq -r '[.[] | select(.stable) | .version
| capture("^go(?<m>[0-9]+\\.[0-9]+)").m] | unique
| sort_by(split(".") | map(tonumber)) | reverse | .[0:2][]' "$releases")
test "${#latest[@]}" -eq 2

mkdir -p /opt/go
for minor in $(printf '%s\n' "$@" "${latest[@]}" | sort -u); do
read -r archive sha < <(jq -r --arg re "^go${minor//./\\.}\\.[0-9]+$" '
[.[] | select(.stable and (.version | test($re)))] | first | .files[]
| select(.os == "linux" and .arch == "amd64" and .kind == "archive")
| [.filename, .sha256] | @tsv' "$releases")
test -n "$archive" && test -n "$sha"
curl --fail --silent --show-error --location \
"https://go.dev/dl/$archive" -o /tmp/go.tar.gz
echo "$sha /tmp/go.tar.gz" | sha256sum --check -
mkdir "/opt/go/$minor"
tar -xzf /tmp/go.tar.gz -C "/opt/go/$minor" --strip-components=1
rm /tmp/go.tar.gz
done
ln -s "${latest[0]}" /opt/go/stable
ln -s "${latest[1]}" /opt/go/oldstable
49 changes: 49 additions & 0 deletions .github/docker/go-wolfssl-ci/smoke-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/bin/bash
# Exercise the installed toolchains before an image is published.
set -euo pipefail

smoke_dir=$(mktemp -d)
trap 'rm -rf "$smoke_dir"' EXIT
cat > "$smoke_dir/main.c" <<'EOF'
int main(void)
{
return 0;
}
EOF

for compiler in gcc gcc-12 gcc-13 gcc-14 clang clang-16 clang-17 clang-18; do
"$compiler" --version | head -n 1
"$compiler" -Wall -Wextra -Werror "$smoke_dir/main.c" -o "$smoke_dir/compiler-test"
"$smoke_dir/compiler-test"
done
for sanitizer in address undefined memory; do
clang -fsanitize="$sanitizer" "$smoke_dir/main.c" -o "$smoke_dir/sanitizer-test"
done

mkdir "$smoke_dir/cgo"
cat > "$smoke_dir/cgo/main.go" <<'EOF'
package main

// int answer(void) { return 42; }
import "C"

func main() {
if C.answer() != 42 {
panic("cgo")
}
}
EOF
printf 'module smoke\n\ngo 1.21\n' > "$smoke_dir/cgo/go.mod"
for goroot in /opt/go/1.21 /opt/go/oldstable /opt/go/stable; do
"$goroot/bin/go" version
(cd "$smoke_dir/cgo" && "$goroot/bin/go" run .)
done
for tool in autoconf automake libtoolize make pkg-config git curl jq openssl \
ss sysctl node python3 shellcheck zip unzip zstd llvm-symbolizer; do
command -v "$tool"
done
python3 -c 'import yaml'
staticcheck -version
govulncheck -version
actionlint -version
test -d "$GOPATH/pkg/mod/golang.org/x/term@v0.25.0"
45 changes: 45 additions & 0 deletions .github/scripts/analyze.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/bin/bash
# Usage: analyze.sh <wolfssl-prefix>
# Runs go vet (every analyzer), staticcheck and govulncheck over the
# go-wolfssl packages and each example program, against the wolfSSL
# installed at <prefix>. staticcheck and govulncheck must be on PATH.

set -euo pipefail

PKGS=". ./handles ./sha256 ./wolftls ./wolfx509"

./generateOptions.sh "$1"
export LD_LIBRARY_PATH="$1/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"

# shellcheck disable=SC2086
go vet $PKGS
# Example directories hold several main programs, so vet each file alone,
# then vet directories with tests as packages so the tests are covered too.
grep -rl --include='*.go' --exclude='*_test.go' '^package main' examples | while read -r f; do
echo "go vet $f"
go vet "$f"
done
find examples -name '*_test.go' -exec dirname {} \; | sort -u | while read -r d; do
echo "go vet ./$d"
go vet "./$d"
done

# shellcheck disable=SC2086
staticcheck $PKGS
# Examples get the correctness checks; the ST* style checks are for the library.
grep -rl --include='*.go' --exclude='*_test.go' '^package main' examples | while read -r f; do
echo "staticcheck $f"
staticcheck -checks 'inherit,-ST*' "$f"
done

# shellcheck disable=SC2086
govulncheck $PKGS
# Examples cannot be loaded as packages, so scan each built program.
bin=$(mktemp -d)
trap 'rm -rf "$bin"' EXIT
grep -rl --include='*.go' --exclude='*_test.go' '^package main' examples | while read -r f; do
out="$bin/$(echo "$f" | tr '/.' '__')"
go build -o "$out" "$f"
echo "govulncheck -mode=binary $f"
govulncheck -mode=binary "$out"
done
Loading
Loading