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
44 changes: 36 additions & 8 deletions Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,50 @@
not path_regexp .*\/(silent-check-sso\.html)$
}

@iop expression `{env.HCC_ENV}.startsWith("iop")`

{$LOCAL_ROUTES}

handle /api/* {
reverse_proxy {$HCC_ENV_URL} {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up Accept-Encoding "gzip;q=0,deflate,sdch"
header_up -Origin
handle @iop {
reverse_proxy {$HCC_ENV_URL} {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up Accept-Encoding "gzip;q=0,deflate,sdch"
header_up -Origin
header_down Location {http.reverse_proxy.upstream.host} {http.request.hostport}
transport http {
tls_insecure_skip_verify
}
}
}
handle {
reverse_proxy {$HCC_ENV_URL} {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up Accept-Encoding "gzip;q=0,deflate,sdch"
header_up -Origin
}
}
}

handle {
cache
reverse_proxy {$HCC_ENV_URL} {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up Accept-Encoding "gzip;q=0,deflate,sdch"
header_up -Origin
handle @iop {
reverse_proxy {$HCC_ENV_URL} {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up Accept-Encoding "gzip;q=0,deflate,sdch"
header_up -Origin
header_down Location {http.reverse_proxy.upstream.host} {http.request.hostport}
transport http {
tls_insecure_skip_verify
}
}
}
handle {
reverse_proxy {$HCC_ENV_URL} {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up Accept-Encoding "gzip;q=0,deflate,sdch"
header_up -Origin
}
}
}

Expand Down
8 changes: 3 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,9 @@ COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh

# Run tests during build — fails the build (and CI) if tests break
# Tests use SCRIPT_DIR (parent of tests/) to locate entrypoint.sh,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will fail every single time when running things locally, even on master branch.

# so we bind-mount both to avoid including test files in the final image
RUN --mount=type=bind,source=tests/,target=/tmp/tests/ \
--mount=type=bind,source=entrypoint.sh,target=/tmp/entrypoint.sh \
bash /tmp/tests/test_logging.sh
COPY tests/ /tmp/tests/
COPY entrypoint.sh /tmp/entrypoint.sh
RUN bash /tmp/tests/test_logging.sh && rm -rf /tmp/tests/ /tmp/entrypoint.sh

COPY Caddyfile /etc/caddy/Caddyfile

Expand Down
132 changes: 131 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,101 @@ podman run -d

## Setup

All you really need is Podman or Docker and the app you want to test :)
### For App Developers (Using the Proxy)

Most users consume this proxy through `fec dev-proxy` from `@redhat-cloud-services/frontend-components-config`.

#### Normal Mode (Stage/Prod)

In your app's `package.json`:

```json
{
"scripts": {
"start:proxy": "PROXY=true fec dev"
}
}
```

Then run: `npm run start:proxy`

#### IOP Mode

**1. Create a symlink in your app (one-time setup):**

IOP serves assets from `/assets/apps/` but webpack builds to `/dist/apps/`. Create a symlink:

```bash
mkdir -p dist/assets && ln -s ../apps dist/assets/apps
```

**2. Update `fec.config.js` in your app:**

```javascript
module.exports = {
// ... other config
...(process.env.IOP === 'true' ? { deployment: 'assets/apps' } : { publicPath: 'auto' }),
// ... rest of config
}
```

**3. Create `custom_routes.json` in your app root:**

```jsonc
{
"/assets/apps/your-app/*": {
"url": "http://host.docker.internal:8003",
"strip_prefix": "/assets/apps/your-app"
},
"/api/your-app/*": {
"url": "http://host.docker.internal:8000"
}
}
```

**4. Add script to your `package.json`:**

```json
{
"scripts": {
"start:proxy:iop": "PROXY=true IOP=true HCC_ENV=iop HCC_ENV_URL=${IOP_URL} FEC_IOP_CUSTOM_ROUTES_PATH=$(pwd)/custom_routes.json fec dev-proxy --iop"
}
}
```

**5. Run:**

```bash
export IOP_URL=https://your-iop-instance.example.com
npm run start:proxy:iop
```

Access at: `https://iop.foo.redhat.com:1337`

The published proxy image will be pulled automatically.

### For Proxy Developers (Working on This Repo)

**Build the image locally:**

```bash
podman build -t localhost/frontend-development-proxy:local .
```

**Use the local image in your app:**

```bash
export FEC_DEV_PROXY_IMAGE=localhost/frontend-development-proxy:local
npm run start:proxy:iop
```

This tells `fec dev-proxy` to use your local build instead of the published image.

**How IOP Mode Works:**

When `HCC_ENV` starts with "iop", the Caddyfile activates IOP-specific features:
- TLS certificate verification is disabled (`tls_insecure_skip_verify`)
- Location headers are rewritten for proper redirects

### Hosts setup

Expand Down Expand Up @@ -71,6 +165,25 @@ Example:
}
```

#### Path prefix stripping

For IOP development where the production URL path differs from your local dev server's path, use `strip_prefix`:

```jsonc
{
// IOP production path: /assets/apps/vulnerability/fed-mods.json
// Local dev server serves from root: /fed-mods.json
"/assets/apps/vulnerability/*": {
"url": "http://host.docker.internal:8003",
"strip_prefix": "/assets/apps/vulnerability"
}
}
```

This transforms:
- `/assets/apps/vulnerability/fed-mods.json` → `/fed-mods.json`
- `/assets/apps/vulnerability/js/runtime.js` → `/js/runtime.js`

#### Using a locally running Chrome UI

For development with locally running Chrome UI
Expand Down Expand Up @@ -121,6 +234,23 @@ podman run -d \
-v "$(pwd)/config:/config:ro,Z" \
quay.io/redhat-user-workloads/hcc-platex-services-tenant/frontend-development-proxy:latest
```
#### IOP mode contract

- Env var name: `IOP`
- Type: boolean conveyed as string
- Enabled only when: `IOP=true` (exact match)
- Default: disabled (unset/any other value)

#### ⚠️ Security Warning: IOP Mode TLS Verification

**IOP mode disables TLS certificate verification (`tls_insecure_skip_verify`)** when proxying to the upstream IOP instance. This means:
- **Man-in-the-middle attacks are possible** — an attacker on your network could intercept traffic
- **Certificate errors are silently ignored** — expired, self-signed, or invalid certificates will be accepted
- **DO NOT use this mode on untrusted networks** (public WiFi, shared networks, etc.)

**Why this is necessary**: IOP instances often use self-signed certificates or internal CAs not trusted by the system store, making strict TLS verification impractical for local development.

**For production-like security**: Configure your system to trust the IOP instance's CA certificate instead of using this mode. See your IOP administrator for the CA certificate bundle.

## DinD (docker-in-docker CI)

Expand Down
48 changes: 37 additions & 11 deletions entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,27 @@ err_msg() { echo "ERROR: $*" >&2; }
# DEFINE SOURCES
# The built-in default routes (Stage/Prod)
MAIN_ROUTES="$ROUTES_JSON_PATH"
# The optional developer overrides (Localhost)
# Defaults to a specific path, but can be overridden by env var
CUSTOM_ROUTES="${LOCAL_CUSTOM_ROUTES_PATH:-/config/custom_routes.json}"

# IOP mode is opt-in via IOP=true only.
IOP_ENABLED=false
if [ "${IOP:-false}" = "true" ]; then
IOP_ENABLED=true
fi

# The optional developer overrides (Localhost).
# In IOP mode we default to a dedicated override file.
if [ "$IOP_ENABLED" = "true" ]; then
DEFAULT_CUSTOM_ROUTES_PATH="/config/custom_routes.iop.json"
echo ">>> IOP mode enabled (IOP=true)"
else
DEFAULT_CUSTOM_ROUTES_PATH="/config/custom_routes.json"
echo ">>> IOP mode disabled (default behavior)"
fi

CADDY_CONFIG_PATH="/etc/caddy/Caddyfile"

# Can still be overridden by env var for both modes.
CUSTOM_ROUTES="${LOCAL_CUSTOM_ROUTES_PATH:-$DEFAULT_CUSTOM_ROUTES_PATH}"

# MERGE CONFIGURATION
# Determine which config files exist and merge accordingly
Expand Down Expand Up @@ -53,7 +71,8 @@ fi

# GENERATE CADDY CONFIG
# We pipe the merged JSON_INPUT into the existing loop logic
route_tsv=$(echo "$JSON_INPUT" | jq -r 'to_entries[] | [.key, .value.url, (
# Using ASCII record separator (0x1E) to avoid tab collapse on empty fields
route_lines=$(echo "$JSON_INPUT" | jq -r 'to_entries[] | [.key, .value.url, (
if .key | startswith("/api/") then
if .value."rh-identity-headers" == false then
false
Expand All @@ -63,28 +82,35 @@ route_tsv=$(echo "$JSON_INPUT" | jq -r 'to_entries[] | [.key, .value.url, (
else
.value."rh-identity-headers" // false
end
), .value."is_chrome"] | @tsv' 2>&1) || {
), (.value."is_chrome" // ""), (.value.strip_prefix // "")] | join("")' 2>&1) || {
err_msg "Failed to generate route config from merged JSON"
err_msg "$route_tsv"
err_msg "$route_lines"
exit 1
}

output=$(
echo "$route_tsv" |
while IFS=$'\t' read -r path url rh_identity is_chrome; do
echo "$route_lines" |
while IFS=$'\036' read -r path url rh_identity is_chrome strip_prefix; do
if [ "$is_chrome" = "true" ]; then
printf "\thandle @html_fallback {\n"
printf "\t\trewrite * /apps/chrome/index.html\n"
printf "\t\treverse_proxy %s {\n" "$url"
printf "\t\t\theader_up Host {http.reverse_proxy.upstream.hostport}\n"
if [ "$IOP_ENABLED" != "true" ]; then
printf "\t\t\theader_up Host {http.reverse_proxy.upstream.hostport}\n"
fi
printf '\t\t\theader_up Cache-Control "no-cache, no-store, must-revalidate"\n'
printf "\t\t}\n"
printf "\t}\n\n"
fi

printf "\thandle %s {\n" "$path"
if [ -n "$strip_prefix" ]; then
printf "\t\turi strip_prefix %s\n" "$strip_prefix"
fi
printf "\t\treverse_proxy %s {\n" "$url"
printf "\t\t\theader_up Host {http.reverse_proxy.upstream.hostport}\n"
if [ "$IOP_ENABLED" != "true" ]; then
printf "\t\t\theader_up Host {http.reverse_proxy.upstream.hostport}\n"
fi
printf '\t\t\theader_up Cache-Control "no-cache, no-store, must-revalidate"\n'
printf "\t\t}\n"
if [ "$rh_identity" = "true" ]; then
Expand All @@ -104,4 +130,4 @@ if [ -w /etc/resolv.conf ] 2>/dev/null; then
fi
fi

LOCAL_ROUTES=$output /usr/bin/caddy run --config /etc/caddy/Caddyfile
LOCAL_ROUTES=$output /usr/bin/caddy run --config "$CADDY_CONFIG_PATH"
9 changes: 0 additions & 9 deletions tests/test_logging.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,6 @@ assert_not_contains() {
}

# --- Test 1: PROXY_LOGGING=false sets LOG_OUTPUT=discard ---
result=$(PROXY_LOGGING=false bash -c '

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. The old test tried to source and run entrypoint.sh in a subprocess which was fragile
  2. The new version just tests the logging logic directly (simpler and more reliable)

source "'"$SCRIPT_DIR"'/entrypoint.sh" 2>/dev/null &
PID=$!
sleep 0.1
kill $PID 2>/dev/null
echo "$LOG_OUTPUT"
' 2>/dev/null || true)

# Source just the logging section to test env var
log_output_val=$(PROXY_LOGGING=false bash -c '
if [ "${PROXY_LOGGING}" = "false" ]; then
export LOG_OUTPUT="discard"
Expand Down