Skip to content

Commit

Permalink
add headers when priorty of token is less than webservice minimum pri…
Browse files Browse the repository at this point in the history
…ority (aka load-shedding) to add more visibility for api users & refactors
  • Loading branch information
navidnabavi committed Jan 9, 2024
1 parent 7289e87 commit 20bd6c1
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 28 deletions.
66 changes: 54 additions & 12 deletions pkg/auth/authenticator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package auth

import (
"context"
"fmt"
"net"
"net/http"
"net/url"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {

Expand All @@ -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)
Expand All @@ -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
Expand Down
31 changes: 17 additions & 14 deletions pkg/auth/authenticator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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{
Expand All @@ -291,7 +291,7 @@ func TestTestAccessValidToken(t *testing.T) {
Name: "SampleWebService",
},
Spec: cerberusv1alpha1.WebServiceSpec{
LookupHeader: "X-Cerberus-Token",
LookupHeader: string(CerberusHeaderAccessToken),
},
},
}
Expand All @@ -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) {
Expand Down Expand Up @@ -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{
Expand All @@ -366,7 +366,7 @@ func TestTestAccessEmptyToken(t *testing.T) {
Name: "SampleWebService",
},
Spec: cerberusv1alpha1.WebServiceSpec{
LookupHeader: "X-Cerberus-Token",
LookupHeader: string(CerberusHeaderAccessToken),
},
},
}
Expand Down Expand Up @@ -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{
Expand All @@ -422,7 +422,7 @@ func TestTestAccessBadIPList(t *testing.T) {
Name: "SampleWebService",
},
Spec: cerberusv1alpha1.WebServiceSpec{
LookupHeader: "X-Cerberus-Token",
LookupHeader: string(CerberusHeaderAccessToken),
},
},
}
Expand Down Expand Up @@ -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{
Expand All @@ -476,7 +476,7 @@ func TestTestAccessLimited(t *testing.T) {
Name: "SampleWebService",
},
Spec: cerberusv1alpha1.WebServiceSpec{
LookupHeader: "X-Cerberus-Token",
LookupHeader: string(CerberusHeaderAccessToken),
MinimumTokenPriority: 100,
},
},
Expand All @@ -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) {
Expand Down Expand Up @@ -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),
},
},
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/auth/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 20bd6c1

Please sign in to comment.