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
28 changes: 22 additions & 6 deletions .devcontainer/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
FROM node:22-bookworm-slim AS node
FROM node:22.23.4-bookworm-slim@sha256:de170b39439eb0e5a3444ad7d60b015614a381e10fecae2e30ec9e735122f38d AS node

FROM composer:2 AS composer
FROM composer:2.10.3@sha256:da624c2c705d54e2f5a9bb77a5dc6c7d091711433ebefd7035672e84c2010fee AS composer

FROM docker:27.5.1-cli AS docker-cli
FROM docker:27.5.1-cli@sha256:851f91d241214e7c6db86513b270d58776379aacc5eb9c4a87e5b47115e3065c AS docker-cli

FROM rust:1.86.0-slim-bookworm AS rust
FROM rust:1.86.0-slim-bookworm@sha256:57d415bbd61ce11e2d5f73de068103c7bd9f3188dc132c97cef4a8f62989e944 AS rust

RUN set -eux; \
rm -rf /usr/local/rustup/downloads /usr/local/rustup/tmp; \
Expand All @@ -14,9 +14,9 @@
rustc --version; \
cargo --version

FROM mariadb:11.4 AS mysql-seed
FROM mariadb:11.4@sha256:250e1609e93c0e6aecf64bfbab2d84d5e89d2481540fe77447ff6c855c3b1873 AS mysql-seed

ENV MYSQL_ROOT_PASSWORD=password \

Check warning on line 19 in .devcontainer/docker/Dockerfile

View workflow job for this annotation

GitHub Actions / Validate candidate image

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "MYSQL_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/

Check warning on line 19 in .devcontainer/docker/Dockerfile

View workflow job for this annotation

GitHub Actions / Validate candidate image

Sensitive data should not be used in the ARG or ENV commands

SecretsUsedInArgOrEnv: Do not use ARG or ENV instructions for sensitive data (ENV "MYSQL_ROOT_PASSWORD") More info: https://docs.docker.com/go/dockerfile/rule/secrets-used-in-arg-or-env/
MYSQL_ROOT_HOST=% \
MYSQL_DATABASE=sample \
MYSQL_USER=laravel \
Expand Down Expand Up @@ -84,7 +84,7 @@
.; \
test -s /tmp/sample-app-mysql-seed.tar

FROM php:8.4-cli-bookworm
FROM php:8.4.27-cli-bookworm@sha256:496b5e02fe9fe44fcfb715023b931048111dc4e0bc719789d83c906be9f04b05

ARG VCS_REF=unknown
ARG DURABLE_WORKFLOW_CLI_VERSION
Expand Down Expand Up @@ -117,8 +117,11 @@
COPY --from=rust /usr/local/cargo /usr/local/cargo
COPY --from=rust /usr/local/rustup /usr/local/rustup
COPY docker/install-phpredis.sh /usr/local/bin/install-phpredis
COPY .devcontainer/docker/verify-php-trust.php .devcontainer/docker/verify-node-trust.mjs /usr/local/share/sample-app/

RUN set -eux; \
php -r 'exit(PHP_MAJOR_VERSION === 8 && PHP_MINOR_VERSION === 4 && PHP_VERSION_ID >= 80427 ? 0 : 1);'; \
php /usr/local/share/sample-app/verify-php-trust.php "$(command -v composer)"; \
ln -snf "/usr/share/zoneinfo/${TZ}" /etc/localtime; \
echo "${TZ}" > /etc/timezone; \
apt-get update; \
Expand Down Expand Up @@ -228,6 +231,8 @@
COPY polyglot/qualified-artifact-tuple.json /usr/local/share/sample-app/qualified-artifact-tuple.json

RUN set -eux; \
php /usr/local/share/sample-app/verify-php-trust.php "$(command -v composer)"; \
node /usr/local/share/sample-app/verify-node-trust.mjs; \
cli_version="${DURABLE_WORKFLOW_CLI_VERSION:-$(php -r '$tuple = json_decode(file_get_contents("/usr/local/share/sample-app/qualified-artifact-tuple.json"), true, flags: JSON_THROW_ON_ERROR); echo $tuple["artifacts"]["cli"];')}"; \
test -n "$cli_version"; \
curl -fsSL https://durable-workflow.com/install.sh -o /tmp/dw-install.sh; \
Expand All @@ -238,11 +243,22 @@
dw --version

