Skip to content

feat: [stable-8.0][OCSRE-1629] add NATS JWT authentication with a .creds file - #13220

Draft
2403905 wants to merge 13 commits into
stable-8.0from
OCSRE-1629-nats-creds
Draft

2403905 wants to merge 13 commits into
stable-8.0from
OCSRE-1629-nats-creds

Conversation

@2403905

@2403905 2403905 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Description

A NATS server in operator (JWT) mode authenticates a client with a user JWT and an NKey seed. Before this change, oCIS could connect to NATS only with a username and a password. Thus oCIS could not use a shared NATS cluster with one NATS account for each instance.

This PR adds JWT authentication for all NATS connections: events, caches, persistent stores, the service registry, and the health checks.

New configuration

Variable Content
OCIS_NATS_CREDS_FILE The path of a NATS .creds file (nats.UserCredentials). The client reads the file again on each reconnect, so a rotated JWT works without a restart.
OCIS_NATS_CREDS The content of a .creds file (nats.UserCredentialBytes). Use it when you cannot mount a file. It takes precedence over OCIS_NATS_CREDS_FILE.
  • All services and the service registry use only these two global variables. There are no service-specific variables for the credentials.
  • The registry reads only environment variables, not the ocis.yaml file.
  • When JWT credentials are set, oCIS does not send the username and the password.
  • oCIS checks the credentials at startup. An invalid value gives a clear error.
  • The embedded nats service does not support JWT mode. Use an external NATS cluster and OCIS_EXCLUDE_RUN_SERVICES=nats. services/nats/README.md describes the steps.

Changes

  • Config: new CredsFile and Creds fields in 17 events structs, 18 cache and store structs, and the shared cache. Creds has the mask:"password" tag.
  • store.Credentials(...) next to each store.Authentication(...) call. The revaconfig maps pass the new reva keys.
  • ocis-pkg/natsjsregistry/registry.go: JWT credentials for the registry.
  • ocis-pkg/checks/checknats.go: the new NewNatsCheckWithConfig uses the authentication and TLS settings of the service. Before this change, the NATS health checks connected without credentials and without TLS.
  • Proxy health check: it used the events cluster ID as the NATS address. It now uses the events endpoint.
  • Proxy debug server: it now masks all secret configuration values. A new test checks this.
  • nats.go v1.54.0, go 1.26.10.

Dependencies

go.mod uses a temporary replace to github.com/2403905/plugins. Do not merge before kobergj/plugins#3 and owncloud/reva#782 are merged and go.mod points to the final commits.

How Has This Been Tested?

  • Unit tests:
    • ocis-pkg/checks: health check against a NATS server in operator mode, with the creds file, the creds content, username and password, and invalid creds
    • ocis-pkg/natsjsregistry: the registry reads the global variables, and it checks the credentials only at startup
    • services/proxy/pkg/server/debug: the debug output masks every secret value
  • reva and plugins: integration tests against a NATS server in operator mode with JetStream.
  • Manual test with the single binary:
    • An external nats-server in operator mode.
    • ocis server with OCIS_EXCLUDE_RUN_SERVICES=nats, OCIS_CACHE_STORE=nats-js-kv, and OCIS_PERSISTENT_STORE=nats-js-kv.
    • It ran once with OCIS_NATS_CREDS and once with OCIS_NATS_CREDS_FILE.
    • Result: no authorization errors, 10 KV buckets and the events stream in the JWT account, all events processed, and the readiness checks return 200. Upload and download work.

Types of changes

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)

Checklist

  • Code changes
  • Unit tests added
  • Documentation added (services/nats/README.md)
  • Changelog added

🤖 Generated with Claude Code

2403905 and others added 8 commits October 23, 2026 13:59
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A NATS server in operator (JWT) mode authenticates a client with a
user JWT and an NKey seed. oCIS could connect to NATS only with a
username and a password.

All NATS connections now accept JWT credentials: events, caches,
persistent stores, the service registry, and the health checks.

- OCIS_NATS_CREDS_FILE: the path of a .creds file
  (nats.UserCredentials). The file is read again on each reconnect.
- OCIS_NATS_CREDS: the content of a .creds file
  (nats.UserCredentialBytes). It takes precedence over the file.
- Each events, cache and store struct has the fields CredsFile and
  Creds with a per-service override, for example
  PROXY_EVENTS_CREDS_FILE.
- The registry reads MICRO_REGISTRY_AUTH_CREDS_FILE and
  MICRO_REGISTRY_AUTH_CREDS. If they are empty, it uses the
  OCIS_NATS_* variables.
