Upstreams
Field-level reference for upstream configuration, credentials handling, and cache behavior.
An upstream is one registry a firewall resolves packages from. Upstreams live in the upstreams array of the firewall config. For behavior and patterns, see Upstreams and caching and Configure upstreams.
Shape
{
"id": "e2de947a",
"endpoint": "https://npm.acme.internal",
"credentials": {
"username": "",
"password": "",
"token": "<encrypted>"
},
"internal": true,
"cached": true
}Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique within the firewall. Referenced by publishTarget, the upstreamId selector field, and log entries |
endpoint | string | Base URL of the registry, in the ecosystem's native protocol |
credentials | object | username, password, token; all optional. See below |
internal | boolean | Marks this registry as the authority for its packages. When any internal upstream knows a package name, only internal upstreams' versions are served |
cached | boolean | Cache responses from this upstream (metadata for a short interval, plus fetched artifacts). Applies to upstreams marked internal |
artifactCredentials | array | Credentials for artifact hosts that are not this upstream's own host. Empty for every ecosystem whose package files live on the registry itself. See Artifact host credentials |
Credentials
The auth scheme is inferred from which fields are set; there is no separate auth-type setting.
- On fetches,
tokenis sent asAuthorization: Bearer. Without a token,username/passwordare sent as HTTP basic auth. If both are set, the token wins. One compat case: Azure Artifacts endpoints (pkgs.dev.azure.com) receive a token as basic auth in addition to the bearer header. - On publishes forwarded to the publish target, npm follows the fetch behavior above; Maven and NuGet send
tokenas a bearer header (NuGet additionally asX-NuGet-ApiKey) and fall back tousername/passwordas basic auth; PyPI sends a token as__token__basic auth (the twine convention), orusername/passwordas basic auth. - Encrypted at rest. API responses and exports never contain the plaintext; they carry a placeholder value that preserves an existing stored credential on import.
- Config import rejects plaintext credential values. Leave placeholders untouched in exported files. A placeholder for a new firewall or upstream resolves to empty because that target has no stored credential; set it separately after import.
Artifact host credentials
Some registries serve metadata and package files from different hosts. Packagist is the current case: its metadata can point a download to a separate artifact host, and most Packagist dist URLs use api.github.com.
Credentials set on the upstream are only ever sent to the upstream's own host. A different host is only given credentials if it has its own entry:
{
"id": "packagist",
"endpoint": "https://repo.packagist.org",
"credentials": { "username": "", "password": "", "token": "" },
"internal": false,
"cached": false,
"artifactCredentials": [
{
"host": "api.github.com",
"credentials": { "username": "", "password": "", "token": "<encrypted>" }
}
]
}hostis a hostname, optionally with a port (registry.internal:8443), never a URL. It is matched case-insensitively against the download URL's host; when both a port-qualified and a bare entry exist, the port-qualified one wins.- If no entry matches, the firewall fetches from that host anonymously. Configuration still succeeds, but the host can later refuse a download after its anonymous rate limit is exhausted.
- Set them with
PUT /config/<firewall-id>/upstreams/<upstream-id>/artifact-credentials/<host>, which takes a plaintext body and merges per field, exactly like the upstream's own credentials endpoint. Currently there is nobsfwcommand for it. - Encrypted at rest, and redacted in API responses, exports, and webhook events, like every other credential.
Composer is the only ecosystem whose downloads use these today. For Composer upstreams, the dashboard provides a field for api.github.com; use the API for another artifact host. See the Composer artifact host token.
Cache behavior
cached: truerequiresinternal: true. An external upstream with caching enabled is rejected with400:upstream <id>: caching is only allowed on internal upstreams. The rule holds for the dashboard, the API, andbsfw firewall importalike.cached: falsedisables caching for the upstream, with one override: Maven Central endpoints (https://repo1.maven.org/maven2,https://repo.maven.apache.org/maven2) are always cached. Every other Maven upstream, including a repository manager, follows its own flag.- Maven
-SNAPSHOTartifacts are never cached, even from a cached upstream, so a rebuilt snapshot is always fetched fresh. - Cache keys include the firewall config version, so a config change is never answered from cache entries built under the previous version. The change itself propagates across the cluster within a few seconds.
- Cache keys include the upstream's credentials, and version listings from an upstream with any credentials set are cached per namespace, for a shorter interval than listings from an upstream without credentials.
Failure behavior
An upstream that cannot be reached, or rejects the firewall's credentials, is skipped during resolution. The firewall answers with what the remaining upstreams returned, so a request another upstream can serve is unaffected, and any version held only by the skipped upstream drops out of the version list.
Upstreams are not copies of each other, so that only helps when another upstream has the package. When the skipped upstream was the only one holding it, or the firewall has a single upstream, nothing is left to serve and the request fails:
- npm, PyPI, NuGet, Go, Composer, and Conda answer as if the package does not exist.
- Maven reports the failure instead, so the client sees a retryable status: an upstream
429is relayed as429, anything else becomes502. This is deliberate, so a failing upstream is not mistaken for a coordinate that was never published.
A failure after resolution, while the artifact itself is being fetched, ends in a server error rather than a not-found. Only an upstream 404 or bytes that have aged out of the cache produce a not-found at that point.
In every case the client error names the package, never the upstream behind it. The skip is recorded in the firewall log as an upstream error entry, naming the upstream and what it returned. An upstream answering that it has no such package is a normal part of resolution and records nothing.
When the upstream is cached and the firewall already holds a copy of what was requested, it can answer from that copy instead of skipping the upstream. That covers a short outage for content already fetched, and nothing else: a DNS failure and an upstream 404 are not covered.
Related
- Concept: Upstreams and caching
- How-to: Configure upstreams, Prevent dependency confusion
- Reference: Firewall config