github.com/avenga/couper@v1.12.2/accesscontrol/jwt_test.go (about)

     1  package accesscontrol_test
     2  
     3  import (
     4  	"context"
     5  	"crypto/rand"
     6  	"crypto/rsa"
     7  	"crypto/x509"
     8  	"encoding/pem"
     9  	"fmt"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"reflect"
    13  	"regexp"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/golang-jwt/jwt/v4"
    19  	"github.com/hashicorp/hcl/v2"
    20  	"github.com/hashicorp/hcl/v2/hcltest"
    21  	"github.com/sirupsen/logrus"
    22  	"github.com/zclconf/go-cty/cty"
    23  
    24  	ac "github.com/avenga/couper/accesscontrol"
    25  	acjwt "github.com/avenga/couper/accesscontrol/jwt"
    26  	"github.com/avenga/couper/cache"
    27  	"github.com/avenga/couper/config/configload"
    28  	"github.com/avenga/couper/config/reader"
    29  	"github.com/avenga/couper/config/request"
    30  	"github.com/avenga/couper/config/runtime"
    31  	"github.com/avenga/couper/errors"
    32  	"github.com/avenga/couper/eval"
    33  	"github.com/avenga/couper/internal/test"
    34  )
    35  
    36  func Test_JWT_NewJWT_RSA(t *testing.T) {
    37  	helper := test.New(t)
    38  
    39  	type fields struct {
    40  		algorithm      string
    41  		claims         hcl.Expression
    42  		claimsRequired []string
    43  		pubKey         []byte
    44  		pubKeyPath     string
    45  	}
    46  
    47  	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
    48  	helper.Must(err)
    49  
    50  	bytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
    51  	helper.Must(err)
    52  
    53  	pubKeyBytesPKIX := pem.EncodeToMemory(&pem.Block{
    54  		Type:  "PUBLIC KEY",
    55  		Bytes: bytes,
    56  	})
    57  	pubKeyBytesPKCS1 := pem.EncodeToMemory(&pem.Block{
    58  		Type:  "RSA PUBLIC KEY",
    59  		Bytes: x509.MarshalPKCS1PublicKey(&privKey.PublicKey),
    60  	})
    61  	privKeyBytes := pem.EncodeToMemory(&pem.Block{
    62  		Type:  "RSA PRIVATE KEY",
    63  		Bytes: x509.MarshalPKCS1PrivateKey(privKey),
    64  	})
    65  	// created using
    66  	// openssl req -new -newkey rsa:1024 -days 100000 -nodes -x509
    67  	certBytes := []byte(`-----BEGIN CERTIFICATE-----
    68  MIICaDCCAdGgAwIBAgIUZe+V/eBcYEaoORX8mfsyR8LqY/kwDQYJKoZIhvcNAQEL
    69  BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
    70  GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAgFw0yMTA0MTIxMzI1MzRaGA8yMjk1
    71  MDEyNjEzMjUzNFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
    72  ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCBnzANBgkqhkiG9w0B
    73  AQEFAAOBjQAwgYkCgYEA2m79uRP+f/L6YgCuQoAiY6Qs5pccKR4DNfb+vQOsO+xx
    74  ZxWrY3RLSLOYKCBybHClz0JLT61duq7yfOl+03lYE6wTdy5XN1PGoijITj3cA6g1
    75  Eah6/CirrDVqEVIng+5lsw/Qws1gOOkHaCdfkL85Trm4AWqppgFgIc/wafHZjekC
    76  AwEAAaNTMFEwHQYDVR0OBBYEFCAUN20ma8sVaz1KZttyofv6tDZdMB8GA1UdIwQY
    77  MBaAFCAUN20ma8sVaz1KZttyofv6tDZdMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
    78  hvcNAQELBQADgYEADyu05JNvWly50lvUksx85QwEMb7oZha6aov/9eslJnHD10Zu
    79  QolLGgj3tz4NbDEitq+zKMr0uTHvP1Vyu1mXAflcpYcJA4ZmuB3Oj39e0U0gnmr/
    80  1T2dX1uHaAWl3pCmkRH1Dmpsx2sHllN/yizHpve2rrVpM9ZMXEdPxnzNNFE=
    81  -----END CERTIFICATE-----`)
    82  
    83  	for _, signingMethod := range []jwt.SigningMethod{
    84  		jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512,
    85  	} {
    86  		alg := signingMethod.Alg()
    87  		tests := []struct {
    88  			name    string
    89  			fields  fields
    90  			wantErr string
    91  		}{
    92  			{"missing key-file path", fields{}, "configuration error: jwt key: read error: required: configured attribute or file"},
    93  			{"missing key-file", fields{pubKeyPath: "./not-there.file"}, "not-there.file: no such file or directory"},
    94  			{"PKIX", fields{
    95  				algorithm: alg,
    96  				pubKey:    pubKeyBytesPKIX,
    97  			}, ""},
    98  			{"PKCS1", fields{
    99  				algorithm: alg,
   100  				pubKey:    pubKeyBytesPKCS1,
   101  			}, ""},
   102  			{"Cert", fields{
   103  				algorithm: alg,
   104  				pubKey:    certBytes,
   105  			}, ""},
   106  			{"Priv", fields{
   107  				algorithm: alg,
   108  				pubKey:    privKeyBytes,
   109  			}, "key is not a valid RSA public key"},
   110  		}
   111  
   112  		for _, tt := range tests {
   113  			t.Run(fmt.Sprintf("%v / %s", signingMethod, tt.name), func(subT *testing.T) {
   114  				key, rerr := reader.ReadFromAttrFile("jwt key", string(tt.fields.pubKey), tt.fields.pubKeyPath)
   115  				if rerr != nil {
   116  					logErr := rerr.(errors.GoError)
   117  					if tt.wantErr != "" && !strings.HasSuffix(logErr.LogError(), tt.wantErr) {
   118  						subT.Errorf("\nWant:\t%q\nGot:\t%q", tt.wantErr, logErr.LogError())
   119  					} else if tt.wantErr == "" {
   120  						subT.Fatal(logErr.LogError())
   121  					}
   122  					return
   123  				}
   124  
   125  				j, jerr := ac.NewJWT(&ac.JWTOptions{
   126  					Algorithm:      tt.fields.algorithm,
   127  					Claims:         tt.fields.claims,
   128  					ClaimsRequired: tt.fields.claimsRequired,
   129  					Name:           "test_ac",
   130  					Key:            key,
   131  					Source:         ac.NewJWTSource("", "Authorization", nil),
   132  				})
   133  				if jerr != nil {
   134  					if tt.wantErr != jerr.Error() {
   135  						subT.Errorf("error: %v, want: %v", jerr.Error(), tt.wantErr)
   136  					}
   137  				} else if tt.wantErr != "" {
   138  					subT.Errorf("error expected: %v", tt.wantErr)
   139  				}
   140  				if tt.wantErr == "" && j == nil {
   141  					subT.Errorf("JWT struct expected")
   142  				}
   143  			})
   144  		}
   145  	}
   146  }
   147  
   148  func Test_JWT_Validate(t *testing.T) {
   149  	log, _ := test.NewLogger()
   150  	type fields struct {
   151  		algorithm      acjwt.Algorithm
   152  		claims         map[string]string
   153  		claimsRequired []string
   154  		source         ac.JWTSource
   155  		pubKey         []byte
   156  	}
   157  
   158  	for _, signingMethod := range []jwt.SigningMethod{
   159  		jwt.SigningMethodRS256, jwt.SigningMethodRS384, jwt.SigningMethodRS512,
   160  		jwt.SigningMethodHS256, jwt.SigningMethodHS384, jwt.SigningMethodHS512,
   161  	} {
   162  
   163  		pubKeyBytes, privKey := newRSAKeyPair()
   164  
   165  		tok := jwt.NewWithClaims(signingMethod, jwt.MapClaims{
   166  			"aud":     "peter",
   167  			"test123": "value123",
   168  		})
   169  		var token string
   170  		var tokenErr error
   171  
   172  		algo := acjwt.NewAlgorithm(signingMethod.Alg())
   173  
   174  		if algo.IsHMAC() {
   175  			pubKeyBytes = []byte("mySecretK3y")
   176  			token, tokenErr = tok.SignedString(pubKeyBytes)
   177  		} else {
   178  			token, tokenErr = tok.SignedString(privKey)
   179  		}
   180  
   181  		if tokenErr != nil {
   182  			t.Error(tokenErr)
   183  		}
   184  
   185  		tests := []struct {
   186  			name        string
   187  			fields      fields
   188  			req         *http.Request
   189  			wantErrKind string
   190  		}{
   191  			{"src: header /w no authorization header", fields{
   192  				algorithm: algo,
   193  				source:    ac.NewJWTSource("", "Authorization", nil),
   194  				pubKey:    pubKeyBytes,
   195  			}, httptest.NewRequest(http.MethodGet, "/", nil), "jwt_token_missing"},
   196  			{"src: header /w different auth-scheme", fields{
   197  				algorithm: algo,
   198  				source:    ac.NewJWTSource("", "Authorization", nil),
   199  				pubKey:    pubKeyBytes,
   200  			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "Basic qbqnb"), "jwt_token_missing"},
   201  			{"src: header /w empty bearer", fields{
   202  				algorithm: algo,
   203  				source:    ac.NewJWTSource("", "Authorization", nil),
   204  				pubKey:    pubKeyBytes,
   205  			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR"), "jwt_token_missing"},
   206  			{"src: header /w valid bearer", fields{
   207  				algorithm: algo,
   208  				source:    ac.NewJWTSource("", "Authorization", nil),
   209  				pubKey:    pubKeyBytes,
   210  			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token), ""},
   211  			{"src: header /w no cookie", fields{
   212  				algorithm: algo,
   213  				source:    ac.NewJWTSource("token", "", nil),
   214  				pubKey:    pubKeyBytes,
   215  			}, httptest.NewRequest(http.MethodGet, "/", nil), "jwt_token_missing"},
   216  			{"src: header /w empty cookie", fields{
   217  				algorithm: algo,
   218  				source:    ac.NewJWTSource("token", "", nil),
   219  				pubKey:    pubKeyBytes,
   220  			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "token", ""), "jwt_token_missing"},
   221  			{"src: header /w valid cookie", fields{
   222  				algorithm: algo,
   223  				source:    ac.NewJWTSource("token", "", nil),
   224  				pubKey:    pubKeyBytes,
   225  			}, setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "token", token), ""},
   226  			{"src: header /w valid bearer & claims", fields{
   227  				algorithm: algo,
   228  				claims: map[string]string{
   229  					"aud":     "peter",
   230  					"test123": "value123",
   231  				},
   232  				claimsRequired: []string{"aud"},
   233  				source:         ac.NewJWTSource("", "Authorization", nil),
   234  				pubKey:         pubKeyBytes,
   235  			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), ""},
   236  			{"src: header /w valid bearer & wrong audience", fields{
   237  				algorithm: algo,
   238  				claims: map[string]string{
   239  					"aud":     "paul",
   240  					"test123": "value123",
   241  				},
   242  				claimsRequired: []string{"aud"},
   243  				source:         ac.NewJWTSource("", "Authorization", nil),
   244  				pubKey:         pubKeyBytes,
   245  			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), "jwt_token_invalid"},
   246  			{"src: header /w valid bearer & w/o claims", fields{
   247  				algorithm: algo,
   248  				claims: map[string]string{
   249  					"aud":  "peter",
   250  					"cptn": "hook",
   251  				},
   252  				source: ac.NewJWTSource("", "Authorization", nil),
   253  				pubKey: pubKeyBytes,
   254  			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), "jwt_token_invalid"},
   255  			{"src: header /w valid bearer & w/o required claims", fields{
   256  				algorithm: algo,
   257  				claims: map[string]string{
   258  					"aud": "peter",
   259  				},
   260  				claimsRequired: []string{"exp"},
   261  				source:         ac.NewJWTSource("", "Authorization", nil),
   262  				pubKey:         pubKeyBytes,
   263  			}, setContext(setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)), "jwt_token_invalid"},
   264  			{
   265  				"token_value number",
   266  				fields{
   267  					algorithm: algo,
   268  					source:    ac.NewJWTSource("", "", hcltest.MockExprLiteral(cty.NumberIntVal(42))),
   269  					pubKey:    pubKeyBytes,
   270  				},
   271  				setContext(httptest.NewRequest(http.MethodGet, "/", nil)),
   272  				"jwt_token_invalid",
   273  			},
   274  			{
   275  				"token_value string",
   276  				fields{
   277  					algorithm:      algo,
   278  					claims:         map[string]string{"aud": "peter", "test123": "value123"},
   279  					claimsRequired: []string{"aud", "test123"},
   280  					source:         ac.NewJWTSource("", "", hcltest.MockExprLiteral(cty.StringVal(token))),
   281  					pubKey:         pubKeyBytes,
   282  				},
   283  				setContext(httptest.NewRequest(http.MethodGet, "/", nil)),
   284  				"",
   285  			},
   286  		}
   287  		for _, tt := range tests {
   288  			t.Run(fmt.Sprintf("%v_%s", signingMethod, tt.name), func(subT *testing.T) {
   289  				claimValMap := make(map[string]cty.Value)
   290  				for k, v := range tt.fields.claims {
   291  					claimValMap[k] = cty.StringVal(v)
   292  				}
   293  				j, err := ac.NewJWT(&ac.JWTOptions{
   294  					Algorithm:      tt.fields.algorithm.String(),
   295  					Claims:         hcl.StaticExpr(cty.ObjectVal(claimValMap), hcl.Range{}),
   296  					ClaimsRequired: tt.fields.claimsRequired,
   297  					Name:           "test_ac",
   298  					Source:         tt.fields.source,
   299  					Key:            tt.fields.pubKey,
   300  				})
   301  				if err != nil {
   302  					subT.Error(err)
   303  					return
   304  				}
   305  
   306  				tt.req = tt.req.WithContext(context.WithValue(context.Background(), request.LogEntry, log.WithContext(context.Background())))
   307  
   308  				errKind := ""
   309  				err = j.Validate(tt.req)
   310  				if err != nil {
   311  					cErr := err.(*errors.Error)
   312  					errKind = cErr.Kinds()[0]
   313  				}
   314  				if errKind != tt.wantErrKind {
   315  					subT.Errorf("Validate() error kind does not match; want: %q, got: %q", tt.wantErrKind, errKind)
   316  				}
   317  
   318  				if tt.wantErrKind == "" && tt.fields.claims != nil {
   319  					acMap := tt.req.Context().Value(request.AccessControls).(map[string]interface{})
   320  					if claims, ok := acMap["test_ac"]; !ok {
   321  						subT.Errorf("Expected a configured access control name within request context")
   322  					} else {
   323  						claimsMap := claims.(map[string]interface{})
   324  						for k, v := range tt.fields.claims {
   325  							if claimsMap[k] != v {
   326  								subT.Errorf("Claim does not match: %q want: %v, got: %v", k, v, claimsMap[k])
   327  							}
   328  						}
   329  					}
   330  
   331  				}
   332  			})
   333  		}
   334  	}
   335  }
   336  
   337  func Test_JWT_yields_permissions(t *testing.T) {
   338  	log, hook := test.NewLogger()
   339  	signingMethod := jwt.SigningMethodHS256
   340  	algo := acjwt.NewAlgorithm(signingMethod.Alg())
   341  
   342  	rolesMap := map[string][]string{
   343  		"admin": {"foo", "bar", "baz"},
   344  		"user1": {"foo"},
   345  		"user2": {"bar"},
   346  		"*":     {"default"},
   347  	}
   348  	permissionsMap := map[string][]string{
   349  		"baz": {"blubb"},
   350  	}
   351  	var noGrantedPermissions []string
   352  
   353  	tests := []struct {
   354  		name             string
   355  		permissionsClaim string
   356  		permissionsValue interface{}
   357  		rolesClaim       string
   358  		rolesValue       interface{}
   359  		expWarning       string
   360  		expGrantedPerms  []string
   361  	}{
   362  		{
   363  			"no permissions, no roles",
   364  			"scp",
   365  			nil,
   366  			"roles",
   367  			nil,
   368  			"",
   369  			noGrantedPermissions,
   370  		},
   371  		{
   372  			"permissions: space-separated list",
   373  			"scp",
   374  			"foo bar",
   375  			"",
   376  			nil,
   377  			"",
   378  			[]string{"foo", "bar"},
   379  		},
   380  		{
   381  			"permissions: space-separated list, multiple",
   382  			"scp",
   383  			"foo bar foo",
   384  			"",
   385  			nil,
   386  			"",
   387  			[]string{"foo", "bar"},
   388  		},
   389  		{
   390  			"permissions: list of string",
   391  			"scoop",
   392  			[]string{"foo", "bar"},
   393  			"",
   394  			nil,
   395  			"",
   396  			[]string{"foo", "bar"},
   397  		},
   398  		{
   399  			"permissions: list of string, multiple",
   400  			"scoop",
   401  			[]string{"foo", "bar", "bar"},
   402  			"",
   403  			nil,
   404  			"",
   405  			[]string{"foo", "bar"},
   406  		},
   407  		{
   408  			"permissions: warn: boolean",
   409  			"scope",
   410  			true,
   411  			"",
   412  			nil,
   413  			"invalid permissions claim value type, ignoring claim, value true",
   414  			noGrantedPermissions,
   415  		},
   416  		{
   417  			"permissions: warn: number",
   418  			"scope",
   419  			1.23,
   420  			"",
   421  			nil,
   422  			"invalid permissions claim value type, ignoring claim, value 1.23",
   423  			noGrantedPermissions,
   424  		},
   425  		{
   426  			"permissions: warn: list of bool",
   427  			"scope",
   428  			[]bool{true, false},
   429  			"",
   430  			nil,
   431  			"invalid permissions claim value type, ignoring claim, value []interface {}{true, false}",
   432  			noGrantedPermissions,
   433  		},
   434  		{
   435  			"permissions: warn: list of number",
   436  			"scope",
   437  			[]int{1, 2},
   438  			"",
   439  			nil,
   440  			"invalid permissions claim value type, ignoring claim, value []interface {}{1, 2}",
   441  			noGrantedPermissions,
   442  		},
   443  		{
   444  			"permissions: warn: mixed list",
   445  			"scope",
   446  			[]interface{}{"eins", 2},
   447  			"",
   448  			nil,
   449  			`invalid permissions claim value type, ignoring claim, value []interface {}{"eins", 2}`,
   450  			noGrantedPermissions,
   451  		},
   452  		{
   453  			"permissions: warn: object",
   454  			"scope",
   455  			map[string]interface{}{"foo": 1, "bar": 1},
   456  			"",
   457  			nil,
   458  			`invalid permissions claim value type, ignoring claim, value map[string]interface {}{"bar":1, "foo":1}`,
   459  			noGrantedPermissions,
   460  		},
   461  		{
   462  			"roles: single string, permission mapped",
   463  			"",
   464  			nil,
   465  			"roles",
   466  			"admin",
   467  			"",
   468  			[]string{"foo", "bar", "baz", "default", "blubb"},
   469  		},
   470  		{
   471  			"roles: space-separated list",
   472  			"",
   473  			nil,
   474  			"roles",
   475  			"user1 user2",
   476  			"",
   477  			[]string{"foo", "bar", "default"},
   478  		},
   479  		{
   480  			"roles: space-separated list, multiple",
   481  			"",
   482  			nil,
   483  			"roles",
   484  			"user1 user2 user1",
   485  			"",
   486  			[]string{"foo", "bar", "default"},
   487  		},
   488  		{
   489  			"roles: list of string",
   490  			"",
   491  			nil,
   492  			"rollen",
   493  			[]string{"user1", "user2"},
   494  			"",
   495  			[]string{"foo", "bar", "default"},
   496  		},
   497  		{
   498  			"roles: list of string, multiple",
   499  			"",
   500  			nil,
   501  			"rollen",
   502  			[]string{"user1", "user2", "user2"},
   503  			"",
   504  			[]string{"foo", "bar", "default"},
   505  		},
   506  		{
   507  			"roles: list of string, no additional 1, permission mapped",
   508  			"",
   509  			nil,
   510  			"rollen",
   511  			[]string{"admin", "user1"},
   512  			"",
   513  			[]string{"foo", "bar", "baz", "default", "blubb"},
   514  		},
   515  		{
   516  			"roles: list of string, no additional 2, permission mapped",
   517  			"",
   518  			nil,
   519  			"rollen",
   520  			[]string{"admin", "user2"},
   521  			"",
   522  			[]string{"foo", "bar", "baz", "default", "blubb"},
   523  		},
   524  		{
   525  			"roles: warn: boolean",
   526  			"",
   527  			nil,
   528  			"roles",
   529  			true,
   530  			"invalid roles claim value type, ignoring claim, value true",
   531  			[]string{"default"},
   532  		},
   533  		{
   534  			"roles: warn: number",
   535  			"",
   536  			nil,
   537  			"roles",
   538  			1.23,
   539  			"invalid roles claim value type, ignoring claim, value 1.23",
   540  			[]string{"default"},
   541  		},
   542  		{
   543  			"roles: warn: list of bool",
   544  			"",
   545  			nil,
   546  			"roles",
   547  			[]bool{true, false},
   548  			"invalid roles claim value type, ignoring claim, value []interface {}{true, false}",
   549  			[]string{"default"},
   550  		},
   551  		{
   552  			"roles: warn: list of number",
   553  			"",
   554  			nil,
   555  			"roles",
   556  			[]int{1, 2},
   557  			"invalid roles claim value type, ignoring claim, value []interface {}{1, 2}",
   558  			[]string{"default"},
   559  		},
   560  		{
   561  			"roles: warn: mixed list",
   562  			"",
   563  			nil,
   564  			"roles",
   565  			[]interface{}{"user1", 2},
   566  			`invalid roles claim value type, ignoring claim, value []interface {}{"user1", 2}`,
   567  			[]string{"default"},
   568  		},
   569  		{
   570  			"roles: warn: object",
   571  			"",
   572  			nil,
   573  			"roles",
   574  			map[string]interface{}{"foo": 1, "bar": 1},
   575  			`invalid roles claim value type, ignoring claim, value map[string]interface {}{"bar":1, "foo":1}`,
   576  			[]string{"default"},
   577  		},
   578  		{
   579  			"combi 1",
   580  			"scope",
   581  			"foo foo",
   582  			"roles",
   583  			[]string{"user2"},
   584  			"",
   585  			[]string{"foo", "bar", "default"},
   586  		},
   587  		{
   588  			"combi 2, permission mapped",
   589  			"scope",
   590  			[]string{"foo", "bar"},
   591  			"roles",
   592  			"admin",
   593  			"",
   594  			[]string{"foo", "bar", "baz", "default", "blubb"},
   595  		},
   596  	}
   597  	for _, tt := range tests {
   598  		t.Run(tt.name, func(subT *testing.T) {
   599  			hook.Reset()
   600  			claims := jwt.MapClaims{}
   601  			if tt.permissionsClaim != "" && tt.permissionsValue != nil {
   602  				claims[tt.permissionsClaim] = tt.permissionsValue
   603  			}
   604  			if tt.rolesClaim != "" && tt.rolesValue != nil {
   605  				claims[tt.rolesClaim] = tt.rolesValue
   606  			}
   607  			tok := jwt.NewWithClaims(signingMethod, claims)
   608  			pubKeyBytes := []byte("mySecretK3y")
   609  			token, tokenErr := tok.SignedString(pubKeyBytes)
   610  			if tokenErr != nil {
   611  				subT.Error(tokenErr)
   612  			}
   613  
   614  			source := ac.NewJWTSource("", "Authorization", nil)
   615  			j, err := ac.NewJWT(&ac.JWTOptions{
   616  				Algorithm:        algo.String(),
   617  				Name:             "test_ac",
   618  				PermissionsClaim: tt.permissionsClaim,
   619  				PermissionsMap:   permissionsMap,
   620  				RolesClaim:       tt.rolesClaim,
   621  				RolesMap:         rolesMap,
   622  				Source:           source,
   623  				Key:              pubKeyBytes,
   624  			})
   625  			if err != nil {
   626  				subT.Fatal(err)
   627  			}
   628  
   629  			req := setCookieAndHeader(httptest.NewRequest(http.MethodGet, "/", nil), "Authorization", "BeAreR "+token)
   630  			req = req.WithContext(context.WithValue(context.Background(), request.LogEntry, log.WithContext(context.Background())))
   631  
   632  			if err = j.Validate(req); err != nil {
   633  				subT.Errorf("Unexpected error = %v", err)
   634  				return
   635  			}
   636  
   637  			grantedPermissionsList, ok := req.Context().Value(request.GrantedPermissions).([]string)
   638  			if !ok {
   639  				subT.Errorf("Expected granted permissions within request context")
   640  			} else {
   641  				if !reflect.DeepEqual(tt.expGrantedPerms, grantedPermissionsList) {
   642  					subT.Errorf("Granted permissions do not match, want: %#v, got: %#v", tt.expGrantedPerms, grantedPermissionsList)
   643  				}
   644  			}
   645  
   646  			entries := hook.AllEntries()
   647  			if tt.expWarning == "" {
   648  				if len(entries) > 0 {
   649  					subT.Errorf("Expected no log messages, got: %d", len(entries))
   650  				}
   651  				return
   652  			}
   653  			if len(entries) != 1 {
   654  				subT.Errorf("Expected one log message: got: %d", len(entries))
   655  				return
   656  			}
   657  			entry := entries[0]
   658  			if entry.Level != logrus.WarnLevel {
   659  				subT.Errorf("Expected warning, got: %v", entry.Level)
   660  				return
   661  			}
   662  			if entry.Message != tt.expWarning {
   663  				subT.Errorf("Warning mismatch,\n\twant: %s,\n\tgot: %s", tt.expWarning, entry.Message)
   664  			}
   665  		})
   666  	}
   667  }
   668  
   669  func TestJwtConfig(t *testing.T) {
   670  
   671  	const backendURL = "http://blackhole.webpagetest.org/"
   672  
   673  	tests := []struct {
   674  		name  string
   675  		hcl   string
   676  		error string
   677  	}{
   678  		{
   679  			"missing both signature_algorithm/jwks_url",
   680  			`
   681  			server "test" {}
   682  			definitions {
   683  			  jwt "myac" {
   684  			  }
   685  			}
   686  			`,
   687  			"configuration error: myac: signature_algorithm or jwks_url attribute required",
   688  		},
   689  		{
   690  			"signature_algorithm, missing key/key_file",
   691  			`
   692  			server "test" {}
   693  			definitions {
   694  			  jwt "myac" {
   695  			    signature_algorithm = "HS256"
   696  			    header = "..."
   697  			  }
   698  			}
   699  			`,
   700  			"configuration error: myac: jwt key: read error: required: configured attribute or file",
   701  		},
   702  		{
   703  			"signature_algorithm, both key and key_file",
   704  			`
   705  			server "test" {}
   706  			definitions {
   707  			  jwt "myac" {
   708  			    signature_algorithm = "HS256"
   709  			    header = "..."
   710  			    key = "..."
   711  			    key_file = "testdata/secret.txt"
   712  			  }
   713  			}
   714  			`,
   715  			"configuration error: myac: jwt key: read error: configured attribute and file",
   716  		},
   717  		{
   718  			"signature_algorithm, both roles_map and roles_map_file",
   719  			`
   720  			server "test" {}
   721  			definitions {
   722  			  jwt "myac" {
   723  			    signature_algorithm = "HS256"
   724  			    header = "..."
   725  			    key = "..."
   726  			    roles_map = {}
   727  			    roles_map_file = "testdata/map.json"
   728  			  }
   729  			}
   730  			`,
   731  			"configuration error: myac: jwt roles map: read error: configured attribute and file",
   732  		},
   733  		{
   734  			"signature_algorithm, roles_map_file not found",
   735  			`
   736  			server "test" {}
   737  			definitions {
   738  			  jwt "myac" {
   739  			    signature_algorithm = "HS256"
   740  			    header = "..."
   741  			    key = "..."
   742  			    roles_map_file = "file_not_found"
   743  			  }
   744  			}
   745  			`,
   746  			"configuration error: myac: roles map: read error: open .*/testdata/file_not_found: no such file or directory",
   747  		},
   748  		{
   749  			"signature_algorithm, both permissions_map and permissions_map_file",
   750  			`
   751  			server "test" {}
   752  			definitions {
   753  			  jwt "myac" {
   754  			    signature_algorithm = "HS256"
   755  			    header = "..."
   756  			    key = "..."
   757  			    permissions_map = {}
   758  			    permissions_map_file = "testdata/map.json"
   759  			  }
   760  			}
   761  			`,
   762  			"configuration error: myac: jwt permissions map: read error: configured attribute and file",
   763  		},
   764  		{
   765  			"signature_algorithm, permissions_map_file not found",
   766  			`
   767  			server "test" {}
   768  			definitions {
   769  			  jwt "myac" {
   770  			    signature_algorithm = "HS256"
   771  			    header = "..."
   772  			    key = "..."
   773  			    permissions_map_file = "file_not_found"
   774  			  }
   775  			}
   776  			`,
   777  			"configuration error: myac: permissions map: read error: open .*/accesscontrol/file_not_found: no such file or directory",
   778  		},
   779  		{
   780  			"ok: signature_algorithm + key (default: header = Authorization)",
   781  			`
   782  			server "test" {}
   783  			definitions {
   784  			  jwt "myac" {
   785  			    signature_algorithm = "HS256"
   786  			    key = "..."
   787  			  }
   788  			}
   789  			`,
   790  			"",
   791  		},
   792  		{
   793  			"ok: signature_algorithm + key + header",
   794  			`
   795  			server "test" {}
   796  			definitions {
   797  			  jwt "myac" {
   798  			    signature_algorithm = "HS256"
   799  			    header = "..."
   800  			    key = "..."
   801  			  }
   802  			}
   803  			`,
   804  			"",
   805  		},
   806  		{
   807  			"ok: signature_algorithm + key + cookie",
   808  			`
   809  			server "test" {}
   810  			definitions {
   811  			  jwt "myac" {
   812  			    signature_algorithm = "HS256"
   813  			    cookie = "..."
   814  			    key = "..."
   815  			  }
   816  			}
   817  			`,
   818  			"",
   819  		},
   820  		{
   821  			"ok: signature_algorithm + key + token_value",
   822  			`
   823  			server "test" {}
   824  			definitions {
   825  			  jwt "myac" {
   826  			    signature_algorithm = "HS256"
   827  			    token_value = env.TOKEN
   828  			    key = "..."
   829  			  }
   830  			}
   831  			`,
   832  			"",
   833  		},
   834  		{
   835  			"token_value + header",
   836  			`
   837  			server "test" {}
   838  			definitions {
   839  			  jwt "myac" {
   840  			    signature_algorithm = "HS256"
   841  			    token_value = env.TOKEN
   842  			    header = "..."
   843  			    key = "..."
   844  			  }
   845  			}
   846  			`,
   847  			"configuration error: myac: token source is invalid",
   848  		},
   849  		{
   850  			"token_value + cookie",
   851  			`
   852  			server "test" {}
   853  			definitions {
   854  			  jwt "myac" {
   855  			    signature_algorithm = "HS256"
   856  			    token_value = env.TOKEN
   857  			    cookie = "..."
   858  			    key = "..."
   859  			  }
   860  			}
   861  			`,
   862  			"configuration error: myac: token source is invalid",
   863  		},
   864  		{
   865  			"cookie + header",
   866  			`
   867  			server "test" {}
   868  			definitions {
   869  			  jwt "myac" {
   870  			    signature_algorithm = "HS256"
   871  			    cookie = "..."
   872  			    header = "..."
   873  			    key = "..."
   874  			  }
   875  			}
   876  			`,
   877  			"configuration error: myac: token source is invalid",
   878  		},
   879  		{
   880  			"ok: signature_algorithm + key_file",
   881  			`
   882  			server "test" {}
   883  			definitions {
   884  			  jwt "myac" {
   885  			    signature_algorithm = "HS256"
   886  			    header = "..."
   887  			    key_file = "testdata/secret.txt"
   888  			  }
   889  			}
   890  			`,
   891  			"",
   892  		},
   893  		{
   894  			"ok: jwks_url",
   895  			`
   896  			server "test" {}
   897  			definitions {
   898  			  jwt "myac" {
   899  			    jwks_url = "file:jwk/testdata/jwks.json",
   900  			    header = "..."
   901  			  }
   902  			}
   903  			`,
   904  			"",
   905  		},
   906  		{
   907  			"jwks_url file not found",
   908  			`
   909  			server "test" {}
   910  			definitions {
   911  			  jwt "myac" {
   912  			    jwks_url = "file:file_not_found",
   913  			    header = "..."
   914  			  }
   915  			}
   916  			`,
   917  			"configuration error: myac: jwks_url: read error: open .*/accesscontrol/file_not_found: no such file or directory",
   918  		},
   919  		{
   920  			"signature_algorithm + jwks_url",
   921  			`
   922  			server "test" {}
   923  			definitions {
   924  			  jwt "myac" {
   925  			    signature_algorithm = "HS256"
   926  			    jwks_url = "` + backendURL + `"
   927  			    header = "..."
   928  			  }
   929  			}
   930  			`,
   931  			"configuration error: myac: signature_algorithm cannot be used together with jwks_url",
   932  		},
   933  		{
   934  			"key + jwks_url",
   935  			`
   936  			server "test" {}
   937  			definitions {
   938  			  jwt "myac" {
   939  			    key = "..."
   940  			    jwks_url = "` + backendURL + `"
   941  			    header = "..."
   942  			  }
   943  			}
   944  			`,
   945  			"configuration error: myac: key cannot be used together with jwks_url",
   946  		},
   947  		{
   948  			"key_file + jwks_url",
   949  			`
   950  			server "test" {}
   951  			definitions {
   952  			  jwt "myac" {
   953  			    key_file = "..."
   954  			    jwks_url = "` + backendURL + `"
   955  			    header = "..."
   956  			  }
   957  			}
   958  			`,
   959  			"configuration error: myac: key_file cannot be used together with jwks_url",
   960  		},
   961  		{
   962  			"backend reference, missing jwks_url",
   963  			`
   964  			server "test" {}
   965  			definitions {
   966  			  jwt "myac" {
   967  			    backend = "foo"
   968  			    header = "..."
   969  				signature_algorithm = "asdf"
   970  			  }
   971  			  backend "foo" {}
   972  			}
   973  			`,
   974  			"configuration error: myac: backend is obsolete without jwks_url attribute",
   975  		},
   976  	}
   977  
   978  	log, hook := test.NewLogger()
   979  	helper := test.New(t)
   980  
   981  	for _, tt := range tests {
   982  		t.Run(tt.name, func(subT *testing.T) {
   983  			hook.Reset()
   984  
   985  			conf, err := configload.LoadBytes([]byte(tt.hcl), "couper.hcl")
   986  			if conf != nil {
   987  				tmpStoreCh := make(chan struct{})
   988  				defer close(tmpStoreCh)
   989  
   990  				ctx, cancel := context.WithCancel(conf.Context)
   991  				conf.Context = ctx
   992  				defer cancel()
   993  
   994  				logger := log.WithContext(ctx)
   995  
   996  				_, err = runtime.NewServerConfiguration(conf, logger, cache.New(logger, tmpStoreCh))
   997  			}
   998  
   999  			var errMsg, expectedError string
  1000  			if err != nil {
  1001  				if _, ok := err.(errors.GoError); ok {
  1002  					errMsg = err.(errors.GoError).LogError()
  1003  				} else {
  1004  					errMsg = err.Error()
  1005  				}
  1006  			}
  1007  
  1008  			if tt.error == "" && errMsg == "" {
  1009  				return
  1010  			}
  1011  
  1012  			time.Sleep(time.Second / 2) // sync routine start
  1013  
  1014  			for _, e := range hook.AllEntries() {
  1015  				if e.Level != logrus.ErrorLevel {
  1016  					continue
  1017  				}
  1018  				errMsg = e.Message
  1019  				break
  1020  			}
  1021  
  1022  			re, err := regexp.Compile(expectedError)
  1023  			helper.Must(err)
  1024  			if !re.MatchString(errMsg) {
  1025  				subT.Errorf("%q: Unexpected configuration error:\n\tWant: %q\n\tGot:  %q", tt.name, expectedError, errMsg)
  1026  			}
  1027  		})
  1028  	}
  1029  }
  1030  
  1031  func newRSAKeyPair() (pubKeyBytes []byte, privKey *rsa.PrivateKey) {
  1032  	privKey, err := rsa.GenerateKey(rand.Reader, 2048)
  1033  	if err != nil {
  1034  		panic(err)
  1035  	}
  1036  	if e := privKey.Validate(); e != nil {
  1037  		panic(e)
  1038  	}
  1039  
  1040  	pubKeyBytes = pem.EncodeToMemory(&pem.Block{
  1041  		Type:  "RSA PUBLIC KEY",
  1042  		Bytes: x509.MarshalPKCS1PublicKey(&privKey.PublicKey),
  1043  	})
  1044  	return
  1045  }
  1046  
  1047  func setCookieAndHeader(req *http.Request, key, value string) *http.Request {
  1048  	req.Header.Set(key, value)
  1049  	req.Header.Set("Cookie", key+"="+value)
  1050  	return req
  1051  }
  1052  
  1053  func setContext(req *http.Request) *http.Request {
  1054  	evalCtx := eval.ContextFromRequest(req)
  1055  	*req = *req.WithContext(evalCtx.WithClientRequest(req))
  1056  	return req
  1057  }