auth: accept oauth2-proxy forwarded headers
This commit is contained in:
parent
09ab3ec889
commit
98f1166685
@ -121,8 +121,11 @@ When `SOTERIA_AUTH_REQUIRED=true`, Soteria expects trusted auth headers from a f
|
||||
- `X-Auth-Request-User`
|
||||
- `X-Auth-Request-Email`
|
||||
- `X-Auth-Request-Groups`
|
||||
- `X-Forwarded-User` (fallback)
|
||||
- `X-Forwarded-Email` (fallback)
|
||||
- `X-Forwarded-Groups` (fallback)
|
||||
|
||||
Allowed groups are configured with `SOTERIA_ALLOWED_GROUPS` and compared after normalizing leading `/` prefixes, so both `maintenance` and `/maintenance` are accepted.
|
||||
Allowed groups are configured with `SOTERIA_ALLOWED_GROUPS` and compared after normalizing leading `/` prefixes, so both `maintenance` and `/maintenance` are accepted. Group lists may be comma- or semicolon-separated.
|
||||
|
||||
Optional machine-to-machine access can be enabled with `SOTERIA_AUTH_BEARER_TOKENS`, which accepts a comma-separated list of bearer tokens.
|
||||
|
||||
|
||||
@ -564,9 +564,9 @@ func (s *Server) authorize(r *http.Request) (authIdentity, int, error) {
|
||||
|
||||
identity := authIdentity{
|
||||
Authenticated: true,
|
||||
User: strings.TrimSpace(r.Header.Get("X-Auth-Request-User")),
|
||||
Email: strings.TrimSpace(r.Header.Get("X-Auth-Request-Email")),
|
||||
Groups: normalizeGroups(strings.Split(strings.TrimSpace(r.Header.Get("X-Auth-Request-Groups")), ",")),
|
||||
User: firstHeader(r, "X-Auth-Request-User", "X-Forwarded-User"),
|
||||
Email: firstHeader(r, "X-Auth-Request-Email", "X-Forwarded-Email"),
|
||||
Groups: normalizeGroups(splitGroups(firstHeader(r, "X-Auth-Request-Groups", "X-Forwarded-Groups"))),
|
||||
}
|
||||
if identity.User == "" && identity.Email == "" {
|
||||
return authIdentity{}, http.StatusUnauthorized, fmt.Errorf("authentication required")
|
||||
@ -638,6 +638,26 @@ func normalizeGroups(values []string) []string {
|
||||
return groups
|
||||
}
|
||||
|
||||
func firstHeader(r *http.Request, names ...string) string {
|
||||
for _, name := range names {
|
||||
value := strings.TrimSpace(r.Header.Get(name))
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func splitGroups(raw string) []string {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
return strings.FieldsFunc(raw, func(r rune) bool {
|
||||
return r == ',' || r == ';'
|
||||
})
|
||||
}
|
||||
|
||||
func buildBackupRecords(backups []longhorn.Backup) []api.BackupRecord {
|
||||
records := make([]api.BackupRecord, 0, len(backups))
|
||||
latestName := ""
|
||||
|
||||
@ -120,6 +120,26 @@ func TestProtectedInventoryAllowsMaintenanceGroup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestProtectedInventoryAllowsForwardedHeaders(t *testing.T) {
|
||||
srv := &Server{
|
||||
cfg: &config.Config{AuthRequired: true, AllowedGroups: []string{"admin", "maintenance"}, BackupDriver: "longhorn", BackupMaxAge: 24 * time.Hour},
|
||||
client: &fakeKubeClient{pvcs: []k8s.PVCSummary{{Namespace: "apps", Name: "data", VolumeName: "pv-apps-data", Phase: "Bound"}}},
|
||||
longhorn: &fakeLonghornClient{backups: []longhorn.Backup{{Name: "backup-1", Created: "2026-04-12T00:00:00Z", State: "Completed", URL: "s3://bucket/backup-1"}}},
|
||||
metrics: newTelemetry(),
|
||||
}
|
||||
srv.handler = http.HandlerFunc(srv.route)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1/inventory", nil)
|
||||
req.Header.Set("X-Forwarded-User", "brad")
|
||||
req.Header.Set("X-Forwarded-Groups", "/ops;/maintenance")
|
||||
res := httptest.NewRecorder()
|
||||
srv.Handler().ServeHTTP(res, req)
|
||||
|
||||
if res.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d: %s", res.Code, res.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreRejectsExistingTargetPVC(t *testing.T) {
|
||||
srv := &Server{
|
||||
cfg: &config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user