diff --git a/.github/workflows/citrix-update.yml b/.github/workflows/citrix-update.yml new file mode 100644 index 00000000..2c4cea47 --- /dev/null +++ b/.github/workflows/citrix-update.yml @@ -0,0 +1,81 @@ +# SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +# +# SPDX-License-Identifier: CC0-1.0 +name: Citrix Update Check +on: # yamllint disable-line rule:truthy + schedule: + - cron: '0 8 * * *' + workflow_dispatch: +permissions: + contents: write + pull-requests: write +jobs: + check-citrix: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Fetch Citrix release info + id: citrix + run: | + page=$(curl -sL "https://www.citrix.com/downloads/workspace-app/linux/workspace-app-for-linux-latest.html") + + version=$(printf '%s' "$page" | grep -oP 'icaclient_\K[0-9.]+(?=_amd64\.deb)' | head -n 1) + raw_date=$(printf '%s' "$page" | grep -oP 'Release Date: \K[A-Za-z]{3} [0-9]{1,2}, [0-9]{4}' | head -n 1) + + if [[ -z "$version" || -z "$raw_date" ]]; then + echo "Could not retrieve Citrix version or release date" >&2 + exit 1 + fi + + release_date=$(LC_TIME=C date -d "$raw_date" +"%Y-%m-%d") + release_epoch=$(LC_TIME=C date -d "$release_date" +%s) + cutoff_epoch=$(LC_TIME=C date -d "-7 days" +%s) + + echo "version=$version" >> "$GITHUB_OUTPUT" + echo "release_date=$release_date" >> "$GITHUB_OUTPUT" + + if [[ "$release_epoch" -le "$cutoff_epoch" ]]; then + echo "old_enough=true" >> "$GITHUB_OUTPUT" + else + echo "old_enough=false" >> "$GITHUB_OUTPUT" + echo "Release $version ($release_date) is less than 7 days old — skipping" + fi + + - name: Check pinned version + if: steps.citrix.outputs.old_enough == 'true' + id: pinned + run: | + current=$(grep -oP 'set -g __citrix_version "\K[^"]+' dot/.config/fish/functions/devbase-citrix.fish) + echo "version=$current" >> "$GITHUB_OUTPUT" + + - name: Open PR if version changed + if: > + steps.citrix.outputs.old_enough == 'true' && + steps.citrix.outputs.version != steps.pinned.outputs.version + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NEW_VERSION: ${{ steps.citrix.outputs.version }} + RELEASE_DATE: ${{ steps.citrix.outputs.release_date }} + run: | + branch="citrix-update/${NEW_VERSION}" + + if gh pr list --head "$branch" --json number --jq '.[0].number' | grep -q .; then + echo "PR for $NEW_VERSION already exists — skipping" + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + + sed -i "s/set -g __citrix_version \"[^\"]*\"/set -g __citrix_version \"${NEW_VERSION}\"/" \ + dot/.config/fish/functions/devbase-citrix.fish + + git add dot/.config/fish/functions/devbase-citrix.fish + git commit -m "build(deps): update citrix-workspace to ${NEW_VERSION}" + git push origin "$branch" + + gh pr create \ + --title "build(deps): update citrix-workspace to ${NEW_VERSION}" \ + --body "Citrix Workspace App ${NEW_VERSION} released on ${RELEASE_DATE} (>= 7 days old)." diff --git a/.github/workflows/oc-update.yml b/.github/workflows/oc-update.yml new file mode 100644 index 00000000..167080d1 --- /dev/null +++ b/.github/workflows/oc-update.yml @@ -0,0 +1,89 @@ +# SPDX-FileCopyrightText: 2025 Digg - Agency for Digital Government +# +# SPDX-License-Identifier: CC0-1.0 +name: OpenShift OC Update Check +on: # yamllint disable-line rule:truthy + schedule: + - cron: '0 8 * * *' + workflow_dispatch: +permissions: + contents: write + pull-requests: write +jobs: + check-oc: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Fetch OC release info + id: oc + run: | + latest=$(curl -s "https://mirror.openshift.com/pub/openshift-v4/clients/ocp/" \ + | grep -oP 'href="\K[0-9]+\.[0-9]+\.[0-9]+(?=/)' \ + | sort -V \ + | tail -n 1) + + if [[ -z "$latest" ]]; then + echo "Could not determine latest OC version" >&2 + exit 1 + fi + + created=$(curl -s "https://mirror.openshift.com/pub/openshift-v4/clients/ocp/${latest}/release.txt" \ + | grep -oP 'Created:\s+\K\S+') + + if [[ -z "$created" ]]; then + echo "Could not read release date from release.txt for ${latest}" >&2 + exit 1 + fi + + release_epoch=$(date -d "$created" +%s) + cutoff_epoch=$(date -d "-7 days" +%s) + release_date=$(date -d "$created" +"%Y-%m-%d") + + echo "version=$latest" >> "$GITHUB_OUTPUT" + echo "release_date=$release_date" >> "$GITHUB_OUTPUT" + + if [[ "$release_epoch" -le "$cutoff_epoch" ]]; then + echo "old_enough=true" >> "$GITHUB_OUTPUT" + else + echo "old_enough=false" >> "$GITHUB_OUTPUT" + echo "Release ${latest} (${release_date}) is less than 7 days old — skipping" + fi + + - name: Check pinned version + if: steps.oc.outputs.old_enough == 'true' + id: pinned + run: | + current=$(grep -oP 'oc: \{version: "\K[^"]+' dot/.config/devbase/packages.yaml) + echo "version=$current" >> "$GITHUB_OUTPUT" + + - name: Open PR if version changed + if: > + steps.oc.outputs.old_enough == 'true' && + steps.oc.outputs.version != steps.pinned.outputs.version + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NEW_VERSION: ${{ steps.oc.outputs.version }} + RELEASE_DATE: ${{ steps.oc.outputs.release_date }} + run: | + branch="oc-update/${NEW_VERSION}" + + if gh pr list --head "$branch" --json number --jq '.[0].number' | grep -q .; then + echo "PR for ${NEW_VERSION} already exists — skipping" + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + + sed -i "s/\(oc: {version: \"\)[^\"]*\"/\1${NEW_VERSION}\"/" \ + dot/.config/devbase/packages.yaml + + git add dot/.config/devbase/packages.yaml + git commit -m "build(deps): update openshift-oc to ${NEW_VERSION}" + git push origin "$branch" + + gh pr create \ + --title "build(deps): update openshift-oc to ${NEW_VERSION}" \ + --body "OpenShift OC ${NEW_VERSION} released on ${RELEASE_DATE} (>= 7 days old)." diff --git a/docs/security.md b/docs/security.md index 403ec8f7..b660f068 100644 --- a/docs/security.md +++ b/docs/security.md @@ -8,38 +8,38 @@ SPDX-License-Identifier: CC0-1.0 ## Security Overview Matrix -| Category | Feature | Status | Implementation | Details | -|----------|---------|--------|----------------|---------| -| **Audit & Monitoring** | Security tools | ✅ 8 tools | Gitleaks, Lynis, Syft, etc. | Pre-installed | -| | CI security scans | ✅ Enabled | Every PR | Gitleaks, shellcheck | -| | Git audit trail | ✅ Enabled | All config changes | PR approval required | -| **Automatic Updates** | APT security patches | ✅ Enabled | unattended-upgrades | Ubuntu/Debian security repos | -| | Development tools | ✅ Enabled | Renovate Bot (84 tools) | Weekly schedule, 7-day wait | -| | Virus definitions | ✅ Enabled | ClamAV freshclam | Continuous updates | -| **Code Safety** | Bash safety flags | ✅ 21/21 scripts | `set -uo pipefail` | Fail on errors | -| | Input validation | ✅ Enabled | Whitelisted inputs | No arbitrary execution | -| | Dangerous patterns | ✅ None found | `curl\|sh`, `rm -rf /`, `chmod 777` | Zero matches | -| **Credentials** | No hardcoded secrets | ✅ Verified | Gitleaks scans | Zero matches | -| | Password policy | ✅ Enabled | 12-char minimum (NIST) | SSH passphrase validation | -| | Secure permissions | ✅ Enabled | SSH 600/700, configs 600 | Enforced at creation | -| **Cryptography** | Secure SSH | ✅ Enabled | ED25519, ChaCha20-Poly1305 | Default configuration | -| | SSH key type | ✅ Strong | ED25519 (256-bit) | Modern standard | -| **Temp Files** | Temp cleanup | ✅ Enabled | devbase temp directories cleaned | Respects TMPDIR/XDG_RUNTIME_DIR | -| **Malware Protection** | ClamAV scanning | ✅ Enabled | Daily full system scan | Low-priority, scheduled | -| | Virus definitions | ✅ Auto-update | freshclam service | Continuous updates | -| **Network** | Firewall (UFW) | ✅ Enabled | Default on Linux | K3s rules auto-configured | -| | HTTPS-only downloads | ✅ Enforced | All package sources | Zero HTTP downloads | -| | Connection timeouts | ✅ Enabled | 30s connect, 90s max | Prevents hanging | -| | Proxy support | ✅ Available | HTTP_PROXY, HTTPS_PROXY | Corporate proxy ready | -| **Privileges** | User-level install | ✅ Default | No root for tools | Only sudo for system packages | -| | Minimal sudo | ✅ 79 operations | apt/dpkg/snap/systemctl | Documented usage | -| **Supply Chain** | Version pinning | ✅ Enabled | 84 tools in YAML/TOML | All versions locked, tracked in git | -| | Checksums | ✅ Enabled | Mise, OpenShift CLI, IntelliJ, VS Code | SHA256 verification | -| | Renovate updates | ✅ Enabled | 7-day stability period | Auto-PRs with CI validation | -| | Aqua SLSA attestation | ✅ Enabled | 50+ mise tools | Supply chain verification | -| **System Hardening** | File descriptors | ✅ 65,536 | /etc/security/limits.d/ | Build-optimized | -| | Process limits | ✅ 32,768 | PAM limits | Parallel builds | -| | Memory locking | ✅ Unlimited | Container support | VM/container ready | +| Category | Feature | Status | Implementation | Details | +|------------------------|-----------------------|-----------------|----------------------------------------|-------------------------------------| +| **Audit & Monitoring** | Security tools | ✅ 8 tools | Gitleaks, Lynis, Syft, etc. | Pre-installed | +| | CI security scans | ✅ Enabled | Every PR | Gitleaks, shellcheck | +| | Git audit trail | ✅ Enabled | All config changes | PR approval required | +| **Automatic Updates** | APT security patches | ✅ Enabled | unattended-upgrades | Ubuntu/Debian security repos | +| | Development tools | ✅ Enabled | Renovate Bot (84 tools) | Weekly schedule, 7-day wait | +| | Virus definitions | ✅ Enabled | ClamAV freshclam | Continuous updates | +| **Code Safety** | Bash safety flags | ✅ 21/21 scripts | `set -uo pipefail` | Fail on errors | +| | Input validation | ✅ Enabled | Whitelisted inputs | No arbitrary execution | +| | Dangerous patterns | ✅ None found | `curl\|sh`, `rm -rf /`, `chmod 777` | Zero matches | +| **Credentials** | No hardcoded secrets | ✅ Verified | Gitleaks scans | Zero matches | +| | Password policy | ✅ Enabled | 12-char minimum (NIST) | SSH passphrase validation | +| | Secure permissions | ✅ Enabled | SSH 600/700, configs 600 | Enforced at creation | +| **Cryptography** | Secure SSH | ✅ Enabled | ED25519, ChaCha20-Poly1305 | Default configuration | +| | SSH key type | ✅ Strong | ED25519 (256-bit) | Modern standard | +| **Temp Files** | Temp cleanup | ✅ Enabled | devbase temp directories cleaned | Respects TMPDIR/XDG_RUNTIME_DIR | +| **Malware Protection** | ClamAV scanning | ✅ Enabled | Daily full system scan | Low-priority, scheduled | +| | Virus definitions | ✅ Auto-update | freshclam service | Continuous updates | +| **Network** | Firewall (UFW) | ✅ Enabled | Default on Linux | K3s rules auto-configured | +| | HTTPS-only downloads | ✅ Enforced | All package sources | Zero HTTP downloads | +| | Connection timeouts | ✅ Enabled | 30s connect, 90s max | Prevents hanging | +| | Proxy support | ✅ Available | HTTP_PROXY, HTTPS_PROXY | Corporate proxy ready | +| **Privileges** | User-level install | ✅ Default | No root for tools | Only sudo for system packages | +| | Minimal sudo | ✅ 79 operations | apt/dpkg/snap/systemctl | Documented usage | +| **Supply Chain** | Version pinning | ✅ Enabled | 84 tools in YAML/TOML | All versions locked, tracked in git | +| | Checksums | ✅ Enabled | Mise, OpenShift CLI, IntelliJ, VS Code | SHA256 verification | +| | Renovate updates | ✅ Enabled | 7-day stability period | Auto-PRs with CI validation | +| | Aqua SLSA attestation | ✅ Enabled | 50+ mise tools | Supply chain verification | +| **System Hardening** | File descriptors | ✅ 65,536 | /etc/security/limits.d/ | Build-optimized | +| | Process limits | ✅ 32,768 | PAM limits | Parallel builds | +| | Memory locking | ✅ Unlimited | Container support | VM/container ready | **Legend**: ✅ Implemented, ⚠️ Known Issue, ❌ Not Implemented @@ -72,19 +72,18 @@ SPDX-License-Identifier: CC0-1.0 #### Git Clones - Version-pinned: `git clone --branch "4.4.5" ...` -- Commit hashes: `git checkout "803bc18"` ## Downloads & Checksums -| Tool | Checksum | Status | -|------|----------|--------| -| Mise | SHA256 vendor file | ✅ | -| OpenShift CLI | `sha256sum.txt` | ✅ | -| IntelliJ IDEA | `.sha256` file | ✅ | -| VS Code | Microsoft API | ✅ | -| JMC | None | HTTPS only | -| DBeaver | None | HTTPS + dpkg | -| KeyStore Explorer | None | HTTPS + dpkg | +| Tool | Checksum | Status | +|-------------------|--------------------|--------------| +| Mise | SHA256 vendor file | ✅ | +| OpenShift CLI | `sha256sum.txt` | ✅ | +| IntelliJ IDEA | `.sha256` file | ✅ | +| VS Code | Microsoft API | ✅ | +| JMC | None | HTTPS only | +| DBeaver | None | HTTPS + dpkg | +| KeyStore Explorer | None | HTTPS + dpkg | ## SSH Security @@ -233,14 +232,14 @@ sudo ufw allow 8080/tcp comment 'custom app' ## File Permissions -| Resource | Permission | -|----------|-----------| -| SSH private keys (ED25519) | 600 | -| SSH public keys | 644 | -| SSH directory | 700 | -| SSH config | 600 | -| Passphrase temp files | 600 (deleted after use) | -| Cache directory | 700 | +| Resource | Permission | +|----------------------------|-------------------------| +| SSH private keys (ED25519) | 600 | +| SSH public keys | 644 | +| SSH directory | 700 | +| SSH config | 600 | +| Passphrase temp files | 600 (deleted after use) | +| Cache directory | 700 | **Sudo Usage**: 79 operations total @@ -485,6 +484,51 @@ sudo unattended-upgrade --dry-run --debug **Configuration Location**: `renovate.json` (extends base config) +### Release Age Coverage per Datasource + +`minimumReleaseAge: "7 days"` is set at the top level in `renovate.json` and applies to all datasources that expose a release timestamp. Some datasources cannot provide timestamps — those have explicit compensating controls. + +#### Not tracked by Renovate + +These package sources have no `# renovate:` annotations and are never touched by Renovate. They install whatever version is current when `setup.sh` runs. + +| Source | Packages | +|-----------|-----------------------------| +| `snap` | chromium, ghostty, microk8s | +| `flatpak` | org.chromium.Chromium | + +#### Tracked — `minimumReleaseAge` enforced + +| Datasource | Used for | +|--------------------|------------------------------------------------| +| `github-releases` | Most mise tools (ripgrep, lazyvim, gh, k9s, …) | +| `gitlab-releases` | glab | +| `forgejo-releases` | gommitlint | +| `npm` | tree-sitter-cli, all VS Code extensions | +| `maven` | Maven | + +#### Tracked — timestamp support uncertain (language runtimes) + +These datasources fetch from each language's own versioning API. Whether Renovate surfaces a `releaseTimestamp` depends on the upstream API. In practice, language runtime releases go through months of public RC testing before appearing as stable, so the supply-chain risk is lower than for registry packages. + +| Datasource | Used for | +|----------------------|----------------| +| `python-version` | Python | +| `node-version` | Node.js | +| `go-version` | Go | +| `ruby-version` | Ruby | +| `rust-version` | Rust | +| `java-version` | Java (Temurin) | +| `gradle-version` | Gradle | +| `jetbrains-releases` | IntelliJ IDEA | + +#### Tracked — compensating control (run dailt) + +| Datasource | Reason | Control | +|-----------------------|---------------------------------------------------------------|----------------| +| `custom.citrix` | Release date is in a `` tag, unreachable by HTML parser | citrix-update | +| `custom.openshift-oc` | Mirror page returns empty `