COPY playground/templates/python/requirements.txt /tmp/sample-app-python-playground-requirements.txt
COPY .devcontainer/docker/verify-python-trust.py /usr/local/share/sample-app/verify-python-trust.py

RUN set -eux; \
python_sdk_version="$(php -r '$tuple = json_decode(file_get_contents("/usr/local/share/sample-app/qualified-artifact-tuple.json"), true, flags: JSON_THROW_ON_ERROR); echo $tuple["artifacts"]["sdk-python"];')"; \
python_distribution_version="$(printf '%s' "$python_sdk_version" | sed 's/-rc\./rc/')"; \
python3 -m venv "$VIRTUAL_ENV"; \
curl --proto '=https' --proto-redir '=https' -fsSL --max-time 120 \
https://files.pythonhosted.org/packages/3f/06/3e4420302363fbb71be3c7f35ce042f54bfca330e3e66b3a2e3c4cca579f/pip-26.2.2-py3-none-any.whl \
-o /tmp/pip-26.2.2-py3-none-any.whl; \
printf '%s %s\n' \
fd7f4dfa8190e014013b9f08f3e973f4e4c26ffd179a204895533a7254813a70 \
/tmp/pip-26.2.2-py3-none-any.whl | sha256sum --check --strict -; \
"$VIRTUAL_ENV/bin/python" -I -m pip --isolated --disable-pip-version-check install \
--no-index --no-deps --upgrade /tmp/pip-26.2.2-py3-none-any.whl; \
rm /tmp/pip-26.2.2-py3-none-any.whl; \
"$VIRTUAL_ENV/bin/python" /usr/local/share/sample-app/verify-python-trust.py --pip-only; \
"$VIRTUAL_ENV/bin/pip" install \
--no-input \
--no-cache-dir \
Expand Down
13 changes: 8 additions & 5 deletions .devcontainer/docker/verify-image.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
set -euo pipefail

[[ "$(php -r 'echo PHP_MAJOR_VERSION.".".PHP_MINOR_VERSION;')" == "8.4" ]]
[[ "$(with-disposable-composer-state composer --no-ansi --version | awk '{print $3}' | cut -d. -f1)" == "2" ]]
[[ "$(node --version | sed -E 's/^v([0-9]+).*/\1/')" == "22" ]]
php /usr/local/share/sample-app/verify-php-trust.php "$(command -v composer)"
node /usr/local/share/sample-app/verify-node-trust.mjs
python -c 'import sys; assert sys.version_info >= (3, 10)'
python /usr/local/share/sample-app/verify-python-trust.py
python -c 'import durable_workflow'
python -m venv --help >/dev/null
rust_version="$(rustc --version | sed -E 's/^rustc ([0-9]+\.[0-9]+).*/\1/')"
[[ "$(printf '%s\n' 1.86 "$rust_version" | sort -V | head -n 1)" == "1.86" ]]
[[ "$(rustc --version | awk '{print $2}')" == "1.86.0" ]]
[[ "$(cargo --version | awk '{print $2}')" == "1.86.0" ]]
[[ "$(docker --version | sed -E 's/^Docker version ([^,]+),.*/\1/')" == "27.5.1" ]]
compose_version="$(docker compose version --short)"
[[ "${compose_version#v}" == "2.33.0" ]]
cli_version="${DURABLE_WORKFLOW_CLI_VERSION:-$(php -r '$tuple = json_decode(file_get_contents("/usr/local/share/sample-app/qualified-artifact-tuple.json"), true, flags: JSON_THROW_ON_ERROR); echo $tuple["artifacts"]["cli"];')}"
[[ -n "$cli_version" ]]
[[ "$(dw --version | awk '$1 == "dw" { print $2 }')" == "$cli_version" ]]
docker compose version >/dev/null

