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
50 changes: 37 additions & 13 deletions package-firewall/bash/generate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,17 @@ SHARED_BLOCKS_DIR="$SCRIPT_DIR/../shared/blocks"
FQDN="${ENDOR_FQDN:-https://factory.endorlabs.com}"

# ─── Compute derived values ────────────────────────────────────────────────────
# Only machine-independent values are derived here. Attribution values
# (<console-user>@<machine>) are computed at install time — see credentials_block.
FQDN_HOST="${FQDN#https://}"
FQDN_HOST="${FQDN_HOST#http://}"
TRUSTED_HOST="${FQDN_HOST%%:*}"

NPM_REGISTRY_URL="${FQDN}/v1/namespaces/${ENDOR_NAMESPACE}/firewall/npm/"
NPM_REGISTRY_HOST="${FQDN_HOST}/v1/namespaces/${ENDOR_NAMESPACE}/firewall/npm/"
NPM_AUTH_B64=$(printf '%s' "${ENDOR_API_KEY_ID}:${ENDOR_API_SECRET}" | base64 | tr -d '\n')
API_SECRET_B64=$(printf '%s' "${ENDOR_API_SECRET}" | base64 | tr -d '\n')

PYPI_URL="${FQDN}/v1/namespaces/${ENDOR_NAMESPACE}/firewall/pypi/simple/"
PIP_INDEX_URL="https://${ENDOR_API_KEY_ID}:${ENDOR_API_SECRET}@${FQDN_HOST}/v1/namespaces/${ENDOR_NAMESPACE}/firewall/pypi/simple/"

GO_PROXY_URL="https://${ENDOR_API_KEY_ID}:${ENDOR_API_SECRET}@${FQDN_HOST}/v1/namespaces/${ENDOR_NAMESPACE}/firewall/go/,direct"
MAVEN_REGISTRY_URL="${FQDN}/v1/namespaces/${ENDOR_NAMESPACE}/firewall/maven/"
API_SECRET_B64=$(printf '%s' "${ENDOR_API_SECRET}" | base64 | tr -d '\n')

# ─── Output directory ─────────────────────────────────────────────────────────
OUT_DIR="${SCRIPT_DIR}/out/${ENDOR_NAMESPACE}"
Expand All @@ -74,16 +71,13 @@ substitute() {
-e "s|{{NAMESPACE}}|${ENDOR_NAMESPACE}|g" \
-e "s|{{API_KEY_ID}}|${ENDOR_API_KEY_ID}|g" \
-e "s|{{API_SECRET}}|${ENDOR_API_SECRET}|g" \
-e "s|{{API_SECRET_B64}}|${API_SECRET_B64}|g" \
-e "s|{{FQDN}}|${FQDN}|g" \
-e "s|{{FQDN_HOST}}|${FQDN_HOST}|g" \
-e "s|{{NPM_REGISTRY_URL}}|${NPM_REGISTRY_URL}|g" \
-e "s|{{NPM_REGISTRY_HOST}}|${NPM_REGISTRY_HOST}|g" \
-e "s|{{NPM_AUTH_B64}}|${NPM_AUTH_B64}|g" \
-e "s|{{API_SECRET_B64}}|${API_SECRET_B64}|g" \
-e "s|{{PYPI_URL}}|${PYPI_URL}|g" \
-e "s|{{PIP_INDEX_URL}}|${PIP_INDEX_URL}|g" \
-e "s|{{ENDOR_PYPI_URL}}|${PIP_INDEX_URL}|g" \
-e "s|{{TRUSTED_HOST}}|${TRUSTED_HOST}|g" \
-e "s|{{GO_PROXY_URL}}|${GO_PROXY_URL}|g" \
-e "s|{{MAVEN_REGISTRY_URL}}|${MAVEN_REGISTRY_URL}|g"
}

