Skip to content

Docs(Security): document the --security anonymous option - #778

Open
matthewmcneely wants to merge 2 commits into
mainfrom
matthewmcneely/security-anonymous-posture
Open

matthewmcneely wants to merge 2 commits into
mainfrom
matthewmcneely/security-anonymous-posture

Conversation

@matthewmcneely

@matthewmcneely matthewmcneely commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Documents the new --security "anonymous=full|data|none" option from dgraph-io/dgraph#9844.

Unreleased. Everything here is in the docs/ ("next") tree, which this site does not publish (includeCurrentVersion: false), so it goes live only when a version cut copies it forward. The new page also opens with an "Unreleased feature" admonition. Do not cherry-pick this into a version-v25.x snapshot until the dgraph PR ships.

What changed

File Change
docs/admin/security/anonymous-access.md New page. Why the option exists, the three postures, what counts as an identified caller, per-operation tables for data, administrative, and always-open operations, a scenario guide for choosing a posture, worked curl / gRPC / YAML / env-var examples, Zero behavior, startup warnings, a step-by-step migration to a closed posture, known limitations, and the mixed-version upgrade constraint
sidebars.ts, docs/admin/security/index.md Register and link the new page
docs/cli/superflags.md anonymous row in the --security table, a value table, and an example. Also notes that whitelist is a location check, not authentication
docs/admin/security/admin-endpoint-security.md Explains that each layer passes when unset, adds a whitelist caution and an anonymous=data example, and points the Zero section at the new option
docs/cli/alpha.md, docs/cli/zero.md --help reference blocks updated verbatim from a binary built from #9844; Zero gets a short section on the option

How the claims were checked

Every behavior and example on the new page was run against a live local cluster built from #9844, not inferred from the code:

  • All three postures, including /state, /health?all, /graphql, /probe/graphql, /login, export with and without the token, and Zero's /state and /health.
  • The error messages quoted on the page are copied from those runs.
  • The mixed-version error is from a binary built from current dgraph main, which rejects the key exactly as quoted.
  • Known limitation, found while writing this: dgraph live --auth_token cannot load data under data or none. The loader's UID lease (AllocateIDs) goes out without the token, is refused, and the loader retries silently forever. dgraph live --creds with ACL works and is what the page recommends. The client-side fix is tracked separately in the dgraph repo.

Tooling:

  • Vale 3.7.1 (the version CI pins) with .github/.vale.ini: 0 errors, 0 warnings across all six changed files.
  • docusaurus build with includeCurrentVersion temporarily switched on so the docs/ tree actually compiles (the default config skips it, so a plain build passes vacuously): exit 0, no broken links or anchors under onBrokenLinks: 'throw'. That config change is not part of this PR.

Open question for review

