Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e564a46
Merge branch 'main' into dev
d0ubl3g Oct 15, 2026
b9c4887
Run the proxy and TLS inspection test on macOS and Windows
d0ubl3g Oct 16, 2026
38d1aac
Write the test certificate's extensions to a file, which openssl on W…
d0ubl3g Oct 16, 2026
5c866e3
Hand python on Windows the scripts folder in a form it can open
d0ubl3g Oct 16, 2026
20e1d56
Merge pull request #194 from Hodeitek/ci/proxy-tls-macos-windows
d0ubl3g Oct 16, 2026
df423ad
Test that whoami labels a user with no name by the email
d0ubl3g Oct 17, 2026
87ed6fd
Document the Linux prerequisites to install and verify, and a checksu…
d0ubl3g Oct 17, 2026
a93d075
Hint at the CA certificates package when the system has none
d0ubl3g Oct 17, 2026
afb5a82
Document how to uninstall the archive and install.sh installs
d0ubl3g Oct 17, 2026
0748cc2
Point servers and CI from the Quick start to the API key
d0ubl3g Oct 17, 2026
3ba2c64
Make the README and config set help match the login option, config ke…
d0ubl3g Oct 17, 2026
625a0ab
Add the Windows steps to check a download and to get Scoop and cosign
d0ubl3g Oct 17, 2026
aa36aec
Document what is left after an uninstall on Windows and how to remove…
d0ubl3g Oct 17, 2026
65ca1cc
Say that login and logout exit 1 when the keychain is out of reach
d0ubl3g Oct 17, 2026
c0423d8
Format the CA hint and its test
d0ubl3g Oct 17, 2026
857d4db
Merge pull request #202 from Hodeitek/fix/beta-qa-pass-1
d0ubl3g Oct 17, 2026
e24c93a
Keep the values of environment variables out of --help
d0ubl3g Oct 17, 2026
35edc3b
Remove the settings before the binary on uninstall, and name the MSI …
d0ubl3g Oct 17, 2026
2dd416c
Merge pull request #203 from Hodeitek/fix/clap-env-values
d0ubl3g Oct 17, 2026
45347fd
Name the Applications screen the same way in login help and the sessi…
d0ubl3g Oct 17, 2026
cd5f793
Keep --api-url and --profile out of the usage line when only the envi…
d0ubl3g Oct 17, 2026
80d2a8f
Accept the zbus unmaintained notices until secret-service moves to zb…
d0ubl3g Oct 17, 2026
014010f
Merge pull request #204 from Hodeitek/fix/release-pass-0-6
d0ubl3g Oct 17, 2026
4e6b6be
Prepare 0.5.1: version and changelog
d0ubl3g Oct 18, 2026
cff019f
Point the Install section to the unblock steps, and give the Windows …
d0ubl3g Oct 18, 2026
176149e
Merge pull request #209 from Hodeitek/docs/blocked-binary-pointer
d0ubl3g Oct 19, 2026
11ed29f
Merge branch 'dev' into release/0.5.1-prep
d0ubl3g Oct 19, 2026
3df4e7b
Add the unblock-steps change to the 0.5.1 changelog
d0ubl3g Oct 19, 2026
277b9cb
Say that two profiles of the same user, tenant and flow share one sig…
d0ubl3g Oct 19, 2026
490a136
Merge pull request #211 from Hodeitek/docs/shared-sign-in-note
d0ubl3g Oct 19, 2026
6327d9b
Merge branch 'dev' into release/0.5.1-prep
d0ubl3g Oct 19, 2026
44fba33
Add the shared sign-in note to the 0.5.1 changelog
d0ubl3g Oct 19, 2026
95ca112
Merge pull request #205 from Hodeitek/release/0.5.1-prep
d0ubl3g Oct 19, 2026
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
89 changes: 69 additions & 20 deletions .github/scripts/proxy-tls-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,47 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2026 Hodeitek S.L.
#
# Proves the README's "Networking: proxies and TLS inspection" claims on Linux, end to end, with a
# real TLS-inspecting proxy (mitmproxy) and a fake API, all on this machine:
# Proves the README's "Networking: proxies and TLS inspection" claims on Linux, macOS and Windows,
# end to end, with a real TLS-inspecting proxy (mitmproxy) and a fake API, all on this machine:
#
# 1. HTTPS_PROXY set, proxy CA not trusted -> exit 7, "error: Could not reach the API:", certificate
# 2. proxy CA in the system store -> works, and the proxy saw the request
# 3. proxy CA only in SSL_CERT_FILE -> works
# 3. proxy CA only in SSL_CERT_FILE -> works on Linux; no effect on macOS and
# Windows (same failure as case 1)
# 4. NO_PROXY covers the host -> the proxy is not used
# 5. lower-case https_proxy -> works like HTTPS_PROXY
# 6. --verbose -> the request line goes to stderr only
#
# The API is reached as api.test:8443, mapped to 127.0.0.1 in /etc/hosts, because the CLI never uses
# The API is reached as api.test:8443, mapped to 127.0.0.1 in the hosts file, because the CLI never uses
# a proxy for a loopback URL. The fake API's certificate comes from one throwaway CA that only the
# proxy trusts; the proxy re-signs with another throwaway CA, the one the CLI must trust.
#
# This is meant to run in CI (GitHub Actions): it edits /etc/hosts and the system certificate store,
# and case 1 is a deliberate failure. It refuses to run elsewhere.
# The CA goes into the system store the way an administrator would do it: update-ca-certificates
# on Linux, `security add-trusted-cert` on macOS, Import-Certificate on Windows. On macOS and
# Windows the fake API's CA goes there too (case 4), since SSL_CERT_FILE is not read there.
#
# This is meant to run in CI (GitHub Actions): it edits the hosts file and the system certificate
# store, and case 1 is a deliberate failure. It refuses to run elsewhere. On Windows it runs in Git
# Bash, as an administrator.
#
# Usage: proxy-tls-test.sh PATH_TO_HODEISHIELD PATH_TO_MITMPROXY_DIR
set -euo pipefail