Expand Down Expand Up @@ -111,7 +105,8 @@ emit_block_assignment() {
# emit_all_blocks
# Emits all block variable assignments into the generated script.
# Edit shared/blocks/*.txt to change shared config content.
# Edit templates/envsh.txt to change the bash-only env var block.
# Attribution {{...}} tokens are not substituted here — the generated scripts
# fill them at install time (see credentials_block).
emit_all_blocks() {
echo "# ── Block content (from shared/blocks/) ─────────────────────────────────────"
emit_block_assignment "ENVSH_BLOCK" "$SHARED_BLOCKS_DIR/envsh.txt"
Expand Down Expand Up @@ -162,6 +157,31 @@ USER_GROUP=$(id -gn "$CONSOLE_USER" 2>/dev/null || echo "staff")
USERBLOCK
}

# credentials_block — user-attribution values, computed on the developer's
# machine at install time (the label <console-user>@<machine> doesn't exist at
# generation time). {{...}} tokens are substituted at generation time.
credentials_block() {
substitute << 'CREDBLOCK'
# ── User attribution (computed at install time) ───────────────────────────────
ENDOR_API_KEY_ID='{{API_KEY_ID}}'
ENDOR_API_SECRET='{{API_SECRET}}'

ENDOR_ATTR_LABEL="${CONSOLE_USER}@$(endor_host_label)"
ENDOR_ATTR_USER="$(endor_attr_username "$ENDOR_ATTR_LABEL" "$ENDOR_API_KEY_ID")"

# npm _auth = base64(username:password)
ENDOR_AUTH_B64="$(printf '%s:%s' "$ENDOR_ATTR_USER" "$ENDOR_API_SECRET" | endor_b64)"

# pip / uv / go embed the username in URL userinfo — percent-encode it.
ENDOR_PYPI_URL="https://$(endor_urlenc_b64 "$ENDOR_ATTR_USER"):${ENDOR_API_SECRET}@{{FQDN_HOST}}/v1/namespaces/{{NAMESPACE}}/firewall/pypi/simple/"
ENDOR_GO_PROXY_URL="https://$(endor_urlenc_b64 "$ENDOR_ATTR_USER"):${ENDOR_API_SECRET}@{{FQDN_HOST}}/v1/namespaces/{{NAMESPACE}}/firewall/go/,direct"

export ENDOR_ATTR_LABEL ENDOR_ATTR_USER ENDOR_AUTH_B64 ENDOR_PYPI_URL ENDOR_GO_PROXY_URL

echo "[endor] user attribution → ${ENDOR_ATTR_LABEL}"
CREDBLOCK
}

# script_header <output> <description>
script_header() {
local output="$1"
Expand Down Expand Up @@ -192,6 +212,8 @@ build_script() {

{
script_header "$output" "$description"
credentials_block
echo ""
emit_all_blocks
echo "# ════════════════════════════════════════════════════════════════════════════"
echo "# Env setup"
Expand Down Expand Up @@ -247,6 +269,8 @@ build_remove_script "$OUT_DIR/endor-remove.sh"
{
script_header "$OUT_DIR/endor-all.sh" \
"Configures all package managers for Endor Package Firewall. Covers: npm · pnpm · yarn classic · yarn 2+ · bun · pip · uv · poetry · go · maven"
credentials_block
echo ""
emit_all_blocks
echo "# ════════════════════════════════════════════════════════════════════════════"
echo "# Env setup"
Expand Down Expand Up @@ -295,7 +319,7 @@ echo " All scripts accept --dry-run to preview changes without writing anythin
echo " Upload to your MDM tool. Each script is self-contained and idempotent."
echo ""
echo " To customise: edit shared/blocks/*.txt (shared config content)"
echo " or templates/envsh.txt (bash env var block)"
echo " or credentials_block in generate.sh (user-attribution values)"
echo " or templates/*.sh (orchestration logic)"
echo ""
echo " Re-running overwrites the same output directory."
32 changes: 32 additions & 0 deletions package-firewall/bash/lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,38 @@ ENDOR_BLOCK_END="# ===== END ENDOR PACKAGE FIREWALL ====="
ENDOR_XML_BLOCK_START="<!-- ===== BEGIN ENDOR PACKAGE FIREWALL (managed — do not edit) ===== -->"
ENDOR_XML_BLOCK_END="<!-- ===== END ENDOR PACKAGE FIREWALL ===== -->"

# ── User attribution helpers ──────────────────────────────────────────────────
# Encode <console-user>@<machine> into the Basic-auth username. The firewall
# decodes the label, auths with the real API key, and logs it as "User".

# endor_b64 — portable base64, no line wrapping (GNU wraps at 76 cols; BSD doesn't).
endor_b64() {
if base64 --help 2>&1 | grep -q -- '-w'; then
base64 -w0
else
base64 | tr -d '\n'
fi
}

# endor_urlenc_b64 <b64> — percent-encode base64 chars (+ / =) for URL userinfo.
endor_urlenc_b64() {
printf '%s' "$1" | sed -e 's/+/%2B/g' -e 's#/#%2F#g' -e 's/=/%3D/g'
}

# endor_host_label — a stable, human-readable machine name for attribution.
endor_host_label() {
scutil --get ComputerName 2>/dev/null || hostname 2>/dev/null || echo unknown
}

# endor_attr_username <label> <api_key_id>
# Returns base64(base64("userattr:"+label)+":"+keyId) — the format
# decodeAttributedUsername() expects in endorfactory's auth layer.
endor_attr_username() {
local label="$1" key_id="$2" inner
inner=$(printf '%s' "userattr:${label}" | endor_b64)
printf '%s:%s' "$inner" "$key_id" | endor_b64
}

# detect_console_user
# MDM tools (Kandji, Jamf) run scripts as root. $HOME resolves to /var/root, which
# is not where developer config files live. Returns the name of the actual logged-in
Expand Down
24 changes: 20 additions & 4 deletions package-firewall/bash/templates/envsh.sh
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
# templates/envsh.sh
# Writes ~/.config/endor/env.sh — the single credential source for all
# env-var-based tools (npm, uv, yarn 2+, poetry; Go in future).
# env-var-based tools (npm, yarn 2+, maven, poetry).
# Then adds a one-line source directive to existing shell profiles.
#
# Block content is defined in templates/blocks/envsh.txt.
# pip is intentionally excluded — pip.conf does not support env var expansion,
# so pip credentials are written as literal values in python.sh instead.
# Block content comes from shared/blocks/envsh.txt. The attribution tokens
# ({{ATTR_USER}}, {{NPM_AUTH_B64}}, {{PIP_INDEX_URL}}, {{GO_PROXY_URL}}) are
# filled here at install time from the credentials block; a token filled by neither
# generate.sh nor this step triggers a warning and a non-zero exit.
#
# pip / uv / go don't read env.sh — they can't expand ${VAR}; python.sh/go.sh
# bake literal values instead.

echo ""
echo "[endor] ── env.sh setup ─────────────────────────────────────────────────────"

ENDOR_ENV_SH="$USER_HOME/.config/endor/env.sh"

# Fill the attribution-dependent tokens (values from the credentials block).
ENVSH_BLOCK=${ENVSH_BLOCK//'{{ATTR_USER}}'/$ENDOR_ATTR_USER}
ENVSH_BLOCK=${ENVSH_BLOCK//'{{NPM_AUTH_B64}}'/$ENDOR_AUTH_B64}
ENVSH_BLOCK=${ENVSH_BLOCK//'{{PIP_INDEX_URL}}'/$ENDOR_PYPI_URL}
ENVSH_BLOCK=${ENVSH_BLOCK//'{{GO_PROXY_URL}}'/$ENDOR_GO_PROXY_URL}

if [[ "$ENVSH_BLOCK" == *'{{'* ]]; then
echo "[endor] WARNING: unresolved {{...}} token in env.sh block — a token in" >&2
echo "[endor] shared/blocks/envsh.txt has no fill in templates/envsh.sh." >&2
_ENDOR_WARNED=1
fi

upsert_block \
"$ENDOR_ENV_SH" \
"$ENVSH_BLOCK" \
Expand Down
7 changes: 5 additions & 2 deletions package-firewall/bash/templates/go.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
# Linux → ~/.config/go/env (or $XDG_CONFIG_HOME/go/env)
#
# Block content is defined in shared/blocks/goenv.txt.
# {{GO_PROXY_URL}} is substituted at generation time — Go env files do not support
# env var expansion, so credentials are baked into the GOPROXY value.
# Go env files can't expand env vars, so the {{GO_PROXY_URL}} token is filled
# here at install time with the attributed literal (from the credentials block).
#
# The go env file takes lower precedence than the GOPROXY process env var, so
# project-level overrides (go env -w GOPROXY=... in a workspace) remain possible.
Expand All @@ -17,6 +17,9 @@
echo ""
echo "[endor-go] ── Go package manager ───────────────────────────────────────────"

# Fill the attributed GOPROXY into the block content (go env can't expand env vars).
GO_BLOCK=${GO_BLOCK//'{{GO_PROXY_URL}}'/$ENDOR_GO_PROXY_URL}

# ── Resolve go env file path ──────────────────────────────────────────────────
# Run `go env GOENV` with the user's HOME so Go's os.UserConfigDir() returns
# the correct user-specific path (macOS: ~/Library/Application Support/go/env,
Expand Down
7 changes: 6 additions & 1 deletion package-firewall/bash/templates/js.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ echo "[endor-js] covers: yarn classic (1.x)"
# ── .yarnrc.yml ───────────────────────────────────────────────────────────────
# Covers: yarn 2+ / berry only
# yarn 2+ reads .yarnrc.yml for registry and auth — it does NOT read .npmrc.
# Uses npmAuthIdent (plain "key:secret") — confirmed working with Endor firewall.
# Uses npmAuthIdent (plain "user:secret") — confirmed working with Endor firewall.
# Yarn berry supports ${VAR} expansion in .yarnrc.yml values (yarn 3+).

# The shared block says ${ENDOR_API_KEY_ID} (Windows-safe); swap to the
# attributed user on bash. Remove once Windows gets attribution.
YARNRC_BLOCK=${YARNRC_BLOCK//'${ENDOR_API_KEY_ID}'/'${ENDOR_ATTR_USER}'}

warn_if_key_conflict \
"$USER_HOME/.yarnrc.yml" \
"^npmRegistryServer:" \
Expand Down
10 changes: 7 additions & 3 deletions package-firewall/bash/templates/maven.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@

# Block content is defined in shared/blocks/mavensettings.txt.
# {{MAVEN_REGISTRY_URL}} is substituted at generation time. Credentials are NOT
# baked in -- settings.xml references ${env.ENDOR_API_KEY_ID} / ${env.ENDOR_API_SECRET},
# which Maven expands at runtime from the env vars already provided by env.sh.
# baked in -- settings.xml references env vars, which Maven expands at runtime
# from the values provided by env.sh.

echo ""
echo "[endor-maven] ── Maven ────────────────────────────────────────────────────────"

# The shared block says ${env.ENDOR_API_KEY_ID} (Windows-safe); swap to the
# attributed user on bash. Remove once Windows gets attribution.
MAVEN_BLOCK=${MAVEN_BLOCK//'${env.ENDOR_API_KEY_ID}'/'${env.ENDOR_ATTR_USER}'}

MAVEN_SETTINGS="$USER_HOME/.m2/settings.xml"

# warn if the admin already defines a mirror/server outside an Endor block --
Expand All @@ -30,5 +34,5 @@ upsert_xml_block \
echo "[endor-maven] settings.xml -> $MAVEN_SETTINGS"
echo "[endor-maven] covers: maven (all versions), and Gradle when it reads ~/.m2"
echo "[endor-maven] mirror: {{MAVEN_REGISTRY_URL}} (mirrorOf=*)"
echo "[endor-maven] NOTE: credentials come from env vars (ENDOR_API_KEY_ID/SECRET) via env.sh"
echo "[endor-maven] NOTE: credentials come from env vars (ENDOR_ATTR_USER/API_SECRET) via env.sh"
echo "[endor-maven] ✓ Maven done"
11 changes: 9 additions & 2 deletions package-firewall/bash/templates/python.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@
# Block content is defined in templates/blocks/pipconf.txt and uvtoml.txt.
#
# Credential approach per tool:
# pip → literal credentials in pip.conf (pip cannot expand env vars — see pipconf.txt)
# uv → ${ENDOR_PYPI_URL} env var reference in uv.toml (uv expands ${VAR})
# pip → literal index-url in pip.conf (pip cannot expand env vars)
# uv → literal index-url in uv.toml (baked at install time, like pip)
# poetry → POETRY_HTTP_BASIC_ENDOR_FIREWALL_* env vars via env.sh (written by env.sh step)
#
# pip/uv index-urls carry the attributed username, so their {{...}} tokens are
# filled here at install time (from the credentials block).
#
# pip section uses [endor-firewall] named section — avoids clobbering admin's [global].

echo ""
echo "[endor-python] ── Python package managers ──────────────────────────────────"

# Fill the attributed index-url into the block content (pip/uv can't expand env vars).
PIP_BLOCK=${PIP_BLOCK//'{{PIP_INDEX_URL}}'/$ENDOR_PYPI_URL}
UV_BLOCK=${UV_BLOCK//'{{ENDOR_PYPI_URL}}'/$ENDOR_PYPI_URL}

# ── pip ───────────────────────────────────────────────────────────────────────
# pip reads pip.conf automatically from all three locations below.
# pip does not support env var expansion — credentials are literal values (see pipconf.txt).
Expand Down
5 changes: 3 additions & 2 deletions package-firewall/shared/blocks/envsh.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export ENDOR_API_KEY_ID="{{API_KEY_ID}}"
export ENDOR_API_SECRET="{{API_SECRET}}"
export ENDOR_ATTR_USER="{{ATTR_USER}}"
export ENDOR_AUTH_B64="{{NPM_AUTH_B64}}"
export ENDOR_API_SECRET_B64="{{API_SECRET_B64}}"
export ENDOR_NPM_REGISTRY_URL="{{NPM_REGISTRY_URL}}"
export ENDOR_PYPI_URL="{{PIP_INDEX_URL}}"
export ENDOR_GO_PROXY_URL="{{GO_PROXY_URL}}"
export POETRY_HTTP_BASIC_ENDOR_FIREWALL_USERNAME="{{API_KEY_ID}}"
export POETRY_HTTP_BASIC_ENDOR_FIREWALL_PASSWORD="{{API_SECRET}}"
export POETRY_HTTP_BASIC_ENDOR_FIREWALL_USERNAME="{{ATTR_USER}}"
export POETRY_HTTP_BASIC_ENDOR_FIREWALL_PASSWORD="{{API_SECRET}}"