Skip to content

[management, client] Fix SSH server audience validator#5105

Merged
mlsmaycon merged 4 commits intomainfrom
fix/browser-ssh-audience-valiudation
Jan 16, 2026
Merged

[management, client] Fix SSH server audience validator#5105
mlsmaycon merged 4 commits intomainfrom
fix/browser-ssh-audience-valiudation

Conversation

@braginini
Copy link
Copy Markdown
Collaborator

@braginini braginini commented Jan 14, 2026

Describe your changes

Issue ticket number and link

Stack

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor
  • Created tests that fail without the change (if possible)

By submitting this pull request, you confirm that you have read and agree to the terms of the Contributor License Agreement.

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

  • New Features
    • SSH server JWT validation now accepts multiple audiences with backward-compatible handling of the previous single-audience setting and a guard ensuring at least one audience is configured.
  • Tests
    • Test suites updated and new tests added to cover multiple-audience scenarios and compatibility with existing behavior.
  • Other
    • Startup logging enhanced to report configured audiences for JWT auth.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jan 14, 2026

📝 Walkthrough

Walkthrough

Replaces single Audience with Audiences ([]string) across SSH server, engine integration, management proto and conversion, and tests; proto adds audiences while preserving audience for compatibility. Tests updated and a new test validates multiple-audience handling.

Changes

Cohort / File(s) Summary
Proto Schema Updates
shared/management/proto/management.proto
Added repeated string audiences = 5 to JWTConfig; preserved existing audience for backward compatibility.
Core SSH Server Logic
client/ssh/server/server.go
JWTConfig now has Audiences []string; added validation requiring at least one audience; validator and claims extractor updated to use Audiences (claims extraction uses Audiences[0]); logs and error messages updated.
SSH Engine Integration
client/internal/engine_ssh.go
When building JWT config from proto, populate JWTConfig.Audiences from protoJWT.GetAudiences() and fall back to protoJWT.GetAudience() if needed; added startup log showing audiences.
Management Proto Conversion
management/internals/shared/grpc/conversion.go
buildJWTConfig now computes and returns Audiences (includes AuthAudience and CLIAuthAudience when applicable) while preserving the existing Audience behavior.
SSH Tests
client/ssh/server/jwt_test.go, client/ssh/proxy/proxy_test.go
Tests updated to use Audiences: []string{...}; added TestJWTMultipleAudiences validating token validation across multiple audiences.
Conversion Tests
management/internals/shared/grpc/conversion_test.go
Added TestBuildJWTConfig_Audiences covering scenarios for single/double audiences and identical audiences.
Misc / Manifests
go.mod
Minor dependency/version line change.

Sequence Diagram(s)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • pascal-fischer

Poem

🐰 I swapped one string for a merry herd,
Audiences lined up, each voice is heard.
Tokens hop in with many a claim,
Validators nod, the names the same,
SSH gates open — hop aboard, assured!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description uses only the template structure without filling in the 'Describe your changes' section, leaving it completely empty despite substantial modifications across multiple files and packages. Add a detailed description of the changes under 'Describe your changes' section, explaining the shift from single Audience to Audiences slice, the backwards compatibility approach, and why this fixes the SSH audience validation issue.
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title '[management, client] Fix SSH server audience validator' directly relates to the core change of adding multiple audience support to the SSH server's JWT configuration validation.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
client/ssh/server/server.go (1)

443-448: Redundant length check can be simplified.

The check len(config.Audiences) != 0 on line 444 is always true at this point since the function returns early on line 431 if Audiences is empty. The conditional assignment is unnecessary.

♻️ Suggested simplification
-	audience := ""
-	if len(config.Audiences) != 0 {
-		audience = config.Audiences[0]
-	}
 	extractorOptions := []jwt.ClaimsExtractorOption{
-		jwt.WithAudience(audience),
+		jwt.WithAudience(config.Audiences[0]),
 	}

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68d77fc and 977166c.

📒 Files selected for processing (2)
  • client/internal/engine_ssh.go
  • client/ssh/server/server.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • client/internal/engine_ssh.go