if [ "${GITHUB_ACTIONS:-}" != "true" ]; then
echo "proxy-tls-test.sh edits /etc/hosts and the system certificate store: it only runs in GitHub Actions." >&2
echo "proxy-tls-test.sh edits the hosts file and the system certificate store: it only runs in GitHub Actions." >&2
exit 2
fi
export MSYS_NO_PATHCONV=1 # Git Bash on Windows: keep arguments such as -subj /CN=... as they are
case "$(uname -s)" in
Linux) OS=linux ;;
Darwin) OS=macos ;;
MINGW* | MSYS* | CYGWIN*) OS=windows ;;
*) echo "unsupported system: $(uname -s)" >&2; exit 2 ;;
esac
BIN=$(realpath "${1:?usage: proxy-tls-test.sh HODEISHIELD MITMPROXY_DIR}")
MITM=$(realpath "${2:?usage: proxy-tls-test.sh HODEISHIELD MITMPROXY_DIR}")
SCRIPTS=$(cd "$(dirname "$0")" && pwd)
if [ "$OS" = windows ]; then SCRIPTS=$(cygpath -m "$SCRIPTS"); fi # handed to python.exe

HOST=api.test
API_PORT=8443
Expand All @@ -37,11 +51,12 @@ API_URL="https://$HOST:$API_PORT"
REQ_RE="GET https://$HOST:$API_PORT/v1/vendors"

WORK=$(mktemp -d)
if [ "$OS" = windows ]; then WORK=$(cygpath -m "$WORK"); fi # C:/... form, understood by native programs
PIDS=()
# shellcheck disable=SC2329 # invoked by the EXIT trap
cleanup() {
for pid in "${PIDS[@]}"; do kill "$pid" 2> /dev/null || true; done
rm -rf "$WORK"
for pid in ${PIDS[@]+"${PIDS[@]}"}; do kill "$pid" 2> /dev/null || true; done
rm -rf "$WORK" || true # on Windows a file can still be open for a moment
}
trap cleanup EXIT

Expand All @@ -56,19 +71,47 @@ make_ca proxy # the "corporate" CA the proxy re-signs with; what the CLI must

openssl req -newkey rsa:2048 -nodes -subj "/CN=$HOST" \
-keyout "$WORK/api.key" -out "$WORK/api.csr" 2> /dev/null
# A file, not <(...): a native openssl.exe on Windows cannot open /dev/fd paths.
printf 'subjectAltName=DNS:%s\nbasicConstraints=CA:FALSE\n' "$HOST" > "$WORK/api.ext"
openssl x509 -req -in "$WORK/api.csr" -CA "$WORK/upstream-ca.crt" -CAkey "$WORK/upstream-ca.key" \
-CAcreateserial -days 2 -out "$WORK/api.crt" \
-extfile <(printf 'subjectAltName=DNS:%s\nbasicConstraints=CA:FALSE\n' "$HOST") 2> /dev/null
-CAcreateserial -days 2 -out "$WORK/api.crt" -extfile "$WORK/api.ext" 2> /dev/null

