feat: propagate admin audit identity
This commit is contained in:
parent
912db2aa18
commit
363d217c35
@ -34,11 +34,11 @@ type Service interface {
|
||||
ReloadConfiguration(context.Context, ReloadCommand) (MutationResult, error)
|
||||
}
|
||||
|
||||
type Authorizer interface {
|
||||
Check(context.Context, *http.Request) error
|
||||
type IdentityResolver interface {
|
||||
Resolve(*http.Request) (httpsecurity.Identity, error)
|
||||
}
|
||||
|
||||
var _ Authorizer = (*httpsecurity.Protection)(nil)
|
||||
var _ IdentityResolver = (*httpsecurity.Protection)(nil)
|
||||
|
||||
type Options struct {
|
||||
MaxBodyBytes int64
|
||||
@ -80,12 +80,16 @@ type MutationResult struct {
|
||||
|
||||
type SetUpstreamCommand struct {
|
||||
RequestID string
|
||||
ActorID string
|
||||
SourceIP string
|
||||
Name string
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type SwitchCommand struct {
|
||||
RequestID string `json:"-"`
|
||||
ActorID string `json:"-"`
|
||||
SourceIP string `json:"-"`
|
||||
Name string `json:"-"`
|
||||
ExpectedCurrent string `json:"expectedCurrent"`
|
||||
Target string `json:"target"`
|
||||
@ -94,19 +98,21 @@ type SwitchCommand struct {
|
||||
|
||||
type ReloadCommand struct {
|
||||
RequestID string
|
||||
ActorID string
|
||||
SourceIP string
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
service Service
|
||||
authorizer Authorizer
|
||||
identity IdentityResolver
|
||||
maxBodyBytes int64
|
||||
}
|
||||
|
||||
func NewHandler(service Service, authorizer Authorizer, options Options) (*Handler, error) {
|
||||
if service == nil || authorizer == nil || options.MaxBodyBytes <= 0 {
|
||||
func NewHandler(service Service, identity IdentityResolver, options Options) (*Handler, error) {
|
||||
if service == nil || identity == nil || options.MaxBodyBytes <= 0 {
|
||||
return nil, ErrInvalidHandler
|
||||
}
|
||||
return &Handler{service: service, authorizer: authorizer, maxBodyBytes: options.MaxBodyBytes}, nil
|
||||
return &Handler{service: service, identity: identity, maxBodyBytes: options.MaxBodyBytes}, nil
|
||||
}
|
||||
|
||||
func (handler *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
@ -115,7 +121,8 @@ func (handler *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Requ
|
||||
writeTransportProblem(writer, http.StatusBadRequest, "INVALID_REQUEST_ID", "Invalid request ID", "X-Request-ID is invalid", requestID)
|
||||
return
|
||||
}
|
||||
if err := handler.authorizer.Check(request.Context(), request); err != nil {
|
||||
identity, err := handler.identity.Resolve(request)
|
||||
if err != nil {
|
||||
if !httpsecurity.WriteProblem(writer, requestID, err) {
|
||||
writeTransportProblem(writer, http.StatusInternalServerError, "INTERNAL_ERROR", "Internal server error", "the request could not be completed", requestID)
|
||||
}
|
||||
@ -133,7 +140,7 @@ func (handler *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Requ
|
||||
if !requireMethod(writer, request, http.MethodPost, requestID) {
|
||||
return
|
||||
}
|
||||
handler.reload(writer, request, requestID)
|
||||
handler.reload(writer, request, requestID, identity)
|
||||
return
|
||||
}
|
||||
|
||||
@ -141,14 +148,14 @@ func (handler *Handler) ServeHTTP(writer http.ResponseWriter, request *http.Requ
|
||||
if !requireMethod(writer, request, http.MethodPost, requestID) {
|
||||
return
|
||||
}
|
||||
handler.setUpstreamEnabled(writer, request, name, action == "enable", requestID)
|
||||
handler.setUpstreamEnabled(writer, request, name, action == "enable", requestID, identity)
|
||||
return
|
||||
}
|
||||
if name, _, ok := matchNamedAction(request.URL.Path, routingPrefix, "switch"); ok {
|
||||
if !requireMethod(writer, request, http.MethodPost, requestID) {
|
||||
return
|
||||
}
|
||||
handler.switchRouting(writer, request, name, requestID)
|
||||
handler.switchRouting(writer, request, name, requestID, identity)
|
||||
return
|
||||
}
|
||||
|
||||
@ -171,9 +178,18 @@ func (handler *Handler) getStatus(writer http.ResponseWriter, request *http.Requ
|
||||
_ = httpapi.WriteJSON(writer, http.StatusOK, status)
|
||||
}
|
||||
|
||||
func (handler *Handler) setUpstreamEnabled(writer http.ResponseWriter, request *http.Request, name string, enabled bool, requestID string) {
|
||||
func (handler *Handler) setUpstreamEnabled(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
name string,
|
||||
enabled bool,
|
||||
requestID string,
|
||||
identity httpsecurity.Identity,
|
||||
) {
|
||||
result, err := handler.service.SetUpstreamEnabled(request.Context(), SetUpstreamCommand{
|
||||
RequestID: requestID,
|
||||
ActorID: identity.ClientID,
|
||||
SourceIP: identity.SourceIP,
|
||||
Name: name,
|
||||
Enabled: enabled,
|
||||
})
|
||||
@ -184,7 +200,13 @@ func (handler *Handler) setUpstreamEnabled(writer http.ResponseWriter, request *
|
||||
writeMutation(writer, result, requestID)
|
||||
}
|
||||
|
||||
func (handler *Handler) switchRouting(writer http.ResponseWriter, request *http.Request, name, requestID string) {
|
||||
func (handler *Handler) switchRouting(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
name string,
|
||||
requestID string,
|
||||
identity httpsecurity.Identity,
|
||||
) {
|
||||
var command SwitchCommand
|
||||
if err := httpapi.DecodeJSON(writer, request, handler.maxBodyBytes, &command); err != nil {
|
||||
writeDecodeProblem(writer, err, requestID)
|
||||
@ -197,6 +219,8 @@ func (handler *Handler) switchRouting(writer http.ResponseWriter, request *http.
|
||||
return
|
||||
}
|
||||
command.RequestID = requestID
|
||||
command.ActorID = identity.ClientID
|
||||
command.SourceIP = identity.SourceIP
|
||||
command.Name = name
|
||||
result, err := handler.service.SwitchRouting(request.Context(), command)
|
||||
if err != nil {
|
||||
@ -206,8 +230,17 @@ func (handler *Handler) switchRouting(writer http.ResponseWriter, request *http.
|
||||
writeMutation(writer, result, requestID)
|
||||
}
|
||||
|
||||
func (handler *Handler) reload(writer http.ResponseWriter, request *http.Request, requestID string) {
|
||||
result, err := handler.service.ReloadConfiguration(request.Context(), ReloadCommand{RequestID: requestID})
|
||||
func (handler *Handler) reload(
|
||||
writer http.ResponseWriter,
|
||||
request *http.Request,
|
||||
requestID string,
|
||||
identity httpsecurity.Identity,
|
||||
) {
|
||||
result, err := handler.service.ReloadConfiguration(request.Context(), ReloadCommand{
|
||||
RequestID: requestID,
|
||||
ActorID: identity.ClientID,
|
||||
SourceIP: identity.SourceIP,
|
||||
})
|
||||
if err != nil {
|
||||
writeServiceProblem(writer, err, requestID)
|
||||
return
|
||||
|
||||
@ -128,6 +128,9 @@ func TestHandlerEnablesAndDisablesUpstream(t *testing.T) {
|
||||
if service.lastUpstream.Name != "provider-a" || service.lastUpstream.Enabled != test.enabled || service.lastUpstream.RequestID != "req-admin" {
|
||||
t.Fatalf("unexpected service call: %+v", service.lastUpstream)
|
||||
}
|
||||
if service.lastUpstream.ActorID != "admin:test" || service.lastUpstream.SourceIP != "192.0.2.10" {
|
||||
t.Fatalf("upstream actor = (%q, %q)", service.lastUpstream.ActorID, service.lastUpstream.SourceIP)
|
||||
}
|
||||
if requestID := recorder.Header().Get(httpapi.HeaderRequestID); requestID != "req-admin" {
|
||||
t.Fatalf("response request ID = %q, want req-admin", requestID)
|
||||
}
|
||||
@ -152,6 +155,9 @@ func TestHandlerSwitchesRoutingWithStrictJSON(t *testing.T) {
|
||||
if service.lastSwitch.Name != "checkout" || service.lastSwitch.ExpectedCurrent != "provider-a" || service.lastSwitch.Target != "provider-b" {
|
||||
t.Fatalf("unexpected switch call: command=%+v", service.lastSwitch)
|
||||
}
|
||||
if service.lastSwitch.ActorID != "admin:test" || service.lastSwitch.SourceIP != "192.0.2.10" {
|
||||
t.Fatalf("switch actor = (%q, %q)", service.lastSwitch.ActorID, service.lastSwitch.SourceIP)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlerReloadsConfigurationWithCommandRequestID(t *testing.T) {
|
||||
@ -170,6 +176,9 @@ func TestHandlerReloadsConfigurationWithCommandRequestID(t *testing.T) {
|
||||
if service.lastReload.RequestID != "req-reload" {
|
||||
t.Fatalf("reload command = %+v", service.lastReload)
|
||||
}
|
||||
if service.lastReload.ActorID != "admin:test" || service.lastReload.SourceIP != "192.0.2.10" {
|
||||
t.Fatalf("reload actor = (%q, %q)", service.lastReload.ActorID, service.lastReload.SourceIP)
|
||||
}
|
||||
if !strings.Contains(recorder.Body.String(), `"requestId":"req-reload"`) {
|
||||
t.Fatalf("unexpected reload response %q", recorder.Body.String())
|
||||
}
|
||||
@ -298,12 +307,14 @@ func (service *stubService) ReloadConfiguration(_ context.Context, command Reloa
|
||||
|
||||
type allowAuthorizer struct{}
|
||||
|
||||
func (allowAuthorizer) Check(context.Context, *http.Request) error { return nil }
|
||||
func (allowAuthorizer) Resolve(*http.Request) (httpsecurity.Identity, error) {
|
||||
return httpsecurity.Identity{ClientID: "admin:test", SourceIP: "192.0.2.10"}, nil
|
||||
}
|
||||
|
||||
type rejectAuthorizer struct{}
|
||||
|
||||
func (rejectAuthorizer) Check(context.Context, *http.Request) error {
|
||||
return &httpsecurity.HTTPError{
|
||||
func (rejectAuthorizer) Resolve(*http.Request) (httpsecurity.Identity, error) {
|
||||
return httpsecurity.Identity{}, &httpsecurity.HTTPError{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
Code: "UNAUTHORIZED",
|
||||
Header: http.Header{"WWW-Authenticate": []string{`Basic realm="proxy-pool"`}},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user