for extension in bcmath curl gd intl mbstring pcntl pdo_mysql pdo_sqlite redis zip; do
php --ri "$extension" >/dev/null
Expand Down
13 changes: 13 additions & 0 deletions .devcontainer/docker/verify-node-trust.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import assert from 'node:assert/strict';
import { X509Certificate } from 'node:crypto';
import { rootCertificates } from 'node:tls';

const [major, minor, patch] = process.versions.node.split('.').map(Number);
assert(major === 22 && (minor > 23 || (minor === 23 && patch >= 4)), 'Node 22.23.4 or newer on the 22 line is required');
const blocked = new Set(['CN=AAA Certificate Services', 'CN=emSign ECC Root CA - G3']);
assert(rootCertificates.length > 0, 'Node root store is empty');
for (const pem of rootCertificates) {
const subject = new X509Certificate(pem).subject.split('\n');
assert(!subject.some(component => blocked.has(component)), 'Blocked certificate in Node embedded trust');
}
console.log(`Verified Node ${process.versions.node} embedded CA roots`);
34 changes: 34 additions & 0 deletions .devcontainer/docker/verify-php-trust.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

Phar::loadPhar($argv[1]);
require 'phar://composer.phar/vendor/autoload.php';

$version = Composer\Composer::VERSION;
if (!str_starts_with($version, '2.') || version_compare($version, '2.10.3', '<')) {
throw new RuntimeException('Composer 2.10.3 or newer on the 2 line is required.');
}

$paths = [
'/etc/ssl/certs/ca-certificates.crt',
getenv('SSL_CERT_FILE') ?: ini_get('openssl.cafile') ?: openssl_get_cert_locations()['default_cert_file'],
getenv('COMPOSER_CAFILE') ?: Composer\CaBundle\CaBundle::getSystemCaRootBundlePath(),
];
foreach (array_unique($paths) as $path) {
$bundle = file_get_contents($path);
if ($bundle === false || !preg_match_all('/-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----/s', $bundle, $certificates)) {
throw new RuntimeException('Cannot inspect effective CA bundle: '.$path);
}
foreach ($certificates[0] as $certificate) {
$parsed = openssl_x509_parse($certificate);
if ($parsed === false) {
throw new RuntimeException('Invalid certificate in effective CA bundle: '.$path);
}
$subject = $parsed['subject']['CN'] ?? '';
if (in_array($subject, ['AAA Certificate Services', 'emSign ECC Root CA - G3'], true)) {
throw new RuntimeException('Blocked certificate in effective CA bundle: '.$path);
}
}
echo 'Verified PHP/Composer CA bundle: '.$path.PHP_EOL;
}
33 changes: 33 additions & 0 deletions .devcontainer/docker/verify-python-trust.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
"""Check the trust stores actually used by the prepared Python runtime."""

import ssl
import sys

import pip
from pip._vendor import certifi as pip_certifi


def verify_bundle(path):
context = ssl.create_default_context(cafile=path)
names = {
value
for certificate in context.get_ca_certs()
for component in certificate["subject"]
for key, value in component
if key == "commonName"
}
if not names or {"AAA Certificate Services", "emSign ECC Root CA - G3"} & names:
raise RuntimeError(f"Unqualified CA trust bundle: {path}")
print(f"Verified Python CA bundle: {path}")


if tuple(int(part) for part in pip.__version__.split(".")) < (26, 2, 2):
raise RuntimeError(f"Patched pip 26.2.2 or newer is required, found {pip.__version__}")

verify_bundle(ssl.get_default_verify_paths().cafile)
verify_bundle(pip_certifi.where())
if "--pip-only" not in sys.argv:
import certifi

verify_bundle(certifi.where())
41 changes: 39 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,33 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: '3.13'
- name: Select authenticated dependency trust
uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust
with:
clients: pip,python,composer,curl,git,npm

- name: Set up PHP
uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # v2.40.0
with:
php-version: ${{ matrix.php }}
extensions: mbstring, intl, pdo_mysql, redis, bcmath, gd, zip
coverage: none
tools: composer:v2
tools: composer:2.10.3

- name: Verify actual Composer trust before dependency requests
uses: durable-workflow/.github/.github/actions/verify-composer-trust@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust

- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-interaction

