mirror of
https://github.com/juanfont/headscale.git
synced 2026-08-07 07:48:44 +09:00
hscontrol/api: return a minimal 401 for failed security requirements
ogen's SecurityError message echoes the operation name and internal security text. Map it to a clean 401 so the unauthorized body stays small and leaks nothing.
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/juanfont/headscale/hscontrol/db"
|
||||
"github.com/juanfont/headscale/hscontrol/state"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/ogen-go/ogen/ogenerrors"
|
||||
"github.com/rs/zerolog/log"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -96,6 +97,14 @@ func classify(err error) *oas.ErrorStatusCode {
|
||||
return esc
|
||||
}
|
||||
|
||||
// A failed security requirement (missing/malformed bearer) must not echo
|
||||
// ogen's internal "operation X: security ...: not satisfied" message, which
|
||||
// leaks the operation name. Return a minimal 401.
|
||||
var secErr *ogenerrors.SecurityError
|
||||
if errors.As(err, &secErr) {
|
||||
return apiError(http.StatusUnauthorized, "valid API key required")
|
||||
}
|
||||
|
||||
var coder interface{ Code() int }
|
||||
if errors.As(err, &coder) {
|
||||
return apiError(coder.Code(), err.Error())
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ogen-go/ogen/ogenerrors"
|
||||
)
|
||||
|
||||
var errSecurityNotSatisfied = errors.New(
|
||||
`operation ListUsers: security "": security requirement is not satisfied`,
|
||||
)
|
||||
|
||||
// TestClassifySecurityErrorIsMinimal ensures a failed security requirement
|
||||
// becomes a clean 401 that does not leak ogen's internal operation/security
|
||||
// message.
|
||||
func TestClassifySecurityErrorIsMinimal(t *testing.T) {
|
||||
secErr := &ogenerrors.SecurityError{Err: errSecurityNotSatisfied}
|
||||
|
||||
esc := classify(secErr)
|
||||
|
||||
if esc.StatusCode != http.StatusUnauthorized {
|
||||
t.Fatalf("status = %d, want 401", esc.StatusCode)
|
||||
}
|
||||
|
||||
detail := esc.Response.Detail.Or("")
|
||||
if strings.Contains(detail, "operation") || strings.Contains(detail, "ListUsers") {
|
||||
t.Errorf("401 detail leaks internals: %q", detail)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user