- store.Credentials is passed next to each store.Authentication call.
  The revaconfig maps pass the new reva keys.
- The new checks.NewNatsCheckWithConfig uses the authentication and
  TLS settings of the service. The NATS health checks connected
  without credentials and without TLS before.

This change needs the reva commit with pkg/natsauth and the natsjs
plugin with the NatsOptions option. go.mod and vendor/ follow when
these changes are pushed.

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

The proxy health check used Events.Cluster, the cluster ID of the
event system, as the NATS address. The check thus never reached the
NATS server.

The check now uses Events.Endpoint. The proxy uses NATS only to
publish events, and an empty endpoint disables the events. So the
check returns no error when the endpoint is empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-users streams

The search service and the storage-users event stream build
stream.NatsConfig field by field. They did not set CredsFile and
Creds, so these two connections did not use the JWT credentials.
The NATS server rejected them with "Authorization Violation".

A test of "ocis server" against a NATS server in operator mode
found this. The compiler cannot find a missing field in a struct
literal.

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

The nats service README did not tell that the embedded nats service
cannot run in operator (JWT) mode. Add a section that describes the
.creds variables, the registry variables, and the steps to use an
external NATS cluster in JWT mode. Add the same note to the changelog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
go-masker copies a field without a mask tag unchanged, also a nested
struct. A mask:"password" tag thus has an effect only when every
parent field on the path has mask:"struct". Several parent fields did
not have this tag, and some secret fields had no mask tag.

- Add mask:"struct" to the proxy fields OIDC, OIDC.UserinfoCache,
  PreSignedURL, PreSignedURL.SigningKeys, ServiceAccount and Events,
  and to the shared Commons.Cache field.
- Add mask:"password" to the AuthPassword fields of the proxy and of
  the shared cache, and to ServiceAccountSecret.
- Add a test that sets a marker in every secret field of the proxy
  config and checks that the debug output does not show it. The test
  also fails when a secret-like field has no mask:"password" tag.

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

kw-security commented Oct 23, 2026 •

Copy link
Copy Markdown

✅ Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
✅ Open Source Security 0 0 0 0 0 issues
✅ Licenses 0 0 0 0 0 issues
✅ Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

2403905 and others added 4 commits October 23, 2026 17:42
Each config has two fields for the NATS credentials: CredsFile
(OCIS_NATS_CREDS_FILE;<SERVICE>_..._CREDS_FILE) and Creds
(OCIS_NATS_CREDS;<SERVICE>_..._CREDS). envdecode sets each field on
its own, and the content always won. Example: OCIS_NATS_CREDS set
globally and GRAPH_CACHE_CREDS_FILE set for the service. The graph
cache then used the global content and ignored the service file.

The registry already used a different rule: the registry variables
take precedence over the global variables.

Use the registry rule everywhere. After envdecode decodes a struct,
it resolves each CredsFile/Creds pair: when the file comes from a
more specific environment variable than the content, the content is
cleared. In the same scope the content still wins. A value from the
config file has the lowest scope, because environment variables
override the config file.

Update the descriptions of the Creds fields, the nats README, and the
changelog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
storeOptions checked the NATS credentials and called os.Exit(1) on an
error. ReconnectedCB calls Init, and Init runs storeOptions again. If
a reconnect happened while a rotated .creds file was only partly
written, the service stopped. In the same path, nats.UserCredentials
reads the file once when the option is applied. When this read
failed, the error was ignored and the connection had no credentials.

Now only the first call checks the credentials, at startup. Later
calls do not check the file. For a file, they set the JWT callbacks
without a read. The callbacks read the file on each connect, so a
partly written file fails only that connect attempt, and NATS
retries it.

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

The create home cache of the gateway (create_personal_space_cache_config)
got the NATS credentials, but it had no TLS settings. With TLS on the
NATS server, this cache could not connect.

Add CreateHomeCacheEnableTLS, CreateHomeCacheTLSInsecure and
CreateHomeCacheTLSRootCACertificate with the same variables as the
provider cache, and pass them to reva.

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

Copy link
Copy Markdown
Contributor

I gave it a test with this helmfile based testbed https://github.com/owncloud-koko/ocis-charts/pull/70 and it runs fine for me so far.

Is there anything specific I can test?

Remove the service-specific NATS credentials variables. Only
OCIS_NATS_CREDS_FILE and OCIS_NATS_CREDS remain. The service registry
also uses these two variables and no longer reads
MICRO_REGISTRY_AUTH_CREDS_FILE and MICRO_REGISTRY_AUTH_CREDS.

Remove the envdecode logic that let the more specific scope win,
because only one scope remains.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants