Skip to content

redis: add credentials provider option and built-in Entra ID auth - #1626

Open
cosmin-staicu wants to merge 5 commits into
livekit:mainfrom
UiPath:feat/redis-entra-credentials-provider
Open

redis: add credentials provider option and built-in Entra ID auth#1626
cosmin-staicu wants to merge 5 commits into
livekit:mainfrom
UiPath:feat/redis-entra-credentials-provider

Conversation

@cosmin-staicu

Copy link
Copy Markdown

Adds support for runtime-rotated Redis credentials to GetRedisClient, with two entry points: a vendor-neutral WithStreamingCredentialsProvider option and a config-driven azure_entra flag for Microsoft Entra ID. Backwards compatible — existing callers and YAML are unaffected.

GetRedisClient currently only accepts a static Username/Password. That doesn't work with managed Redis offerings that authenticate via short-lived identity tokens — Azure Managed Redis and Redis Enterprise Cloud issue Microsoft Entra ID tokens that expire (~1h) and must be refreshed and re-AUTH'd on the live connection before they lapse.

With only static credentials, a long-running deployment gets disconnected when the token expires. It's worst for the coordination bus that LiveKit relies on: managed Redis evicts pub/sub connections when their token expires, and under RESP2 a pub/sub socket can't process an out-of-band AUTH, so it must reconnect with a fresh token. Every component that builds its client through protocol — server, sip, egress, ingress — is affected.

go-redis already supports this through StreamingCredentialsProvider (≥ v9.6; this module is on v9.20.0), and Redis publishes an official extension, go-redis-entraid, that implements

@changeset-bot

changeset-bot Bot commented Jun 15, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 9867e3b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@CLAassistant

CLAassistant commented Jun 15, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@cosmin-staicu
cosmin-staicu force-pushed the feat/redis-entra-credentials-provider branch from 63e17d0 to 3135f36 Compare August 4, 2026 14:27
@cosmin-staicu
cosmin-staicu force-pushed the feat/redis-entra-credentials-provider branch from 3135f36 to e227b1a Compare August 19, 2026 13:19
cosmin-staicu and others added 4 commits September 3, 2026 14:50
…start

The pieces for changing levels at runtime were already here — the root level and
every component level are zap.AtomicLevel, Config.Update pushes new values into
them, and newSharedConfig registers itself as an update observer — but nothing
ever called Update, so a level change meant restarting the process.

Add the missing trigger: when LK_LOG_CONFIG_PATH is set, poll that file and apply
it to the Config the service is holding (LK_LOG_CONFIG_INTERVAL overrides the
30s default). The hook sits in newSharedConfig, which is the one path every
consumer reaches — livekit-server via InitFromConfig, livekit-sip via
NewZapLogger — so no binary needs its own flag or call site.

Polling rather than fsnotify because the target is a mounted ConfigMap: kubelet
swaps the ..data symlink instead of rewriting the file, so a watch on the file
never fires.

Two details that would otherwise bite:

- Update assigns every field, so a partial file decoded into a zero Config would
  silently reset the rest. Config.snapshot copies the data fields and the file is
  unmarshalled over that, leaving unspecified keys — including ComponentLevels,
  where livekit-server puts pion_level — as they were.

- sharedConfig kept the caller's live *Config and read ComponentLevels from it
  under its own mutex, while Update writes those fields under Config.lock. Two
  mutexes over the same memory was harmless while Update was unreachable; now it
  is reachable, so sharedConfig holds a snapshot it owns instead.

An unreadable file (an optional ConfigMap not yet mounted), unchanged bytes and
malformed YAML all leave the config in force untouched, without logging once per
interval.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgV5yPwbxmFhMuN62ybthK
Signed-off-by: Cosmin Staicu <cosmin.staicu@uipath.com>
feat(logger): apply a log config file to the live logger without a restart
…ast one

Emptying the watched file was supposed to restore the levels the process booted
with. It did not: each file was decoded over a snapshot of the config currently in
force, so once `level: debug` had been applied, an empty file snapshotted debug and
changed nothing. A component_levels entry that disappeared from the file kept
applying for the same reason.

WatchConfigFile now snapshots the startup config once and applyConfigFile decodes
every file over that baseline, which makes the file a declarative overlay: keys it
omits fall back to the startup values.

A file that goes away is still not a reset. A transient read error would otherwise
flap levels on a live process, so the reset is emptying the file to `{}`, which now
works.

Reported by Copilot on UiPath/cx#823.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FgV5yPwbxmFhMuN62ybthK
Signed-off-by: Cosmin Staicu <cosmin.staicu@uipath.com>
fix(logger): apply the config file over the startup config, not the last one
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.

2 participants