The "Default value and future releases" section says a future major release is planned to make data the default. That matches the proposal in #9844 but is not decided yet. Happy to soften or drop it if we'd rather not commit to it in docs.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Adds an Anonymous Access page for the new --security "anonymous=full|data|none"
option (dgraph-io/dgraph#9844), and updates the superflag reference, the Admin
Endpoint Security page, and the alpha and zero CLI references to match.

Unreleased: everything here is in the "next" docs/ tree only, which is not
published (includeCurrentVersion: false) until a version cut copies it forward.
The page also carries an "Unreleased feature" admonition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page said schema changes were unaffected by every posture, and did not mention
drops at all. That matched the code at the time and was wrong: review on
dgraph-io/dgraph#9844 found that only drop_all reached the posture, so under
anonymous=data an anonymous caller with an open whitelist could still change the
schema, drop a predicate, or empty a namespace with drop_op DATA.

The fix gates every network Alter under data and none. This moves schema changes
and drops into the administrative table, adds a caution that drop_op DATA empties a
namespace, tells operators that an application changing its own schema now needs
the token, and syncs the alpha help text with the new binary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@matthewmcneely

Copy link
Copy Markdown
Collaborator Author

Pushed a correction. The first version said schema changes were unaffected by every posture and did not mention drops. That matched the code at the time but described a bug: review on dgraph-io/dgraph#9844 found that only drop_all reached the posture, so under anonymous=data an anonymous caller with an open whitelist could still empty a namespace with drop_op: DATA.

dgraph-io/dgraph@79de59da6 now gates every network Alter under data and none. This page now:

  • lists schema changes and every kind of drop as administrative operations
  • adds a caution that drop_op: DATA empties a namespace in one request
  • tells operators that an application changing its own schema at startup now needs the token
  • syncs the alpha --help block with the new binary

Re-verified: Vale reports 0 errors, the build with the current docs compiled in has no broken links or anchors, and the help blocks match the binary byte for byte. This was all in the unpublished docs/ tree, so nothing incorrect was ever live.

matthewmcneely added a commit to dgraph-io/dgraph that referenced this pull request Oct 6, 2026
…to full (#9844)

**Description**

Adds a `--security "anonymous=full|data|none"` superflag key that says
directly what a caller
with no verified identity may do. The default is `full`, which is the
behavior of every earlier
release, so nothing about an existing cluster changes.

**Why**

Three controls gate Alpha's privileged operations: the `--security`
whitelist, the `--security`
token, and ACL. Each one passes when its own feature is unconfigured, so
the protection an
operator gets is whatever they configured rather than the union of the
three. The gap that leaves
is specific: the whitelist answers *where a request came from* and
carries no credential in it,
so widening the range is by itself enough to make administrative
operations reachable
anonymously from anywhere inside it. `whitelist=0.0.0.0/0` is a common
setting, including in our
own `dgraph/standalone` quickstart image, and in that posture there is
nothing left to check.

| Value | What an anonymous caller may do |
| --- | --- |
| `full` (default) | Whatever the whitelist, token, and ACL settings
decide. Unchanged from every earlier release. |
| `data` | Query, mutate, commit, log in, read `/health`. Every
`Capability` check and every `Alter` (schema changes and drops) denies
regardless of the whitelist. |
| `none` | Log in, `CheckVersion`, and the health and readiness
endpoints. |

"Anonymous" means the request produced no `x.Principal`: it presented no
credential, or the one
it presented did not verify.

**How it is enforced**

The posture is an `AccessController` that **wraps** whichever policy is
installed rather than
replacing it. "Has this caller been identified at all" is prior to and
independent of "what may
this identity do", and answering the first one inside the capability
rules would mean every
policy, built in or installed, reimplementing the same check and keeping
it consistent. It is
installed after `ConfigureIdentity()` so a deployment policy gets
wrapped instead of silently
discarding the floor, and under `anonymous=full` it installs nothing at
all.

Two operations don't go through a capability check, so they get explicit
gates of their own:

- **Alter.** Only `drop_all` reaches a `Capability`, so
`RequireIdentifiedAdmin` gates every network
`Alter` under `data` and `none`: schema changes, `drop_attr`, and every
`drop_op`. Without it, an
anonymous caller with an open whitelist could still empty a namespace
with `drop_op: DATA`. It runs
  after the `NoAuthorize` short-circuit, so `AlterNoAuth` is unaffected.
- **The data plane under `none`.** `RequireIdentifiedCaller` gates
`doQuery` (which covers `Query`,
`RunDQL`, `/query`, `/mutate`, `/graphql`, and subscription polls) and
`CommitOrAbort`.

`--security "token=..."` now mints a `Principal` with
`x.MethodPreshared` instead of answering a
yes or no. That is what makes a closed posture usable without standing
up full ACL: before this,
a token-bearing caller was indistinguishable from an anonymous one. Two
details worth a look:

- ACL is tried first, so a request carrying both resolves to the user
rather than the service,
which also keeps `userDataFromPrincipal`'s fast path (it requires
`Method == MethodACL`).
- `Principal.Groups` is left empty on purpose. `x.IsSuperAdmin` reads it
by name, so a
`guardians` entry would turn a shared secret into cluster authority
without passing through
  `authorizeClusterAdmin` and its ACL-on exclusion.
- `Subject` is `audit.PoorManAuth`, because audit prefers the resolved
`Principal` over
re-parsing the credential and would otherwise change what every
token-bearing request logs.
  Pinned by `TestPresharedSubjectMatchesAudit`.

Zero honors the same key, since `--security` shares one defaults string.
Its admin HTTP handler
stops letting a whitelisted source IP stand in for the token once the
posture is closed, and
enforces the non-strict routes (`/state`, `/assign`) that are otherwise
open until something is
configured.

Two startup warnings, both pure functions with a table test: a widened
whitelist with no
credential, and a closed posture with no credential, where every
capability check denies
including the operator's own and the cluster cannot be administered at
all.

**One fix that came out of running it**

`/query`, `/mutate`, `/commit`, `/state` and `/health?all` attached only
the access JWT, never
the auth token. Unit tests passed; a live cluster with `anonymous=data`
and a token configured
then refused a caller who was presenting that token on `/state`, because
the header never became
`auth-token` metadata. All five now use `x.AttachRequestIdentity`.

`TestHTTPEdgeResolvesIdentityThroughOneHelper` pins it: handlers in
`dgraph/cmd/alpha` either go
through `x.AttachRequestIdentity` or appear in `identityExceptions` with
a reason, and a stale
exception fails the test too. `loginHandler` is the one exception, since
login must not depend on
a credential it is the means of issuing.

**Testing**

Unit tests across `x`, `edgraph`, `dgraph/cmd/alpha`, and
`dgraph/cmd/zero`. The ones that carry
the argument:

- `TestBreakGlassIsNotAnIdentity`: with ACL off and no token,
break-glass grants cluster admin to
any whitelisted source IP. Under `anonymous=data` the same context is
refused.
- `TestSecurityTokenIsAnIdentityUnderClosedPosture`: the token
identifies a caller, and does not
  bypass the whitelist while doing so.
- `TestAnonymousPostureCoversEveryCapability`: the floor is capability
independent, so a new
  `Capability` constant cannot quietly escape it.
- `TestAnonymousFullIsTheZeroValue`: the v25 default is reachable by
doing nothing.

Also verified against a live local cluster across all three values:

| Config | Result |
| --- | --- |
| stock `dgraph alpha`, no `--security` | byte-for-byte unchanged, no
warnings logged |
| `whitelist=0.0.0.0/0`, no token, `anonymous=full` | `/state` and
`listBackups` answered unauthenticated, exposure warning logged |
| same, `anonymous=data` | both `Unauthenticated`; queries and mutations
still work |
| `whitelist=0.0.0.0/0; token=...; anonymous=data` | token re-opens
`/state` and `/health?all`; no warnings |
| `anonymous=none` | data plane closed, `/health` still open for
readiness |

`go vet` output is unchanged from `main` (the same pre-existing
`copylocks` findings).

**Notes for review**

1. `--security` is shared with Zero and `z.SuperFlag` calls `log.Fatal`
on an unknown key, so a
config that sets `anonymous=` will not start a pre-v25 binary. Nothing
in-tree sets it: no
`docker-compose*.yml` and no `dgraphtest` default was changed. It does
constrain how
   `TAGS=upgrade` can exercise this.
2. Under `anonymous=data`, `/state` requires a credential, because
reading cluster topology is a
`CapTenantAdmin` check. That is correct by the flag's definition, but
`dgraphapi/cluster.go`
polls `/state` for readiness, so adopting the closed posture in test
infra needs a token.
3. Rollout intent is `full` in v25 with the warnings above, and flipping
the default to `data` in
v26. Feedback on that, and on whether the three-value split is the right
cut, is the main
   thing I am after here.

This is deliberately scoped to the flag. The related completeness work
(admin GraphQL resolvers
and admin HTTP routes that are registered with no middleware at all, the
external-snapshot
`DropData` arming requirement, and the `pb.Zero/AssignIds` proxy that
discards the server
interceptor) is untouched and wants its own PR against `main`.

**Checklist**

- [x] The PR title follows the
[Conventional
Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) syntax,
leading
      with `fix:`, `feat:`, `chore:`, `ci:`, etc.
- [x] Code compiles correctly and linting (via trunk) passes locally
- [x] Tests added for new functionality, or regression tests for bug
fixes added as applicable
- [x] For public APIs, new features, etc., a PR on the
[docs repo](https://github.com/dgraph-io/dgraph-docs) staged and linked
here:
dgraph-io/dgraph-docs#778 (unreleased; lives in the unpublished `docs/`
tree).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/dgraph-io/codesmith/dgraph/pr/9844?autoLogin=true&ref=codesmith_pr_footer"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img
alt="View with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a>
<a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1793555309&installation_model_id=8575&pr_number=9844&ref=codesmith_pr_footer&repository=dgraph-io%2Fdgraph&return_to=https%3A%2F%2Fgithub.com%2Fdgraph-io%2Fdgraph%2Fpull%2F9844&signature=d80fb377d1ad73fa97791de98226b27be51e51925255dd2ad8ab9f9730200b31"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you
need. Autofix is disabled.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added configurable anonymous access modes: `full` preserves existing
behavior, `data` requires an identified caller for administrative
actions, and `none` also requires identity for queries, mutations, and
commits.
* Security tokens can provide caller identity when ACL credentials do
not.
* In stricter modes, administrative endpoints require authentication; IP
allowlisting alone does not grant access.
* Startup warnings flag security configurations that may deny requests
or allow access without credentials.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant