Skip to content

Switch to reader and writer auth#25

Merged
KevinGruber2001 merged 4 commits intomainfrom
add-authorization-support
Feb 15, 2026
Merged

Switch to reader and writer auth#25
KevinGruber2001 merged 4 commits intomainfrom
add-authorization-support

Conversation

@KevinGruber2001
Copy link
Copy Markdown
Collaborator

@KevinGruber2001 KevinGruber2001 commented Feb 10, 2026

Summary by CodeRabbit

  • New Features

    • Role-based authentication: distinct reader and writer credentials; reads use reader access, writes require writer access
    • Per-route enforcement so write endpoints require elevated permission
  • Chores

    • Helm chart and configuration updated to manage separate reader/writer secrets and values
    • Chart version bumped to 0.3.1

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

Restructures auth from a single-user model to role-based reader/writer credentials across Helm charts and application code; adds separate Kubernetes secrets for reader/writer, updates deployment env bindings, refactors config and middleware to enforce role-aware auth, and bumps chart version to 0.3.1.

Changes

Cohort / File(s) Summary
Chart Version Bump
chart/Chart.yaml
Version updated from 0.3.0 to 0.3.1.
Configuration Values
chart/values.yaml
Auth values restructured from flat username/password to nested reader and writer blocks with separate credentials.
Helm Templates: Config & Deployment
chart/templates/configmap.yaml, chart/templates/deployment.yaml
ConfigMap now emits two users (reader, writer); deployment env vars changed from CACHE_PASSWORD to CACHE_READER_PASSWORD and CACHE_WRITER_PASSWORD, sourcing new secrets.
Helm Templates: Secrets
chart/templates/auth-secrets.yaml, chart/templates/redis-secret.yaml
New template generates {{ .Release.Name }}-cache-reader and ...-cache-writer secrets; redis-secret template no longer includes cache-password key.
Application Config
src/internal/config/config.go
AuthConfig.Users removed; AuthConfig.Reader and AuthConfig.Writer added; validation and env bindings updated to require both reader and writer creds when auth enabled.
Authentication Middleware
src/internal/middleware/auth.go
Added CacheAuth(auth config.AuthConfig, requireWriter bool) implementing constant-time checks for reader/writer and returning 401/403 as appropriate; replaces previous basic-auth approach.
Server Routing
src/internal/server/server.go
Routes updated to apply per-route auth: GET/HEAD use reader-level (requireWriter=false), PUT uses writer-level (requireWriter=true); added cacheAuth() helper to conditionally apply middleware.
Manifest
go.mod
Referenced in manifest summary; no API/public signature changes beyond auth structs noted above.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Server
    participant Middleware as CacheAuth
    participant Config as AuthConfig
    Client->>Server: HTTP request (method, credentials)
    Server->>Middleware: invoke CacheAuth(requireWriter?)
    Middleware->>Config: compare creds with Reader/Writer
    alt creds valid & role allowed
        Middleware->>Server: allow request
        Server->>Client: 200/2xx response
    else creds invalid
        Middleware->>Server: deny (401)
        Server->>Client: 401 Unauthorized (WWW-Authenticate)
    else creds valid but insufficient role
        Middleware->>Server: deny (403)
        Server->>Client: 403 Forbidden
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

🐰 Two burrows now guard the cache, reader and writer take the stage,
I nibble keys and hop with glee, secrets tucked on every page,
One may peek, the other may write, both cozy in their place,
Hops of code and careful checks — a snug, secured embrace.

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: transitioning from a flat authentication model to a reader/writer role-based model across configuration, middleware, and Helm templates.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch add-authorization-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 10, 2026

📦 Chart Preview Ready

helm install test oci://ghcr.io/ls1intum/charts/theia-shared-cache --version 0.3.1-pr.25

Updated: 2383f38

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/internal/config/config.go (1)

157-163: ⚠️ Potential issue | 🟠 Major

Missing validation for the Role field on UserAuth.

Validate() checks Username and Password but not Role. If a user entry has an empty role (e.g., omitted in config), the RequireRole("writer") middleware will silently deny all writes with 403, making the issue hard to diagnose.

🛡️ Proposed fix
 	for i, user := range c.Auth.Users {
 		if user.Username == "" {
 			return fmt.Errorf("auth.users[%d].username is required", i)
 		}
 		if user.Password == "" {
 			return fmt.Errorf("auth.users[%d].password is required", i)
 		}
+		if user.Role == "" {
+			return fmt.Errorf("auth.users[%d].role is required", i)
+		}
 	}
🤖 Fix all issues with AI agents
In `@chart/values.yaml`:
- Around line 19-21: Fix the typos in the comments near the password entry:
change "optimaly" to "optimally" and "envrionment" to "environment" in the
comment lines surrounding the password: "changeme-reader" (these comment lines
appear twice). Leave the password value unchanged but update both occurrences of
the misspelled words so the comment reads e.g. "optimally from github
environment secrets".

In `@src/internal/config/config.go`:
- Around line 120-122: The current env-override uses fixed indices via
overrideUserFromEnv(&cfg, 0, ...) and overrideUserFromEnv(&cfg, 1, ...) which is
brittle if cfg.Auth.Users order or length changes; change the logic to locate
the user by role (e.g., "reader" and "writer") instead of by index: implement a
new helper (or modify overrideUserFromEnv) to iterate cfg.Auth.Users, match on
Users[i].Role, and then set Users[i].Username and Users[i].Password from the
corresponding env vars if present, returning after a match so it safely skips
missing roles.
🧹 Nitpick comments (1)
src/internal/middleware/auth.go (1)

59-67: Unsafe type assertion could panic if context value is unexpected.

Line 62 uses a direct type assertion role.(string) which will panic if the value stored under "role" is not a string. While in the current flow BasicAuth always sets a string, a defensive comma-ok pattern is safer against future middleware changes.

♻️ Defensive fix
 func RequireRole(requiredRole string) gin.HandlerFunc {
 	return func(c *gin.Context) {
 		role, exists := c.Get("role")
-		if !exists || role.(string) != requiredRole {
+		roleStr, ok := role.(string)
+		if !exists || !ok || roleStr != requiredRole {
 			c.AbortWithStatus(http.StatusForbidden)
 			return
 		}
 		c.Next()
 	}
 }

@KevinGruber2001 KevinGruber2001 moved this from Milestone 1 to In Progress in Scalable Cloud Tools Project Feb 10, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
chart/templates/deployment.yaml (1)

22-22: ⚠️ Potential issue | 🟠 Major

Checksum annotation doesn't cover the new auth secrets — credential changes won't trigger a rollout.

Line 22 computes a checksum from secrets.yaml, but reader/writer credentials live in auth-secrets.yaml. If auth credentials are rotated, pods won't restart automatically.

Proposed fix: add a checksum for auth-secrets
         checksum/secret: {{ include (print $.Template.BasePath "/secrets.yaml") . | sha256sum }}
+        {{- if .Values.auth.enabled }}
+        checksum/auth-secret: {{ include (print $.Template.BasePath "/auth-secrets.yaml") . | sha256sum }}
+        {{- end }}
🤖 Fix all issues with AI agents
In `@chart/templates/auth-secrets.yaml`:
- Line 1: The auth secrets template is guarded by .Values.enabled but not
.Values.auth.enabled, so secrets are created when the chart is enabled even if
auth is disabled; update the top-level conditional in the auth-secrets template
to also require .Values.auth.enabled (e.g. change the if to test both
.Values.enabled and .Values.auth.enabled or replace with .Values.auth.enabled
alone) so the Secret resource is only rendered when auth is actually enabled;
ensure references to the template (and any include calls) still work with the
updated condition.

In `@src/internal/middleware/auth.go`:
- Around line 11-12: Update the stale doc comment that references BasicAuth to
correctly describe CacheAuth: find the top-of-file or above the CacheAuth
function declaration (symbol CacheAuth) in auth.go and replace "BasicAuth" with
"CacheAuth" and adjust wording if necessary so the comment accurately describes
that the middleware validates cached authentication and stores the user's role
in the gin context.
🧹 Nitpick comments (2)
chart/Chart.yaml (1)

8-8: Consider a minor version bump for this breaking change.

Switching from a single-user auth model to reader/writer roles changes the secret structure, environment variables, and values schema. This is a breaking change for existing deployments — a bump to 0.4.0 would better signal incompatibility to consumers.

chart/templates/auth-secrets.yaml (1)

12-13: Plaintext credentials in values.yaml are double-base64-encoded if users supply pre-encoded values.

Using b64enc on the data field is correct when values.yaml contains plaintext credentials. However, consider documenting that values must be plaintext (not pre-encoded), or alternatively use stringData instead of data to avoid the b64enc call entirely and let Kubernetes handle encoding. This is less error-prone.

Simpler alternative using stringData
-data:
-  username: {{ .Values.auth.reader.username | b64enc | quote }}
-  password: {{ .Values.auth.reader.password | b64enc | quote }}
+stringData:
+  username: {{ .Values.auth.reader.username | quote }}
+  password: {{ .Values.auth.reader.password | quote }}

Same change for the writer secret.

Comment on lines +11 to +12
// BasicAuth creates a middleware that validates HTTP Basic Authentication
// and stores the user's role in the gin context.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Stale doc comment references BasicAuth instead of CacheAuth.

Fix
-// BasicAuth creates a middleware that validates HTTP Basic Authentication
-// and stores the user's role in the gin context.
+// CacheAuth creates a middleware that validates HTTP Basic Authentication
+// with role-based access control (reader/writer).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// BasicAuth creates a middleware that validates HTTP Basic Authentication
// and stores the user's role in the gin context.
// CacheAuth creates a middleware that validates HTTP Basic Authentication
// with role-based access control (reader/writer).
🤖 Prompt for AI Agents
In `@src/internal/middleware/auth.go` around lines 11 - 12, Update the stale doc
comment that references BasicAuth to correctly describe CacheAuth: find the
top-of-file or above the CacheAuth function declaration (symbol CacheAuth) in
auth.go and replace "BasicAuth" with "CacheAuth" and adjust wording if necessary
so the comment accurately describes that the middleware validates cached
authentication and stores the user's role in the gin context.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
chart/templates/auth-secrets.yaml (1)

12-13: Consider using required to fail fast on missing credentials.

If any of the auth credential values are unset or empty in values.yaml, Helm will silently render an empty base64-encoded string. Using required would surface misconfigurations at helm template / helm install time rather than at runtime.

Proposed fix
-  username: {{ .Values.auth.reader.username | b64enc | quote }}
-  password: {{ .Values.auth.reader.password | b64enc | quote }}
+  username: {{ required "auth.reader.username is required" .Values.auth.reader.username | b64enc | quote }}
+  password: {{ required "auth.reader.password is required" .Values.auth.reader.password | b64enc | quote }}

Apply the same pattern to the writer secret (lines 25-26).

src/internal/middleware/auth.go (1)

22-26: Timing side-channel: username compared with == and short-circuit skips the constant-time password check.

The && short-circuits, so when the username doesn't match, ConstantTimeCompare is never called. An attacker can distinguish "wrong username" (fast) from "right username, wrong password" (slower due to ConstantTimeCompare). This partially negates the benefit of the constant-time comparison.

For a build cache this is low risk, but if you want full timing-attack resistance, compare both username and password in constant time and combine the results:

Proposed fix
-		isReader := username == auth.Reader.Username &&
-			subtle.ConstantTimeCompare([]byte(password), []byte(auth.Reader.Password)) == 1
-		isWriter := username == auth.Writer.Username &&
-			subtle.ConstantTimeCompare([]byte(password), []byte(auth.Writer.Password)) == 1
+		isReader := subtle.ConstantTimeCompare([]byte(username), []byte(auth.Reader.Username)) == 1 &&
+			subtle.ConstantTimeCompare([]byte(password), []byte(auth.Reader.Password)) == 1
+		isWriter := subtle.ConstantTimeCompare([]byte(username), []byte(auth.Writer.Username)) == 1 &&
+			subtle.ConstantTimeCompare([]byte(password), []byte(auth.Writer.Password)) == 1

Note: && still short-circuits in Go, so you'd need to evaluate both and combine to be fully constant-time:

readerUser := subtle.ConstantTimeCompare([]byte(username), []byte(auth.Reader.Username))
readerPass := subtle.ConstantTimeCompare([]byte(password), []byte(auth.Reader.Password))
isReader := readerUser & readerPass == 1

writerUser := subtle.ConstantTimeCompare([]byte(username), []byte(auth.Writer.Username))
writerPass := subtle.ConstantTimeCompare([]byte(password), []byte(auth.Writer.Password))
isWriter := writerUser & writerPass == 1

@KevinGruber2001 KevinGruber2001 moved this from In Progress to Done in Scalable Cloud Tools Project Feb 15, 2026
@KevinGruber2001 KevinGruber2001 merged commit 4f2010e into main Feb 15, 2026
4 checks passed
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.

1 participant