Skip to content

Commit

Permalink
working version of generic jwt parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
nikals99 committed Apr 21, 2021
1 parent ea6affe commit 82562d6
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 47 deletions.
19 changes: 10 additions & 9 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package auth

import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
Expand All @@ -28,8 +29,8 @@ type EventQuery struct {

// Claims all claim fields a token from Gitlab could have
type Claims struct {
ClaimsJSON []byte
jwt.StandardClaims
ClaimsJSON []byte
StandardClaims *jwt.StandardClaims
}

// Config holds all configuration for the Handler
Expand All @@ -42,10 +43,10 @@ type Config struct {

// Rule represents a single claim to role mapping
type Rule struct {
Role string `json:"role"`
Region string `json:"region"`
Duration int64 `json:"duration"`
ClaimValues Claims `json:"claim_values"`
Role string `json:"role"`
Region string `json:"region"`
Duration int64 `json:"duration"`
ClaimValues json.RawMessage `json:"claim_values"`
}

// Handler lambda function interface
Expand All @@ -69,7 +70,7 @@ func NewHandler(auth Authorizer) Handler {
if err != nil {
return RespondError(err, http.StatusUnauthorized)
}

log.Printf("Claims JSON: %s", claims.ClaimsJSON)
log.Printf("Validated Token")

role, err := auth.TokenValidator().ValidateClaimsForRule(claims, event.Query.Role, auth.Config().Rules)
Expand All @@ -79,8 +80,8 @@ func NewHandler(auth Authorizer) Handler {
return RespondError(fmt.Errorf("unable to find matching role for the given token"), http.StatusUnauthorized)
}

log.Printf("Retrieved request from %s to assume role %s", claims.Subject, role.Role)
credentials, err := auth.AwsConsumer().AssumeRole(role, claims.Subject)
log.Printf("Retrieved request from %s to assume role %s", claims.StandardClaims.Subject, role.Role)
credentials, err := auth.AwsConsumer().AssumeRole(role, claims.StandardClaims.Subject)
if err != nil {
return RespondError(err, http.StatusInternalServerError)
}
Expand Down
34 changes: 18 additions & 16 deletions handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ package auth_test

import (
"context"
"github.com/aws/aws-sdk-go/service/sts"
"github.com/dgrijalva/jwt-go"

"net/http"
"testing"

"github.com/aws/aws-sdk-go/service/sts"
"github.com/dgrijalva/jwt-go"
auth "token_authorizer"
"token_authorizer/mock"

"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"testing"
)

func TestAuthorizationHandler(t *testing.T) {
Expand All @@ -36,23 +35,26 @@ func TestAuthorizationHandler(t *testing.T) {

var rules []auth.Rule
rules = append(rules, auth.Rule{
Role: "one",
ClaimValues: auth.Claims{
ClaimsJSON: []byte("{\"namespace_id\": \"1\""),
StandardClaims: jwt.StandardClaims{Subject: "hans"},
},
Role: "one",
ClaimValues: []byte("{\"namespace_id\": \"1\"}"),
})

rules = append(rules, auth.Rule{
Role: "two",
ClaimValues: auth.Claims{
ClaimsJSON: []byte("{\"namespace_id\": \"2\""),
StandardClaims: jwt.StandardClaims{Subject: "hans"},
},
Role: "two",
ClaimValues: []byte("{\"namespace_id\": \"2\"}"),
})

claims := auth.Claims{ClaimsJSON: rules[0].ClaimValues,
StandardClaims: &jwt.StandardClaims{
Subject: "hans",
}}

tokenValidator := mock.NewMockTokenValidatorInterface(ctrl)
tokenValidator.EXPECT().RetrieveClaimsFromToken(gomock.Eq("token")).Return(&rules[0].ClaimValues, nil)
tokenValidator.EXPECT().ValidateClaimsForRule(gomock.Eq(&rules[0].ClaimValues), gomock.Eq("one"), gomock.Eq(rules)).Return(&rules[0], nil)

tokenValidator.EXPECT().RetrieveClaimsFromToken(gomock.Eq("token")).Return(&claims, nil)

tokenValidator.EXPECT().ValidateClaimsForRule(gomock.Eq(&claims), gomock.Eq("one"), gomock.Eq(rules)).Return(&rules[0], nil)

awsConsumer := mock.NewMockAwsConsumerInterface(ctrl)
awsConsumer.EXPECT().ValidateRole(gomock.Eq("one")).Return(true)
awsConsumer.EXPECT().AssumeRole(gomock.Eq(&rules[0]), gomock.Eq("hans")).Return(&sts.Credentials{}, nil)
Expand Down
14 changes: 7 additions & 7 deletions mock/authorizer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions mock/aws_consumer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions mock/token_validator.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 10 additions & 6 deletions token_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// TokenValidatorInterface interface of validation objects
type TokenValidatorInterface interface {
RetrieveClaimsFromToken(tokenInput string) (*Claims, error)
MatchClaims(tokenClaims *Claims, ruleClaims *Claims) bool
MatchClaims(tokenClaims *Claims, ruleClaims []byte) bool
ValidateClaimsForRule(tokenClaims *Claims, requestedRole string, rules []Rule) (*Rule, error)
}

Expand All @@ -38,7 +38,7 @@ type TokenValidator struct {

// RetrieveClaimsFromToken validate the token and get all included claims
func (t *TokenValidator) RetrieveClaimsFromToken(tokenInput string) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenInput, &Claims{}, t.jwks.KeyFunc)
token, err := jwt.ParseWithClaims(tokenInput, &jwt.StandardClaims{}, t.jwks.KeyFunc)

if err != nil {
return nil, err
Expand All @@ -48,6 +48,8 @@ func (t *TokenValidator) RetrieveClaimsFromToken(tokenInput string) (*Claims, er
return nil, fmt.Errorf("token invalid")
}

log.Printf("Raw token: %s", token.Raw)

parts := strings.Split(token.Raw, ".")
if len(parts) != 3 {
return nil, fmt.Errorf("error splitting token into parts")
Expand All @@ -61,11 +63,12 @@ func (t *TokenValidator) RetrieveClaimsFromToken(tokenInput string) (*Claims, er

claims := &Claims{
ClaimsJSON: claimsJSON,
StandardClaims: token.Claims.(jwt.StandardClaims),
StandardClaims: token.Claims.(*jwt.StandardClaims),
}

return claims, nil
}

// MatchClaimsInternal implements claims matching on the json byte data level
func MatchClaimsInternal(claims []byte, rules []byte) (bool, error) {
matches := true
Expand Down Expand Up @@ -114,8 +117,9 @@ func MatchClaimsInternal(claims []byte, rules []byte) (bool, error) {
}

// MatchClaims check if all claims from a token are presented within rules
func (t *TokenValidator) MatchClaims(tokenClaims *Claims, ruleClaims *Claims) bool {
match, err := MatchClaimsInternal(tokenClaims.ClaimsJSON, ruleClaims.ClaimsJSON)
func (t *TokenValidator) MatchClaims(tokenClaims *Claims, ruleClaims []byte) bool {
log.Printf("Rules JSON: %s", ruleClaims)
match, err := MatchClaimsInternal(tokenClaims.ClaimsJSON, ruleClaims)
if err != nil {
log.Fatalf("error matching claims: %s", err)
}
Expand All @@ -126,7 +130,7 @@ func (t *TokenValidator) MatchClaims(tokenClaims *Claims, ruleClaims *Claims) bo
// ValidateClaimsForRule check if
func (t *TokenValidator) ValidateClaimsForRule(tokenClaims *Claims, requestedRole string, rules []Rule) (*Rule, error) {
for _, rule := range rules {
if strings.Compare(rule.Role, requestedRole) == 0 && t.MatchClaims(tokenClaims, &rule.ClaimValues) {
if strings.Compare(rule.Role, requestedRole) == 0 && t.MatchClaims(tokenClaims, rule.ClaimValues) {
return &rule, nil
}
}
Expand Down
5 changes: 5 additions & 0 deletions token_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ func TestMatchClaimsInternal(t *testing.T) {
Rules: "{\"foo\": \"foo\"}",
IsMatch: false,
},
"03_complex_match": {
Claims: "{\n \"namespace_id\": \"172\",\n \"namespace_path\": \"niklas.fassbender\",\n \"project_id\": \"1093\",\n \"project_path\": \"niklas.fassbender/runner-trial\",\n \"user_id\": \"134\",\n \"user_login\": \"niklas.fassbender\",\n \"user_email\": \"niklas.fassbender@aoe.com\",\n \"pipeline_id\": \"1255137\",\n \"job_id\": \"2769626\",\n \"ref\": \"master\",\n \"ref_type\": \"branch\",\n \"ref_protected\": \"true\",\n \"jti\": \"439b39a2-0d31-4ab6-aae7-e73805a12dce\",\n \"iss\": \"gitlab.aoe.com\",\n \"iat\": 1619003306,\n \"nbf\": 1619003301,\n \"exp\": 1619006906,\n \"sub\": \"job_2769626\"\n}",
Rules: "{\"namespace_id\": \"172\"}",
IsMatch: true,
},
}

for name, testCase := range tests {
Expand Down

0 comments on commit 82562d6

Please sign in to comment.