mkdir "$WORK/confdir"
cat "$WORK/proxy-ca.key" "$WORK/proxy-ca.crt" > "$WORK/confdir/mitmproxy-ca.pem"
cp "$WORK/proxy-ca.crt" "$WORK/confdir/mitmproxy-ca-cert.pem"

# --- Name, fake API, proxy --------------------------------------------------------------------
echo "127.0.0.1 $HOST" | sudo tee -a /etc/hosts > /dev/null
SUDO=sudo
HOSTS_FILE=/etc/hosts
PY=python3
if [ "$OS" = windows ]; then
SUDO= # the runner account is already an administrator
HOSTS_FILE=/c/Windows/System32/drivers/etc/hosts
PY=python
fi
echo "127.0.0.1 $HOST" | $SUDO tee -a "$HOSTS_FILE" > /dev/null
if [ "$OS" = macos ]; then sudo dscacheutil -flushcache; fi
if [ "$OS" = windows ]; then ipconfig /flushdns > /dev/null; fi

# Puts a CA certificate (PEM) into the system store, the way an administrator would.
trust_ca() {
case "$OS" in
linux)
sudo cp "$1" "/usr/local/share/ca-certificates/hodeishield-test-$2.crt"
sudo update-ca-certificates > /dev/null
;;
macos)
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$1"
;;
windows)
powershell.exe -NoProfile -Command \
"Import-Certificate -FilePath '$1' -CertStoreLocation Cert:\\LocalMachine\\Root | Out-Null"
;;
esac
}

: > "$WORK/api.log"
python3 -I "$SCRIPTS/proxy-tls-fake-api.py" "$WORK/api.crt" "$WORK/api.key" "$API_PORT" "$WORK/api.log" &
"$PY" -I "$SCRIPTS/proxy-tls-fake-api.py" "$WORK/api.crt" "$WORK/api.key" "$API_PORT" "$WORK/api.log" &
PIDS+=($!)

PYTHONUNBUFFERED=1 "$MITM/mitmdump" --set "confdir=$WORK/confdir" \
Expand Down Expand Up @@ -148,16 +191,23 @@ else
fi

# --- Case 3: the proxy's CA only in SSL_CERT_FILE (run before it enters the system store) -----
p0=$(proxy_seen)
# Linux reads the variable; macOS and Windows use the system store only (README), so there it
# must fail like case 1.
p0=$(proxy_seen) a0=$(api_seen)
cli HTTPS_PROXY="$PROXY" SSL_CERT_FILE="$WORK/proxy-ca.crt" -- vendors list --json
if works && wait_proxy_above "$p0"; then
record 3 "proxy CA only in SSL_CERT_FILE: works through the proxy" PASS "exit $rc"
if [ "$OS" = linux ]; then
desc="proxy CA only in SSL_CERT_FILE: works through the proxy"
if works && wait_proxy_above "$p0"; then ok=1; else ok=0; fi
else
record 3 "proxy CA only in SSL_CERT_FILE: works through the proxy" FAIL "exit $rc"
show_failure 3
desc="proxy CA only in SSL_CERT_FILE: no effect here, exit 7, certificate error"
sleep 1
if [ "$rc" -eq 7 ] && [[ "$(cat "$WORK/err")" == "error: Could not reach the API:"* ]] \
&& [ "$(proxy_seen)" -eq "$p0" ] && [ "$(api_seen)" -eq "$a0" ]; then ok=1; else ok=0; fi
fi
if [ "$ok" -eq 1 ]; then record 3 "$desc" PASS "exit $rc"; else record 3 "$desc" FAIL "exit $rc"; show_failure 3; fi

# --- Case 4: NO_PROXY covers the host: direct, the proxy never sees it ------------------------
if [ "$OS" != linux ]; then trust_ca "$WORK/upstream-ca.crt" upstream; fi
p0=$(proxy_seen) a0=$(api_seen)
cli HTTPS_PROXY="$PROXY" NO_PROXY="$HOST" SSL_CERT_FILE="$WORK/upstream-ca.crt" -- vendors list --json
sleep 1
Expand All @@ -169,8 +219,7 @@ else
fi