- name: Verify the playground Composer configuration
uses: durable-workflow/.github/.github/actions/verify-composer-trust@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust
with:
working-directory: playground/php-runtime

- name: Validate packages and stable artifact tuple
run: |
composer validate --strict --no-check-all
Expand Down Expand Up @@ -90,6 +106,12 @@ jobs:
} >> "$GITHUB_ENV"
fi

- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '22.23.4'
- name: Verify Node before running application tooling
run: node docker/verify-node-trust.mjs

- name: Run application tests
env:
DB_CONNECTION: mysql
Expand Down Expand Up @@ -133,13 +155,26 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: '3.13'
- name: Select authenticated dependency trust
uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust
with:
clients: pip,python,composer,curl,git

- name: Set up PHP
uses: shivammathur/setup-php@eb7c497e18156a6bbabfef1d3a82760b9eda3962 # v2.40.0
with:
php-version: ${{ matrix.php }}
extensions: mbstring, intl, pdo_mysql, bcmath, gd, zip
coverage: none
tools: composer:v2
tools: composer:2.10.3

- name: Verify actual Composer trust before dependency requests
uses: durable-workflow/.github/.github/actions/verify-composer-trust@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust
with:
working-directory: microservice

- name: Install and test
env:
Expand Down Expand Up @@ -177,6 +212,8 @@ jobs:
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
with:
python-version: '3.13'
- name: Bootstrap patched pip trust
uses: durable-workflow/.github/.github/actions/bootstrap-patched-pip@75587fc33ff40c34f32ef777293dad56bb6f48c7 # clean-trust
- run: python -m pip install PyYAML==6.0.2
- name: Require centrally approved immutable action commits
run: >-
Expand Down
47 changes: 42 additions & 5 deletions .github/workflows/smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,41 @@ permissions:

jobs:
compose:
name: docker compose sample workflows
runs-on: ubuntu-latest
name: docker compose sample workflows (${{ matrix.platform }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
platform: linux/amd64
- runner: ubuntu-24.04-arm
platform: linux/arm64
env:
COMPOSE_PROJECT_NAME: sample-app-smoke-${{ github.run_id }}-${{ github.run_attempt }}
COMPOSE_PROJECT_NAME: sample-app-smoke-${{ strategy.job-index }}-${{ github.run_id }}-${{ github.run_attempt }}
APP_PORT: '0'
SAMPLE_APP_COMMIT: ${{ github.sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Build and start stack
run: docker compose up -d --build --wait app worker
- name: Build the production input without a prior cache
run: |
test "$(git rev-parse HEAD)" = "$SAMPLE_APP_COMMIT"
docker compose build --pull --no-cache app
- name: Start the qualified build
run: docker compose up -d --wait app worker
- name: Verify the copied runtime and browser
env:
EXPECTED_PLATFORM: ${{ matrix.platform }}
run: |
image_id="$(docker compose images -q app)"
test "$(docker image inspect "$image_id" --format '{{.Os}}/{{.Architecture}}')" = "$EXPECTED_PLATFORM"
test "$(docker image inspect "$image_id" --format '{{index .Config.Labels "org.opencontainers.image.revision"}}')" = "$SAMPLE_APP_COMMIT"
docker image inspect "$image_id" --format 'image={{.Id}} platform={{.Os}}/{{.Architecture}} revision={{index .Config.Labels "org.opencontainers.image.revision"}}'
docker compose exec -T app node docker/verify-node-trust.mjs
docker compose exec -T app node --test tests/Node/ProductionNodeTrustTest.mjs
docker compose exec -T app php /usr/local/share/sample-app/verify-runtime-trust.php /usr/bin/composer
docker compose exec -T app node docker/playwright-smoke.js
- name: Run embedded workflow samples
run: scripts/compose-smoke.sh
- name: Dump logs
Expand All @@ -30,3 +55,15 @@ jobs:
- name: Tear down stack
if: always()
run: docker compose down -v --remove-orphans

qualify:
name: docker compose sample workflows
needs: [compose]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- name: Require both native image jobs
env:
IMAGE_RESULT: ${{ needs.compose.result }}
run: test "$IMAGE_RESULT" = success
Loading
Loading