diff --git a/pkg/auth/authenticator.go b/pkg/auth/authenticator.go index 6332fc2..a4aa28c 100644 --- a/pkg/auth/authenticator.go +++ b/pkg/auth/authenticator.go @@ -2,6 +2,7 @@ package auth import ( "context" + "fmt" "net" "net/http" "net/url" @@ -37,9 +38,6 @@ type Authenticator struct { updateLock sync.Mutex } -// ExtraHeaders are headers which will be added to the response -type ExtraHeaders map[string]string - // AccessCache is where Authenticator holds its authentication data, // under the hood it is a Map from RawTokens to some information about // AccessToken, see AccessCacheEntry for more information @@ -145,6 +143,36 @@ const ( CerberusReasonUpstreamAuthNoReq CerberusReason = "upstream-auth-no-request" ) +// ExtraHeaders are headers which will be added to the response +type ExtraHeaders map[string]string + +// ExtraHeaders setting generally +const ( + CerberusHeaderReasonHeader string = "X-Cerberus-Reason" + ExternalAuthHandlerHeader string = "X-Auth-Handler" +) + +// CerberusHeaderName is the type which is used to identifies header name +type CerberusHeaderName string + +// CerberusExtraHeaders are headers which will be added to the response starting +// with X-Cerberus-* this headers also will be set Test function +type CerberusExtraHeaders map[CerberusHeaderName]string + +const ( + CerberusHeaderAccessLimitReason CerberusHeaderName = "X-Cerberus-Access-Limit-Reason" + CerberusHeaderTokenPriority CerberusHeaderName = "X-Cerberus-Token-Priority" + CerberusHeaderWebServiceMinPriority CerberusHeaderName = "X-Cerberus-Webservice-Min-Priority" + CerberusHeaderAccessToken CerberusHeaderName = "X-Cerberus-AccessToken" +) + +// Access limit reasons +const ( + //TokenPriorityLowerThanServiceMinAccessLimit is the value to be set on CerberusHeaderAccessLimitReason + // header when load-shedding is done due token priority values + TokenPriorityLowerThanServiceMinAccessLimit string = "TokenPriorityLowerThanServiceMinimum" +) + //+kubebuilder:rbac:groups=cerberus.snappcloud.io,resources=accesstokens,verbs=get;list;watch; //+kubebuilder:rbac:groups=cerberus.snappcloud.io,resources=accesstokens/status,verbs=get; //+kubebuilder:rbac:groups=cerberus.snappcloud.io,resources=webservices,verbs=get;list;watch; @@ -267,8 +295,8 @@ func (a *Authenticator) UpdateCache(c client.Client, ctx context.Context, readOn // TestAccess will check if given AccessToken (identified by raw token in the request) // has access to given Webservice (identified by its name) and returns proper CerberusReason -func (a *Authenticator) TestAccess(request *Request, wsvc ServicesCacheEntry) (bool, CerberusReason, ExtraHeaders) { - newExtraHeaders := make(ExtraHeaders) +func (a *Authenticator) TestAccess(request *Request, wsvc ServicesCacheEntry) (bool, CerberusReason, CerberusExtraHeaders) { + newExtraHeaders := make(CerberusExtraHeaders) ok, reason, token := a.readToken(request, wsvc) if !ok { return false, reason, newExtraHeaders @@ -287,8 +315,12 @@ func (a *Authenticator) TestAccess(request *Request, wsvc ServicesCacheEntry) (b if !ok { return false, CerberusReasonTokenNotFound, newExtraHeaders } - - if (*a.accessCache)[token].Spec.Priority < wsvc.Spec.MinimumTokenPriority { + priority := (*a.accessCache)[token].Spec.Priority + minPriority := wsvc.Spec.MinimumTokenPriority + if priority < minPriority { + newExtraHeaders[CerberusHeaderAccessLimitReason] = TokenPriorityLowerThanServiceMinAccessLimit + newExtraHeaders[CerberusHeaderTokenPriority] = fmt.Sprint(priority) + newExtraHeaders[CerberusHeaderWebServiceMinPriority] = fmt.Sprint(minPriority) return false, CerberusReasonAccessLimited, newExtraHeaders } @@ -338,7 +370,7 @@ func (a *Authenticator) TestAccess(request *Request, wsvc ServicesCacheEntry) (b } } - newExtraHeaders["X-Cerberus-AccessToken"] = ac.ObjectMeta.Name + newExtraHeaders[CerberusHeaderAccessToken] = ac.ObjectMeta.Name if _, ok := (*a.accessCache)[token].allowedServices[wsvc.Name]; !ok { return false, CerberusReasonUnauthorized, newExtraHeaders @@ -375,6 +407,14 @@ func (a *Authenticator) readService(wsvc string) (bool, CerberusReason, Services return true, "", res } +func toExtraHeaders(headers CerberusExtraHeaders) ExtraHeaders { + extraHeaders := make(ExtraHeaders) + for key, value := range headers { + extraHeaders[string(key)] = value + } + return extraHeaders +} + // Check is the function which is used to Authenticate and Respond to gRPC envoy.CheckRequest func (a *Authenticator) Check(ctx context.Context, request *Request) (*Response, error) { @@ -385,7 +425,9 @@ func (a *Authenticator) Check(ctx context.Context, request *Request) (*Response, ok, reason, wsvcCacheEntry := a.readService(wsvc) if ok { - ok, reason, extraHeaders = a.TestAccess(request, wsvcCacheEntry) + var cerberusExtraHeaders CerberusExtraHeaders + ok, reason, cerberusExtraHeaders = a.TestAccess(request, wsvcCacheEntry) + extraHeaders = toExtraHeaders(cerberusExtraHeaders) if ok && hasUpstreamAuth(wsvcCacheEntry) { request.Context[HasUpstreamAuth] = "true" ok, reason = a.checkServiceUpstreamAuth(wsvcCacheEntry, request, &extraHeaders, ctx) @@ -401,13 +443,13 @@ func (a *Authenticator) Check(ctx context.Context, request *Request) (*Response, response := http.Response{ StatusCode: httpStatusCode, Header: http.Header{ - "X-Auth-Handler": {"cerberus"}, - "X-Cerberus-Reason": {string(reason)}, + ExternalAuthHandlerHeader: {"cerberus"}, + CerberusHeaderReasonHeader: {string(reason)}, }, } for key, value := range extraHeaders { - response.Header.Add(key, value) + response.Header.Add(string(key), value) } var err error diff --git a/pkg/auth/authenticator_test.go b/pkg/auth/authenticator_test.go index 080920a..7aee723 100644 --- a/pkg/auth/authenticator_test.go +++ b/pkg/auth/authenticator_test.go @@ -208,12 +208,12 @@ func TestReadToken(t *testing.T) { webservice := ServicesCacheEntry{ cerberusv1alpha1.WebService{ Spec: cerberusv1alpha1.WebServiceSpec{ - LookupHeader: "X-Cerberus-Token", + LookupHeader: string(CerberusHeaderAccessToken), }, }, } - request.Request.Header.Set("X-Cerberus-Token", "test-token") + request.Request.Header.Set(string(CerberusHeaderAccessToken), "test-token") ok, reason, token := authenticator.readToken(request, webservice) @@ -274,7 +274,7 @@ func TestTestAccessValidToken(t *testing.T) { (*authenticator.accessCache)["valid-token"] = tokenEntry headers := http.Header{} - headers.Set("X-Cerberus-Token", "valid-token") + headers.Set(string(CerberusHeaderAccessToken), "valid-token") request := &Request{ Context: map[string]string{ @@ -291,7 +291,7 @@ func TestTestAccessValidToken(t *testing.T) { Name: "SampleWebService", }, Spec: cerberusv1alpha1.WebServiceSpec{ - LookupHeader: "X-Cerberus-Token", + LookupHeader: string(CerberusHeaderAccessToken), }, }, } @@ -301,7 +301,7 @@ func TestTestAccessValidToken(t *testing.T) { assert.True(t, ok, "Expected access to be granted") assert.Equal(t, CerberusReasonOK, reason, "Expected reason to be OK") - assert.Equal(t, "valid-token", extraHeaders["X-Cerberus-AccessToken"], "Expected token in extraHeaders") + assert.Equal(t, "valid-token", extraHeaders[CerberusHeaderAccessToken], "Expected token in extraHeaders") } func TestTestAccessInvalidToken(t *testing.T) { @@ -349,7 +349,7 @@ func TestTestAccessEmptyToken(t *testing.T) { } headers := http.Header{} - headers.Set("X-Cerberus-Token", "") + headers.Set(string(CerberusHeaderAccessToken), "") request := &Request{ Context: map[string]string{ @@ -366,7 +366,7 @@ func TestTestAccessEmptyToken(t *testing.T) { Name: "SampleWebService", }, Spec: cerberusv1alpha1.WebServiceSpec{ - LookupHeader: "X-Cerberus-Token", + LookupHeader: string(CerberusHeaderAccessToken), }, }, } @@ -403,7 +403,7 @@ func TestTestAccessBadIPList(t *testing.T) { // Assuming an IP not in the allow list headers := http.Header{} - headers.Set("X-Cerberus-Token", "valid-token") + headers.Set(string(CerberusHeaderAccessToken), "valid-token") headers.Set("X-Forwarded-For", "192.168.1.3") request := &Request{ @@ -422,7 +422,7 @@ func TestTestAccessBadIPList(t *testing.T) { Name: "SampleWebService", }, Spec: cerberusv1alpha1.WebServiceSpec{ - LookupHeader: "X-Cerberus-Token", + LookupHeader: string(CerberusHeaderAccessToken), }, }, } @@ -459,7 +459,7 @@ func TestTestAccessLimited(t *testing.T) { (*authenticator.accessCache)["valid-token"] = tokenEntry headers := http.Header{} - headers.Set("X-Cerberus-Token", "valid-token") + headers.Set(string(CerberusHeaderAccessToken), "valid-token") request := &Request{ Context: map[string]string{ @@ -476,7 +476,7 @@ func TestTestAccessLimited(t *testing.T) { Name: "SampleWebService", }, Spec: cerberusv1alpha1.WebServiceSpec{ - LookupHeader: "X-Cerberus-Token", + LookupHeader: string(CerberusHeaderAccessToken), MinimumTokenPriority: 100, }, }, @@ -488,7 +488,10 @@ func TestTestAccessLimited(t *testing.T) { assert.False(t, ok, "Expected access to be denied") assert.Equal(t, CerberusReasonAccessLimited, reason, "Expected reason to be AccessLimited") - assert.Empty(t, extraHeaders, "Expected no extra headers for AccessLimited") + assert.Equal(t, extraHeaders[CerberusHeaderAccessLimitReason], TokenPriorityLowerThanServiceMinAccessLimit) + assert.Equal(t, extraHeaders[CerberusHeaderTokenPriority], fmt.Sprint(tokenEntry.Spec.Priority)) + assert.Equal(t, extraHeaders[CerberusHeaderWebServiceMinPriority], fmt.Sprint(webservice.Spec.MinimumTokenPriority)) + } func setupTestEnvironment(t *testing.T) (client.Client, *Authenticator) { @@ -558,11 +561,11 @@ func prepareWebservices(count int) []cerberusv1alpha1.WebService { webservices[i] = cerberusv1alpha1.WebService{ ObjectMeta: metav1.ObjectMeta{Name: webserviceName, Namespace: "default"}, Spec: cerberusv1alpha1.WebServiceSpec{ - LookupHeader: "X-Cerberus-Token", + LookupHeader: string(CerberusHeaderAccessToken), UpstreamHttpAuth: cerberusv1alpha1.UpstreamHttpAuthService{ Address: "http://example.com/auth", ReadTokenFrom: "Authorization", - WriteTokenTo: "X-Cerberus-Token", + WriteTokenTo: string(CerberusHeaderAccessToken), }, }, } diff --git a/pkg/auth/server.go b/pkg/auth/server.go index a2a85c7..574f052 100644 --- a/pkg/auth/server.go +++ b/pkg/auth/server.go @@ -44,7 +44,7 @@ func (a *authV2) Check(ctx context.Context, check *CheckRequestV2) (*CheckRespon final_response := response.AsV2() // update metrics - reason := CerberusReason(response.Response.Header.Get("X-Cerberus-Reason")) + reason := CerberusReason(response.Response.Header.Get(CerberusHeaderReasonHeader)) labels := AddReasonLabel(nil, reason) labels = AddUpstreamAuthLabel(labels, request.Context[HasUpstreamAuth]) labels[CheckRequestVersionLabel] = MetricsCheckRequestVersion2 @@ -70,7 +70,7 @@ func (a *authV3) Check(ctx context.Context, check *CheckRequestV3) (*CheckRespon final_response := response.AsV3() // update metrics - reason := CerberusReason(response.Response.Header.Get("X-Cerberus-Reason")) + reason := CerberusReason(response.Response.Header.Get(CerberusHeaderReasonHeader)) labels := AddReasonLabel(nil, reason) labels = AddUpstreamAuthLabel(labels, request.Context[HasUpstreamAuth]) labels[CheckRequestVersionLabel] = MetricsCheckRequestVersion3