# --- Case 2: the proxy's CA in the system store ------------------------------------------------
sudo cp "$WORK/proxy-ca.crt" /usr/local/share/ca-certificates/hodeishield-test-proxy-ca.crt
sudo update-ca-certificates > /dev/null
trust_ca "$WORK/proxy-ca.crt" proxy
p0=$(proxy_seen)
cli HTTPS_PROXY="$PROXY" -- vendors list --json
if works && wait_proxy_above "$p0"; then
Expand Down
69 changes: 52 additions & 17 deletions .github/workflows/proxy-tls.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: Proxy and TLS inspection

# Checks the README's "Networking: proxies and TLS inspection" claims on Linux against a real
# TLS-inspecting proxy (mitmproxy) and a fake API, both on the runner, with throwaway certificates:
# Checks the README's "Networking: proxies and TLS inspection" claims on Linux, macOS and Windows
# against a real TLS-inspecting proxy (mitmproxy) and a fake API, both on the runner, with
# throwaway certificates:
# nothing secret is used and nothing leaves the machine except the download of mitmproxy. Case 1
# is a deliberate certificate failure, so the script is only meant to run here (see
# .github/scripts/proxy-tls-test.sh).
Expand Down Expand Up @@ -34,19 +35,42 @@ env:

jobs:
proxy-tls:
name: proxy-tls
runs-on: ubuntu-latest
timeout-minutes: 15
name: proxy-tls (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 20
permissions:
contents: read
strategy:
fail-fast: false
matrix:
# Official mitmproxy releases, pinned by version and checksum. Bumped by hand.
# The download site publishes no checksum file, so each SHA-256 is the one of the
# release archive as downloaded when it was pinned (trust on first use). mitmproxy
# is only a test tool here and is never shipped. When bumping, record the new
# archives' SHA-256 the same way.
include:
- os: ubuntu-latest
mitm_archive: mitmproxy-12.2.3-linux-x86_64.tar.gz
mitm_sha256: 2e95286b618fa6fd33e5e62a78c2e5112571d85f42ec2bac29b97ee242bdb5c5
mitm_dir: mitmproxy
bin: target/debug/hodeishield
- os: macos-latest
mitm_archive: mitmproxy-12.2.3-macos-arm64.tar.gz
mitm_sha256: 0a09ee3b82569e8985aff8186e4792618b8e5d0c766098db093d09a87d4b013a
mitm_dir: mitmproxy/mitmproxy.app/Contents/MacOS
bin: target/debug/hodeishield
- os: windows-latest
mitm_archive: mitmproxy-12.2.3-windows-x86_64.zip
mitm_sha256: 04a01ea95ae96df75058a893e774957d294e69012dab1f4e256ce2b0c6725483
mitm_dir: mitmproxy
bin: target/debug/hodeishield.exe
defaults:
run:
shell: bash
env:
# Official Linux release of mitmproxy, pinned by version and checksum. Bumped by hand.
# The download site publishes no checksum file, so the SHA-256 is the one of the
# release archive as downloaded when it was pinned (trust on first use). mitmproxy
# is only a test tool here and is never shipped. When bumping, record the new
# archive's SHA-256 the same way.
MITMPROXY_VERSION: "12.2.3"
MITMPROXY_SHA256: 2e95286b618fa6fd33e5e62a78c2e5112571d85f42ec2bac29b97ee242bdb5c5
MITM_ARCHIVE: ${{ matrix.mitm_archive }}
MITM_SHA256: ${{ matrix.mitm_sha256 }}
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -67,12 +91,23 @@ jobs:
- name: Install mitmproxy
run: |
set -euo pipefail
archive="mitmproxy-${MITMPROXY_VERSION}-linux-x86_64.tar.gz"
curl --proto '=https' --tlsv1.2 -fsSL -o "$RUNNER_TEMP/$archive" \
"https://downloads.mitmproxy.org/${MITMPROXY_VERSION}/${archive}"
echo "${MITMPROXY_SHA256} $RUNNER_TEMP/$archive" | sha256sum -c -
curl --proto '=https' --tlsv1.2 -fsSL -o "$RUNNER_TEMP/$MITM_ARCHIVE" \
"https://downloads.mitmproxy.org/${MITMPROXY_VERSION}/${MITM_ARCHIVE}"
if command -v sha256sum > /dev/null; then sum=sha256sum; else sum="shasum -a 256"; fi
echo "${MITM_SHA256} $RUNNER_TEMP/$MITM_ARCHIVE" | $sum -c -
mkdir "$RUNNER_TEMP/mitmproxy"
tar -xzf "$RUNNER_TEMP/$archive" -C "$RUNNER_TEMP/mitmproxy"
case "$MITM_ARCHIVE" in
*.zip) 7z x -y "-o$RUNNER_TEMP/mitmproxy" "$RUNNER_TEMP/$MITM_ARCHIVE" > /dev/null ;;
*) tar -xzf "$RUNNER_TEMP/$MITM_ARCHIVE" -C "$RUNNER_TEMP/mitmproxy" ;;
esac

