feat: basic authorization model
This commit is contained in:
@ -16,9 +16,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ErrCodeUnknownError api.ErrorCode = "unknown-error"
|
||||
ErrCodeNotFound api.ErrorCode = "not-found"
|
||||
ErrInvalidSignature api.ErrorCode = "invalid-signature"
|
||||
ErrCodeUnknownError api.ErrorCode = "unknown-error"
|
||||
ErrCodeNotFound api.ErrorCode = "not-found"
|
||||
ErrCodeInvalidSignature api.ErrorCode = "invalid-signature"
|
||||
ErrCodeConflict api.ErrorCode = "conflict"
|
||||
)
|
||||
|
||||
type registerAgentRequest struct {
|
||||
@ -46,6 +47,8 @@ func (s *Server) registerAgent(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx = logger.With(ctx, logger.F("agentThumbprint", registerAgentReq.Thumbprint))
|
||||
|
||||
// Validate that the existing signature validates the request
|
||||
|
||||
validSignature, err := jwk.Verify(keySet, registerAgentReq.Signature, registerAgentReq.Thumbprint, registerAgentReq.Metadata)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not validate signature", logger.E(errors.WithStack(err)))
|
||||
@ -55,8 +58,8 @@ func (s *Server) registerAgent(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if !validSignature {
|
||||
logger.Error(ctx, "invalid signature", logger.F("signature", registerAgentReq.Signature))
|
||||
api.ErrorResponse(w, http.StatusBadRequest, ErrInvalidSignature, nil)
|
||||
logger.Error(ctx, "conflicting signature", logger.F("signature", registerAgentReq.Signature))
|
||||
api.ErrorResponse(w, http.StatusConflict, ErrCodeConflict, nil)
|
||||
|
||||
return
|
||||
}
|
||||
@ -97,6 +100,28 @@ func (s *Server) registerAgent(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
agentID := agents[0].ID
|
||||
|
||||
agent, err = s.agentRepo.Get(ctx, agentID)
|
||||
if err != nil {
|
||||
logger.Error(
|
||||
ctx, "could not retrieve agent",
|
||||
logger.E(errors.WithStack(err)), logger.F("agentID", agentID),
|
||||
)
|
||||
api.ErrorResponse(w, http.StatusInternalServerError, ErrCodeUnknownError, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
validSignature, err = jwk.Verify(agent.KeySet.Set, registerAgentReq.Signature, registerAgentReq.Thumbprint, registerAgentReq.Metadata)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not validate signature using previous keyset", logger.E(errors.WithStack(err)))
|
||||
|
||||
api.ErrorResponse(w, http.StatusConflict, ErrCodeConflict, nil)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
agent, err = s.agentRepo.Update(
|
||||
ctx, agents[0].ID,
|
||||
datastore.WithAgentUpdateKeySet(keySet),
|
||||
|
155
internal/server/authorization.go
Normal file
155
internal/server/authorization.go
Normal file
@ -0,0 +1,155 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/auth"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/auth/agent"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/auth/thirdparty"
|
||||
"github.com/pkg/errors"
|
||||
"gitlab.com/wpetit/goweb/api"
|
||||
"gitlab.com/wpetit/goweb/logger"
|
||||
)
|
||||
|
||||
var ErrCodeForbidden api.ErrorCode = "forbidden"
|
||||
|
||||
func assertGlobalReadAccess(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
reqUser, ok := assertRequestUser(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch user := reqUser.(type) {
|
||||
case *thirdparty.User:
|
||||
role := user.Role()
|
||||
if role == thirdparty.RoleReader || role == thirdparty.RoleWriter {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case *agent.User:
|
||||
// Agents dont have global read access
|
||||
|
||||
default:
|
||||
logUnexpectedUserType(r.Context(), reqUser)
|
||||
}
|
||||
|
||||
forbidden(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func assertAgentWriteAccess(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
reqUser, ok := assertRequestUser(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
agentID, ok := getAgentID(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch user := reqUser.(type) {
|
||||
case *thirdparty.User:
|
||||
role := user.Role()
|
||||
if role == thirdparty.RoleWriter {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case *agent.User:
|
||||
if user.Agent().ID == agentID {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
logUnexpectedUserType(r.Context(), reqUser)
|
||||
}
|
||||
|
||||
forbidden(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func assertAgentReadAccess(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
reqUser, ok := assertRequestUser(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
agentID, ok := getAgentID(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
switch user := reqUser.(type) {
|
||||
case *thirdparty.User:
|
||||
role := user.Role()
|
||||
if role == thirdparty.RoleReader || role == thirdparty.RoleWriter {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case *agent.User:
|
||||
if user.Agent().ID == agentID {
|
||||
h.ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
default:
|
||||
logUnexpectedUserType(r.Context(), reqUser)
|
||||
}
|
||||
|
||||
forbidden(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func assertRequestUser(w http.ResponseWriter, r *http.Request) (auth.User, bool) {
|
||||
ctx := r.Context()
|
||||
user, err := auth.CtxUser(ctx)
|
||||
if err != nil {
|
||||
logger.Error(ctx, "could not retrieve user", logger.E(errors.WithStack(err)))
|
||||
|
||||
forbidden(w, r)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if user == nil {
|
||||
forbidden(w, r)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return user, true
|
||||
}
|
||||
|
||||
func forbidden(w http.ResponseWriter, r *http.Request) {
|
||||
logger.Warn(r.Context(), "forbidden", logger.F("path", r.URL.Path))
|
||||
|
||||
api.ErrorResponse(w, http.StatusForbidden, ErrCodeForbidden, nil)
|
||||
}
|
||||
|
||||
func logUnexpectedUserType(ctx context.Context, user auth.User) {
|
||||
logger.Error(
|
||||
ctx, "unexpected user type",
|
||||
logger.F("subject", user.Subject()),
|
||||
logger.F("type", fmt.Sprintf("%T", user)),
|
||||
)
|
||||
}
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/auth"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/auth/agent"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/auth/user"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/auth/thirdparty"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/config"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/datastore"
|
||||
"forge.cadoles.com/Cadoles/emissary/internal/jwk"
|
||||
@ -105,19 +105,19 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(auth.Middleware(
|
||||
user.NewAuthenticator(keys, string(s.conf.Issuer)),
|
||||
thirdparty.NewAuthenticator(keys, string(s.conf.Issuer)),
|
||||
agent.NewAuthenticator(s.agentRepo),
|
||||
))
|
||||
|
||||
r.Route("/agents", func(r chi.Router) {
|
||||
r.Get("/", s.queryAgents)
|
||||
r.Get("/{agentID}", s.getAgent)
|
||||
r.Put("/{agentID}", s.updateAgent)
|
||||
r.Delete("/{agentID}", s.deleteAgent)
|
||||
r.With(assertGlobalReadAccess).Get("/", s.queryAgents)
|
||||
r.With(assertAgentReadAccess).Get("/{agentID}", s.getAgent)
|
||||
r.With(assertAgentWriteAccess).Put("/{agentID}", s.updateAgent)
|
||||
r.With(assertAgentWriteAccess).Delete("/{agentID}", s.deleteAgent)
|
||||
|
||||
r.Get("/{agentID}/specs", s.getAgentSpecs)
|
||||
r.Post("/{agentID}/specs", s.updateSpec)
|
||||
r.Delete("/{agentID}/specs", s.deleteSpec)
|
||||
r.With(assertAgentReadAccess).Get("/{agentID}/specs", s.getAgentSpecs)
|
||||
r.With(assertAgentWriteAccess).Post("/{agentID}/specs", s.updateSpec)
|
||||
r.With(assertAgentWriteAccess).Delete("/{agentID}/specs", s.deleteSpec)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user