Security
SWIRL for Backstage has two kinds of results, and they are protected in two different ways. Read this page before you activate a federated source.
The shared credential model
In SWIRL Community, a federated source is configured with one credential: a service account token or a user name and password. SWIRL uses that one credential for every query, no matter which portal user typed it.
Every Backstage user who can search can see anything the service account can read in that source. Backstage permissions do not apply to federated results, because Backstage never indexed them.
That is the whole model, stated plainly. It is workable when the service account is deliberately narrow. Two controls exist to keep it that way.
- Give the service account the least access that answers useful queries. A token that can read three repositories is a token that can leak three repositories.
- Set a scope restriction, which SWIRL requires before a source can be activated. It is described next.
Per-user authentication is the SWIRL Enterprise answer to this. See What changes with Enterprise.
Mandatory scope restriction
Some sources return the whole tenant when queried with no scope. A GitHub search with no repo: qualifier searches everything the token can read. A Confluence query with no space searches every space.
SWIRL blocks that. A SearchProvider whose tags include a tag with a scope rule cannot be activated unless its query carries a matching restriction. The check runs in three places, all calling the same function: the model, the REST API serializer, and the Django admin form. There is no path that skips it.
The check looks at query_template and query_template_json together, case-insensitively, and only when the provider is active.
| Tag | The query must contain | Example |
|---|---|---|
github |
repo: or org: or user: |
{url}?q={query_string}+repo:acme/platform |
confluence |
spaceKey= or space= or cql= followed by space |
{url}&cql=text~'{query_string}'+and+space='ENG' |
jira |
project followed by = or in, or jql= followed by project |
{url}&jql=project=ENG and text~'{query_string}' |
gitlab |
group_id= or projects/ or groups/ |
{url}/groups/42/search?scope=issues&search={query_string} |
Jira and GitLab rules are already enforced, so a source you add later with those tags is covered too. Trying to activate a provider without a restriction returns an error naming the tag and the pattern it needs.
The bypass flag
An operator who genuinely wants an unrestricted source sets this on the provider's config field:
{"swirl": {"scope_unrestricted": true}}
Three things follow from setting it.
- The provider activates without a scope restriction.
- SWIRL logs a warning every time that provider is queried, not only when the flag is set.
- Every result from that provider carries
payload.shared_visibility = "unrestricted", so a consumer can label it.
With the bypass flag set, every Backstage user can reach everything the credential can read in that source, across every repository or space. Use it only where that is the intended outcome.
Backstage permissions on indexed results
Catalog and TechDocs results are different. They were indexed by Backstage collators, so Backstage can filter them.
Each collated document carries an authorization block with a resource reference. SWIRL stores the original Backstage document verbatim and returns it unchanged, including that block. With permission.enabled set, Backstage's AuthorizedSearchEngine filters the returned results against your permission policy, by document type and resource reference. It then asks SWIRL for more pages until the page is full. Those extra pages are database reads in SWIRL rather than fresh federations.
The result is that catalog and TechDocs permission filtering behaves exactly as it did with the previous engine. Federated results are not filtered this way, which is why the swirl-federated document type is registered separately and why the shared credential model matters.
How the Backstage token is verified
The Backstage search router mints one plugin token per query and hands it to the engine. The module forwards it to SWIRL unchanged. SWIRL verifies it before the request reaches any view.
- SWIRL reads the unverified JWT header. A token whose
typisvnd.backstage.plugintakes the Backstage path. Anything else takes the existing OIDC path. - The signing key is fetched from
SWIRL_BACKSTAGE_JWKS_URLand cached, using SWIRL's normal HTTP stack, so proxy and TLS settings apply. - The signature is checked with ES256, ES384, or RS256. The
audclaim must equalSWIRL_BACKSTAGE_AUDIENCE, andexp,iat,sub, andaudmust all be present. - The nested
obotoken is read for itssub, the user entity reference. It is not verified separately, because its bytes are covered by the outer signature. - A token with an
obomaps to a SWIRL user namedbackstage:user:default/name. That user is provisioned on first sight into the groupBackstage Users, with search permissions only. - A token with no
obois a service principal. It maps to the single userbackstage:service, which holds the ingest permission and nothing else. This is the identity the indexer uses.
Any failure returns HTTP 401 with WWW-Authenticate: Bearer error="invalid_token". When SWIRL_BACKSTAGE_JWKS_URL or SWIRL_BACKSTAGE_AUDIENCE is empty, the path is disabled and no bearer token is treated as a Backstage token.
The user reference is used for the search owner and for audit in SWIRL Community. It does not select credentials there, because a Community source has only one credential.
What changes with Enterprise
SWIRL Enterprise supports OAuth2 sources, including sources that offer no other authentication. With an OAuth2 source, SWIRL holds a token per user rather than one token for the portal. The source itself then applies that user's permissions. The shared credential problem does not arise, and the scope restriction stops being a security control.
The Backstage user reference from the obo claim is the key SWIRL Enterprise uses to select the right per-user token. The flow that lets a Backstage user connect their account, without a SWIRL session, is planned and is not in this release. Enterprise also adds a semantic cache and cross-source deduplication.
The Backstage SKU license permits use inside Backstage only. A request that does not carry a verified Backstage token is answered with HTTP 402 and a message naming the full edition. See Troubleshooting.