Collectors authenticate to Shepherd with an agent token over HTTP Basic auth: the token’s ID is the username, its secret the password.
remotecfg block (keep the secret in
an environment variable or a Kubernetes Secret, not in the file):remotecfg {
url = "https://shepherd.example.com"
basic_auth {
username = "<token-id>"
password = sys.env("SHEPHERD_AGENT_TOKEN_SECRET")
}
attributes = { cluster = "prod-eu-1", role = "metrics" }
poll_frequency = "60s"
}
A token is not tied to an organisation: which org a collector belongs to is decided by claiming its cluster (Admin → Clusters). Revoke a token on the same page; collectors using it are refused on their next poll. On Grafana’s k8s-monitoring chart, Connect a cluster writes this wiring for you.
To create tokens from a script instead, run the CLI inside the Shepherd pod, which already has the database settings:
kubectl -n shepherd exec svc/shepherd -- /usr/local/bin/shepherd token create --name ci
A collector can present an OAuth2 access token from the same identity provider that does
your single sign-on, rather than a shared agent token. Alloy’s remotecfg
block fetches and presents it natively, so nothing changes on the agent beyond its config.
With a binding (below) or IdP-asserted organisations, the token is scoped to one
organisation, so a collector in one org can never pull another’s configuration;
without either, the collector’s organisation comes from claiming its cluster, as with
an agent token.
Turn it on in the chart under config.oidc (it stays off until
agent_audience is set). These settings are read from chart config only, so
collector OIDC needs config.oidc.issuer in your values, which also makes single
sign-on chart-managed; with single sign-on set up in the admin UI, the
agent_* settings are ignored.
config:
oidc:
issuer: "https://login.microsoftonline.com/<tenant>/v2.0"
# The resource identifier a collector's token must carry in `aud`. This is
# the master switch and the thing that separates a machine token from a
# user login token.
agent_audience: "api://shepherd-collectors"
# Recommended: require the token to carry a specific app role, so a valid
# token for the audience is not enough without the explicit grant.
agent_required_role: "Collector.Poll"
# Or, if your provider grants access as a scope: agent_required_scope.
# The claim naming the calling client, matched against a binding's app id.
# Defaults to sub; for Entra client-credentials tokens use azp.
agent_app_claim: "azp"
Then bind each collector’s client identity to an organisation. By default Shepherd holds the mapping — the identity provider is not authoritative for which org a collector belongs to (see Trusting the IdP for org assignment to opt into the alternative):
shepherd agent-identity create \
--issuer "https://login.microsoftonline.com/<tenant>/v2.0" \
--app-id "<client-id>" \
--org "platform-engineering" \
--cluster "prod-eu-1" # optional; repeatable. Omit for any cluster in the org.
A collector whose cluster is unclaimed is auto-assigned to its bound org on its first
authenticated poll. A cluster already claimed by a different org is refused. A binding can
also restrict the collector roles it admits (--role, repeatable). App admins
can manage bindings in the UI too, under Admin → Single sign-on →
Collector identity bindings.
Instead of a Shepherd-side binding, you can let the identity provider assert the org, if you trust it to. Off by default; turn it on with one of two config values:
oidc.agent_trust_org_claim — the name of a token claim whose value is the
org’s name (a custom claim your IdP emits, e.g. shepherd_org).oidc.agent_org_role_prefix — read the org from an app role under this prefix,
the “app role names the org” pattern. With prefix shepherd-org:,
the role shepherd-org:platform-engineering assigns the collector to
platform-engineering.The asserted value is an org name, resolved against your organisations and then auto-claiming the cluster exactly as a binding would. An org that does not exist is refused; a role prefix that matches two different orgs is treated as no assertion. A token that carries neither a binding nor a trusted org claim still falls through to the admin cluster-claim. A binding, when present, wins over an asserted claim.
Point remotecfg at Shepherd with an oauth2 block instead of
basic_auth. Alloy fetches its own access token from the provider and presents
it on every poll:
remotecfg {
url = "https://shepherd.example.com"
oauth2 {
client_id = sys.env("SHEPHERD_OIDC_CLIENT_ID")
client_secret = sys.env("SHEPHERD_OIDC_CLIENT_SECRET")
token_url = "https://login.microsoftonline.com/<tenant>/oauth2/v2.0/token"
scopes = ["api://shepherd-collectors/.default"]
}
attributes = { cluster = "prod-eu-1", role = "metrics" }
poll_frequency = "60s"
}
The IdP must issue JWT access tokens with the configured
audience. This is the standard OAuth2 client-credentials grant: in Entra ID, expose an API
on Shepherd’s app registration, define an app role, and grant it to each collector’s
own app registration. Keycloak, Auth0 and Okta express the same thing with a client role, a
permission, or a scope — set agent_roles_claim to whichever claim your provider
emits.
To send the collector’s own health beacon over the same credential, set
config.oidc.beacon_auth: oauth2 and agent_token_url. Shepherd then
renders the beacon’s write-back with an oauth2 block too. This is a
whole-fleet choice: the served configuration is shared across a collector’s instances,
so a deployment uses one beacon-auth mode.