diff --git a/go.mod b/go.mod index 3af8265..3d62477 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( ) require ( + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect diff --git a/go.sum b/go.sum index 7d09403..5be13ea 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,8 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= diff --git a/pkg/auth/authenticator.go b/pkg/auth/authenticator.go index 55e7e99..386ea54 100644 --- a/pkg/auth/authenticator.go +++ b/pkg/auth/authenticator.go @@ -7,6 +7,7 @@ import ( "regexp" "sync" + "github.com/asaskevich/govalidator" "github.com/go-logr/logr" cerberusv1alpha1 "github.com/snapp-incubator/Cerberus/api/v1alpha1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -15,7 +16,8 @@ import ( ) type Authenticator struct { - logger logr.Logger + logger logr.Logger + httpClient *http.Client accessCache *AccessCache servicesCache *ServicesCache @@ -41,13 +43,17 @@ type ServicesCacheEntry struct { type CerberusReason string const ( - CerberusReasonOK CerberusReason = "ok" - CerberusReasonUnauthorized CerberusReason = "unauthorized" - CerberusReasonTokenEmpty CerberusReason = "token-empty" - CerberusReasonLookupEmpty CerberusReason = "lookup-empty" - CerberusReasonLookupIdentifierEmpty CerberusReason = "lookup-identifier-empty" - CerberusReasonTokenNotFound CerberusReason = "token-notfound" - CerberusReasonWebserviceNotFound CerberusReason = "webservice-notfound" + CerberusReasonOK CerberusReason = "ok" + CerberusReasonUnauthorized CerberusReason = "unauthorized" + CerberusReasonTokenEmpty CerberusReason = "token-empty" + CerberusReasonLookupEmpty CerberusReason = "lookup-empty" + CerberusReasonLookupIdentifierEmpty CerberusReason = "lookup-identifier-empty" + CerberusReasonTokenNotFound CerberusReason = "token-notfound" + CerberusReasonWebserviceNotFound CerberusReason = "webservice-notfound" + CerberusReasonInvalidUpstreamAddress CerberusReason = "invalid-auth-upstream" + CerberusReasonSourceAuthTokenEmpty CerberusReason = "upstream-source-identifier-empty" + CerberusReasonTargetAuthTokenEmpty CerberusReason = "upstream-target-identifier-empty" + CerberusReasonUpstreamAuthFailed CerberusReason = "upstream-auth-failed" ) //+kubebuilder:rbac:groups=cerberus.snappcloud.io,resources=accesstokens,verbs=get;list;watch; @@ -200,6 +206,9 @@ func (a *Authenticator) Check(ctx context.Context, request *Request) (*Response, if ok { ok, reason, extraHeaders = a.TestAccess(wsvc, token) } + if ok { + ok, reason = a.checkServiceUpstreamAuth(wsvc, request, &extraHeaders) + } a.logger.Info("checking request", "reason", reason, "req", request) if ok { @@ -228,7 +237,8 @@ func (a *Authenticator) Check(ctx context.Context, request *Request) (*Response, func NewAuthenticator(logger logr.Logger) (*Authenticator, error) { a := Authenticator{ - logger: logger, + logger: logger, + httpClient: &http.Client{}, } return &a, nil } @@ -261,3 +271,47 @@ func CheckDomain(domain string, domainAllowedList []string) (bool, error) { } return false, nil } + +func (a *Authenticator) checkServiceUpstreamAuth(wsvc string, request *Request, extraHeaders *ExtraHeaders) (bool, CerberusReason) { + service, ok := (*a.servicesCache)[wsvc] + if !ok { + return false, CerberusReasonWebserviceNotFound + } + if service.Spec.UpstreamHttpAuth.ReadTokenFrom == "" { + return false, CerberusReasonSourceAuthTokenEmpty + } + if service.Spec.UpstreamHttpAuth.WriteTokenTo == "" { + return false, CerberusReasonTargetAuthTokenEmpty + } + if !govalidator.IsRequestURL(service.Spec.UpstreamHttpAuth.Address) { + return false, CerberusReasonInvalidUpstreamAddress + } + + token := request.Request.Header.Get(service.Spec.UpstreamHttpAuth.ReadTokenFrom) + + // TODO: get http method from webservice crd + req, err := http.NewRequest("GET", service.Spec.UpstreamHttpAuth.Address, nil) + if err != nil { + return false, CerberusReasonUpstreamAuthFailed + } + + req.Header = http.Header{ + service.Spec.UpstreamHttpAuth.WriteTokenTo: {token}, + "Content-Type": {"application/json"}, + } + + resp, err := a.httpClient.Do(req) + if err != nil { + return false, CerberusReasonUpstreamAuthFailed + } + + var headersString string + for header, values := range resp.Header { + for _, value := range values { + headersString += header + ": " + value + "\n" + } + } + (*extraHeaders)["X-Cerberus-Upstream-Headers"] = headersString + + return true, CerberusReasonOK +}