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-User`
|
||||||
- `X-Auth-Request-Email`
|
- `X-Auth-Request-Email`
|
||||||
- `X-Auth-Request-Groups`
|
- `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.
|
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{
|
identity := authIdentity{
|
||||||
Authenticated: true,
|
Authenticated: true,
|
||||||
User: strings.TrimSpace(r.Header.Get("X-Auth-Request-User")),
|
User: firstHeader(r, "X-Auth-Request-User", "X-Forwarded-User"),
|
||||||
Email: strings.TrimSpace(r.Header.Get("X-Auth-Request-Email")),
|
Email: firstHeader(r, "X-Auth-Request-Email", "X-Forwarded-Email"),
|
||||||
Groups: normalizeGroups(strings.Split(strings.TrimSpace(r.Header.Get("X-Auth-Request-Groups")), ",")),
|
Groups: normalizeGroups(splitGroups(firstHeader(r, "X-Auth-Request-Groups", "X-Forwarded-Groups"))),
|
||||||
}
|
}
|
||||||
if identity.User == "" && identity.Email == "" {
|
if identity.User == "" && identity.Email == "" {
|
||||||
return authIdentity{}, http.StatusUnauthorized, fmt.Errorf("authentication required")
|
return authIdentity{}, http.StatusUnauthorized, fmt.Errorf("authentication required")
|
||||||
@ -638,6 +638,26 @@ func normalizeGroups(values []string) []string {
|
|||||||
return groups
|
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 {
|
func buildBackupRecords(backups []longhorn.Backup) []api.BackupRecord {
|
||||||
records := make([]api.BackupRecord, 0, len(backups))
|
records := make([]api.BackupRecord, 0, len(backups))
|
||||||
latestName := ""
|
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) {
|
func TestRestoreRejectsExistingTargetPVC(t *testing.T) {
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
cfg: &config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
cfg: &config.Config{AuthRequired: false, BackupDriver: "longhorn"},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user