🧰 Additional context used
🧬 Code graph analysis (1)
client/ssh/server/server.go (1)
shared/auth/jwt/extractor.go (2)
  • ClaimsExtractorOption (40-40)
  • WithAudience (43-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Android / Build
  • GitHub Check: Linux
  • GitHub Check: Windows
  • GitHub Check: Darwin
  • GitHub Check: release
  • GitHub Check: release_ui_darwin
  • GitHub Check: release_ui
  • GitHub Check: FreeBSD Port / Build & Test
  • GitHub Check: Build Cache
  • GitHub Check: Client / Unit
  • GitHub Check: Client / Unit
  • GitHub Check: Client / Unit
  • GitHub Check: JS / Lint
🔇 Additional comments (3)
client/ssh/server/server.go (3)

177-182: LGTM - Struct updated to support multiple audiences.

The change from Audience string to Audiences []string properly enables multi-audience JWT validation.


434-440: LGTM - JWT validator properly initialized with multiple audiences.

The validator correctly receives the full Audiences slice, enabling validation against any of the configured audiences.


485-486: LGTM - Improved error message for debugging.

The error message now correctly reflects the multi-audience configuration while showing the actual token values, aiding in debugging authentication failures.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

Copy link
Copy Markdown
Contributor

@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: 0

Caution

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

⚠️ Outside diff range comments (1)
client/ssh/server/server.go (1)

426-441: Potential panic if Audiences slice is empty.

Line 440 accesses config.Audiences[0] without verifying the slice is non-empty. If Audiences is nil or has zero length, this will cause a panic during JWT validator initialization.

🐛 Proposed fix
 	if config == nil {
 		return fmt.Errorf("JWT config not set")
 	}
+
+	if len(config.Audiences) == 0 {
+		return fmt.Errorf("JWT config has no audiences configured")
+	}

 	log.Debugf("Initializing JWT validator (issuer: %s, audiences: %v)", config.Issuer, config.Audiences)
🧹 Nitpick comments (1)
management/internals/shared/grpc/conversion.go (1)

432-441: Audiences slice may contain duplicates or empty strings.

When CLIAuthAudience is empty, audience equals AuthAudience, resulting in [AuthAudience, AuthAudience, ""]. When set, the slice becomes [CLIAuthAudience, AuthAudience, CLIAuthAudience] with a duplicate.

Consider deduplicating and filtering empty strings to avoid potential validation issues or confusion in logs.

♻️ Suggested fix
 	audience := config.AuthAudience
 	if config.CLIAuthAudience != "" {
 		audience = config.CLIAuthAudience
 	}
+
+	audiences := []string{}
+	seen := make(map[string]bool)
+	for _, a := range []string{audience, config.AuthAudience, config.CLIAuthAudience} {
+		if a != "" && !seen[a] {
+			audiences = append(audiences, a)
+			seen[a] = true
+		}
+	}
+
 	return &proto.JWTConfig{
 		Issuer:       issuer,
 		Audience:     audience,
-		Audiences:    []string{audience, config.AuthAudience, config.CLIAuthAudience},
+		Audiences:    audiences,
 		KeysLocation: keysLocation,
 	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cac9326 and 7b59b41.

⛔ Files ignored due to path filters (1)
  • shared/management/proto/management.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (5)
  • client/internal/engine_ssh.go
  • client/ssh/server/jwt_test.go
  • client/ssh/server/server.go
  • management/internals/shared/grpc/conversion.go
  • shared/management/proto/management.proto
🧰 Additional context used
🧬 Code graph analysis (1)
client/ssh/server/server.go (1)
shared/auth/jwt/extractor.go (2)
  • ClaimsExtractorOption (40-40)
  • WithAudience (43-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (27)
  • GitHub Check: Management / Benchmark (API) (amd64, sqlite)
  • GitHub Check: Management / Integration (amd64, postgres)
  • GitHub Check: Management / Benchmark (amd64, postgres)
  • GitHub Check: Client (Docker) / Unit
  • GitHub Check: Management / Benchmark (API) (amd64, postgres)
  • GitHub Check: Management / Unit (amd64, postgres)
  • GitHub Check: Management / Unit (amd64, sqlite)
  • GitHub Check: Management / Unit (amd64, mysql)
  • GitHub Check: Relay / Unit (386)
  • GitHub Check: Relay / Unit (amd64, -race)
  • GitHub Check: Management / Benchmark (amd64, sqlite)
  • GitHub Check: Client / Unit (386)
  • GitHub Check: Management / Integration (amd64, sqlite)
  • GitHub Check: Client / Unit (amd64)
  • GitHub Check: Darwin
  • GitHub Check: Windows
  • GitHub Check: Client / Unit
  • GitHub Check: Linux
  • GitHub Check: Client / Unit
  • GitHub Check: Android / Build
  • GitHub Check: Client / Unit
  • GitHub Check: iOS / Build
  • GitHub Check: FreeBSD Port / Build & Test
  • GitHub Check: release_ui_darwin
  • GitHub Check: release_ui
  • GitHub Check: release
  • GitHub Check: JS / Lint
🔇 Additional comments (4)
shared/management/proto/management.proto (1)

249-259: LGTM! Backward-compatible proto schema extension.

The addition of audiences (field 5) while retaining the deprecated audience field ensures backward compatibility with older clients. The deprecation comment clearly indicates the migration path.

client/ssh/server/jwt_test.go (1)

44-48: LGTM! Test configurations correctly updated for multi-audience support.

All test cases consistently use Audiences: []string{...} instead of the old Audience field. The test coverage for JWT enforcement, detection, fail-close, and authentication scenarios looks comprehensive.

client/internal/engine_ssh.go (1)

74-83: LGTM! Correctly maps proto audiences to SSH server config.

The change from GetAudience() to GetAudiences() aligns with the proto schema update. Note that server.go accesses Audiences[0] without a bounds check—ensure the management server always populates at least one audience.

client/ssh/server/server.go (1)

476-479: Good improvement to error diagnostics.

The updated error message clearly shows the expected audiences (from config) versus actual values from the token, which aids debugging authentication failures.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@mlsmaycon mlsmaycon changed the title Fix SSH server audience validator [client] Fix SSH server audience validator Jan 15, 2026
Comment thread client/internal/engine_ssh.go Outdated
@lixmal lixmal changed the title [client] Fix SSH server audience validator [management, client] Fix SSH server audience validator Jan 15, 2026
@sonarqubecloud
Copy link
Copy Markdown

@mlsmaycon mlsmaycon merged commit 1ff7abe into main Jan 16, 2026
41 checks passed
@mlsmaycon mlsmaycon deleted the fix/browser-ssh-audience-valiudation branch January 16, 2026 11:28
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