Threat intelligence and the data relay
The relay is one in-cluster service, download-api. It is the only component with a route out, and it
is how your fleet gets threat intelligence.
This is not a choice you make. It is a requirement. See Known limitations.
What it relays
Read-only, upstream-produced content only.
| Class | Routes | Why it is not shipped |
|---|---|---|
| CTI indicators | /v1/indicators/version, /v1/indicators/download | Built by the upstream intelligence pipeline |
| ML models | /v1/models/* and the per-model aliases | Trained and published by Gen0Sec |
| GeoIP | /v1/geoip/manifest, /v1/geoip/{file}/download | Licensed by MaxMind and IPInfo. The IPInfo Lite bundle is the default source |
| IDS rules | /v1/ids-rules/* | Licensed as part of the Emerging Threats ruleset |
It is a relay, not a cache. Nothing is stored on the way through. There is no local artifact store to size, no synchronization job to supervise, and no staleness window. An agent that polls gets whatever upstream has at that moment.
The relayed URLs are identical to the direct ones, so your agents need no configuration change.
What it never relays
| Class | Routes | Why |
|---|---|---|
| Workload identity | /v1/identity/* | Produced in your cluster, describes your workloads |
| Policy edges | /v1/policy-edges/* | Pod IPs, namespaces, NetworkPolicy edges |
| Fleet bans | /v1/fleet-bans/* | Your own agents' ban decisions |
These three are tenant-scoped: they describe your infrastructure or your decisions. Relaying them would push that to a service you do not run. They keep the local object store path behind the local authorization check, in relay mode exactly as without it.
Uploads are never relayed. The relay is read-only, so PUT and POST on the download surface are
served locally or not at all.
How authorization works
Two hops, two credentials, checked in two places.
| Hop | Credential | Checked by |
|---|---|---|
| Agent to relay | The agent's own key, issued by your dashboard | Your cluster. Relayed routes sit behind the local auth middleware and each carries the same permission it has in direct mode |
| Relay to Gen0Sec | Your Gen0Sec API key | Gen0Sec |
A caller's key is never forwarded upstream on a self-hosted deployment. A locally-issued key cannot authenticate to gen0sec. Forwarding it would leak a local credential to a third party without making the request succeed. With no Gen0Sec API key configured, the relay sends no credential at all.
The setting that must be right
downloadProxy:
enabled: true
upstream: "https://api.gen0sec.com"
apiKeySecret:
name: gen0sec-download-proxy # named by default
key: DOWNLOAD_PROXY_API_KEY
Why the key is required
Your agents' keys are issued by your dashboard and exist only in your database, so they cannot authenticate to gen0sec. The relay presents your own Gen0Sec API key instead, and that is the only credential the upstream will accept from your installation.
Without it, the relay sends no credential, Gen0Sec answers 401, and no relayed artifact reaches your fleet.
apiKeySecret.name defaults to gen0sec-download-proxy. If that secret does not exist, the relay pod
never starts: CreateContainerConfigError, visible immediately.
That is deliberate. An empty name renders no environment variable at all, the relay starts, and the only symptom is every relayed class answering 401 while agents report empty rulesets rather than errors. A failed rollout at install time is the cheaper failure.
Blank the name only if you relay through a gateway that supplies its own credential.
Getting the key right
printf '%s' '<your Gen0Sec API key>' > ~/.gen0sec/relay-key
kubectl -n gen0sec create secret generic gen0sec-download-proxy \
--from-file=DOWNLOAD_PROXY_API_KEY=$HOME/.gen0sec/relay-key
The value renders as a secretKeyRef, so it never appears in a manifest or in a values file.
Through a forward proxy
If your network's only route out is a corporate proxy, send the relay through it. Add this to your platform overrides file:
downloadProxy:
httpsProxy: "http://proxy.corp.example:3128"
The setting applies to download-api alone, so it stays the only component with a route out. A
SOCKS5 proxy works the same way, with a socks5:// address.
A proxy that needs a login. Only a username and password in the address are supported, not NTLM
or Kerberos. The address then carries the password, so keep it in a Secret and set
httpsProxySecret instead of httpsProxy:
kubectl -n gen0sec create secret generic corp-proxy \
--from-literal=HTTPS_PROXY='http://<user>:<password>@proxy.corp.example:3128'
downloadProxy:
httpsProxySecret:
name: corp-proxy
Set one or the other. Setting both fails the render with
downloadProxy: set httpsProxy or httpsProxySecret.name, not both.
What the proxy must allow: CONNECT to api.gen0sec.com on port 443. The relay opens a tunnel
through the proxy, so TLS still runs end to end and the proxy never sees your Gen0Sec API key.
Traffic inside the cluster skips the proxy. noProxy defaults to
localhost,127.0.0.1,.svc,.cluster.local. To add your own internal domains, append them rather than
replacing the list.
The relay checks Gen0Sec's certificate, and an inspecting proxy replaces it with one the relay does not
trust. Exempt api.gen0sec.com from inspection.
To confirm the setting reached the relay, wait for the new relay pod, then read its startup line:
kubectl -n gen0sec rollout status deploy/g0s-download-api
kubectl -n gen0sec logs deploy/g0s-download-api | grep 'proxy mode enabled'
It shows "https_proxy":true. Without the wait, right after an upgrade, the log can come from the old
pod and show false. The log never shows the proxy's address, because it can carry a
password.
All settings
| Key | Default | Meaning |
|---|---|---|
downloadProxy.enabled | true | Turning it off is not supported |
downloadProxy.upstream | https://api.gen0sec.com | The upstream base URL. A malformed value fails startup rather than degrading |
downloadProxy.external | geoip,ids-rules | Which external classes to relay. An unknown name fails startup |
downloadProxy.timeout | 60s | Upstream request timeout, matching the router's own |
downloadProxy.apiKeySecret.name | gen0sec-download-proxy, set by values-onprem.yaml. The chart's own default is empty | Keep it. See above |
downloadProxy.apiKeySecret.key | DOWNLOAD_PROXY_API_KEY | The key within the secret |
downloadProxy.httpsProxy | "", meaning connect directly | A forward proxy for the relay. See above |
downloadProxy.httpsProxySecret.name | "" | A Secret holding the proxy address, for a proxy that needs a login. Not together with httpsProxy |
downloadProxy.httpsProxySecret.key | HTTPS_PROXY | The key within that Secret |
downloadProxy.noProxy | localhost,127.0.0.1,.svc,.cluster.local | Addresses that skip the proxy. Used only when a proxy is set |
A malformed upstream is not treated as "fall back to reading from the local object store". That would leave every agent poll erroring against a store this deployment does not populate. Failing fast is the safer wrong answer.
Verifying
kubectl -n gen0sec get --raw \
'/api/v1/namespaces/gen0sec/services/g0s-download-api:http/proxy/status/healthz'
The output is similar to this:
{ "status": "ready", "mode": "proxy", "upstream": "https://api.gen0sec.com",
"external_relays": ["geoip", "ids-rules"] }
That tells you the relay started, not that it is authorized. For that, read the log:
kubectl -n gen0sec logs deploy/g0s-download-api | grep -i 'relayed from upstream' | tail -5
Any status=401 means your Gen0Sec API key is missing or wrong.
Response codes worth knowing
| Code | Means | What an agent does |
|---|---|---|
| 200 | Relayed successfully | Uses the artifact |
| 401 | The upstream rejected the credential | Reports an empty ruleset, not an error. This is the silent failure |
| 502 | The upstream is unreachable | Reads it as "ask again later" and keeps what it holds. Correct degradation |
| 404 | The artifact does not exist upstream | Treats it as withdrawn |
The 401 and 502 rows are the reason this page exists.
Preview a change before applying it
helm template g0s $B/charts/gen0sec-platform-*.tgz -n gen0sec \
-f $B/values/gen0sec-platform-values-onprem.yaml \
--set entrypointHost=$ENTRYPOINT_HOST \
--set "services.ui.ingress.hosts[0].host=$DASHBOARD_HOST" \
--set downloadProxy.apiKeySecret.name=gen0sec-download-proxy \
| grep -A3 DOWNLOAD_PROXY