# macOS ships LibreSSL as `openssl`; the script needs OpenSSL 3 (req -addext).
- name: Use Homebrew OpenSSL on macOS
if: runner.os == 'macOS'
run: echo "$(brew --prefix openssl@3)/bin" >> "$GITHUB_PATH"

- name: Run the proxy cases
run: bash .github/scripts/proxy-tls-test.sh target/debug/hodeishield "$RUNNER_TEMP/mitmproxy"
env:
BIN: ${{ matrix.bin }}
MITM_DIR: ${{ matrix.mitm_dir }}
run: bash .github/scripts/proxy-tls-test.sh "$BIN" "${RUNNER_TEMP//\\//}/$MITM_DIR"
47 changes: 46 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,50 @@ commands, `--json` output and exit codes. Each release's notes start with its se

## [Unreleased]

## [0.5.1] - 2026-10-18

### Changed

- When the system has no CA certificates, the error for a request that cannot start or reach the API
now suggests installing the system CA certificates package (for example `ca-certificates`). The
HTTP client reports that case only as "No CA certificates were loaded from the system".
([#196](https://github.com/Hodeitek/hodeishield-cli/issues/196))
- `--help` no longer prints the values of `HODEISHIELD_API_URL` and `HODEISHIELD_PROFILE` when they
are set in the environment.
- The `login` help, the session notice and the README name the screen where an app's access is
revoked "Account → Applications", as the web app does, and the README and `config set --help`
match the code on `login --no-browser` and the configuration keys.
([#199](https://github.com/Hodeitek/hodeishield-cli/issues/199))
- CI now runs the proxy and TLS inspection test on macOS and Windows as well as Linux.

### Fixed

- Usage errors no longer list `--api-url` or `--profile` in the `Usage:` line when only
`HODEISHIELD_API_URL` or `HODEISHIELD_PROFILE` sets them. The variables still work the same and
`--help` still names them.
([#201](https://github.com/Hodeitek/hodeishield-cli/issues/201))

### Documentation

- The README lists the Linux prerequisites to install and to verify a download, gives a checksum
command that BusyBox also runs, and has Windows steps to check a download and to get Scoop and
cosign.
([#195](https://github.com/Hodeitek/hodeishield-cli/issues/195))
- The README says how to uninstall the archive and `install.sh` installs, in which order (remove the
settings before the binary), what an uninstall leaves behind on Windows, how to remove the zip
and how to check the MSI.
([#197](https://github.com/Hodeitek/hodeishield-cli/issues/197))
- The Quick start points servers and CI from the interactive sign-in to the tenant API key.
([#198](https://github.com/Hodeitek/hodeishield-cli/issues/198))
- The README says that `login` and `logout` exit with code 1 when the system keychain cannot be
reached.
([#200](https://github.com/Hodeitek/hodeishield-cli/issues/200))
- The Install section links to the steps for a binary that macOS or Windows blocks, and the Windows
steps (`Unblock-File`, SmartScreen's "More info → Run anyway") apply to every version.
([#16](https://github.com/Hodeitek/hodeishield-cli/issues/16))
- The README says that two profiles signed in as the same user, to the same tenant, with the same
flow share one sign-in, so logging out of either one signs out both.

## [0.5.0] - 2026-10-15

### Added
Expand Down Expand Up @@ -381,7 +425,8 @@ commands, `--json` output and exit codes. Each release's notes start with its se
- Profiles (`config`), shell completions, and release archives for Linux, macOS and Windows signed
with Sigstore and with SLSA provenance.

[Unreleased]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.5.0...HEAD
[Unreleased]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.5.1...HEAD
[0.5.1]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.5.0...v0.5.1
[0.5.0]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.4.4...v0.5.0
[0.4.4]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.4.3...v0.4.4
[0.4.3]: https://github.com/Hodeitek/hodeishield-cli/compare/v0.4.2...v0.4.3
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ default-members = ["crates/hodeishield-api", "crates/hodeishield-cli"]

[workspace.package]
authors = ["Hodeitek S.L. <info@hodeitek.com>"]
version = "0.5.0"
version = "0.5.1"
edition = "2024"
rust-version = "1.98"
license = "Apache-2.0"
Expand Down
Loading
Loading