Skip to content

Commit b176a54

Browse files
authored
Adds optional validate_iss_claim config param to allow skip iss validation (#91)
1 parent 371c3bd commit b176a54

File tree

6 files changed

+266
-36
lines changed

6 files changed

+266
-36
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/hashicorp/go-hclog v0.12.0
1212
github.com/hashicorp/go-multierror v1.0.0
1313
github.com/hashicorp/go-sockaddr v1.0.2
14+
github.com/hashicorp/go-version v1.2.0 // indirect
1415
github.com/hashicorp/vault/api v1.0.5-0.20200215224050-f6547fa8e820
1516
github.com/hashicorp/vault/sdk v0.1.14-0.20200215224050-f6547fa8e820
1617
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2I
7474
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
7575
github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0=
7676
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
77+
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
78+
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
7779
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
7880
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
7981
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=

path_config.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ extracted. Not every installation of Kuberentes exposes these keys.`,
5757
Name: "JWT Issuer",
5858
},
5959
},
60+
"disable_iss_validation": {
61+
Type: framework.TypeBool,
62+
Description: "Disable JWT issuer validation. Allows to skip ISS validation.",
63+
Default: false,
64+
DisplayAttrs: &framework.DisplayAttributes{
65+
Name: "Disable JWT Issuer Validation",
66+
},
67+
},
6068
},
6169
Callbacks: map[logical.Operation]framework.OperationFunc{
6270
logical.UpdateOperation: b.pathConfigWrite,
@@ -79,10 +87,11 @@ func (b *kubeAuthBackend) pathConfigRead(ctx context.Context, req *logical.Reque
7987
// Create a map of data to be returned
8088
resp := &logical.Response{
8189
Data: map[string]interface{}{
82-
"kubernetes_host": config.Host,
83-
"kubernetes_ca_cert": config.CACert,
84-
"pem_keys": config.PEMKeys,
85-
"issuer": config.Issuer,
90+
"kubernetes_host": config.Host,
91+
"kubernetes_ca_cert": config.CACert,
92+
"pem_keys": config.PEMKeys,
93+
"issuer": config.Issuer,
94+
"disable_iss_validation": config.DisableISSValidation,
8695
},
8796
}
8897

@@ -100,6 +109,7 @@ func (b *kubeAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Requ
100109
pemList := data.Get("pem_keys").([]string)
101110
caCert := data.Get("kubernetes_ca_cert").(string)
102111
issuer := data.Get("issuer").(string)
112+
disableIssValidation := data.Get("disable_iss_validation").(bool)
103113
if len(pemList) == 0 && len(caCert) == 0 {
104114
return logical.ErrorResponse("one of pem_keys or kubernetes_ca_cert must be set"), nil
105115
}
@@ -114,12 +124,13 @@ func (b *kubeAuthBackend) pathConfigWrite(ctx context.Context, req *logical.Requ
114124
}
115125

116126
config := &kubeConfig{
117-
PublicKeys: make([]interface{}, len(pemList)),
118-
PEMKeys: pemList,
119-
Host: host,
120-
CACert: caCert,
121-
TokenReviewerJWT: tokenReviewer,
122-
Issuer: issuer,
127+
PublicKeys: make([]interface{}, len(pemList)),
128+
PEMKeys: pemList,
129+
Host: host,
130+
CACert: caCert,
131+
TokenReviewerJWT: tokenReviewer,
132+
Issuer: issuer,
133+
DisableISSValidation: disableIssValidation,
123134
}
124135

125136
var err error
@@ -157,6 +168,8 @@ type kubeConfig struct {
157168
TokenReviewerJWT string `json:"token_reviewer_jwt"`
158169
// Issuer is the claim that specifies who issued the token
159170
Issuer string `json:"issuer"`
171+
// DisableISSValidation is optional parameter to allow to skip ISS validation
172+
DisableISSValidation bool `json:"disable_iss_validation"`
160173
}
161174

162175
// PasrsePublicKeyPEM is used to parse RSA and ECDSA public keys from PEMs

path_config_test.go

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ func TestConfig_Read(t *testing.T) {
1212
b, storage := getBackend(t)
1313

1414
data := map[string]interface{}{
15-
"pem_keys": []string{testRSACert, testECCert},
16-
"kubernetes_host": "host",
17-
"kubernetes_ca_cert": testCACert,
18-
"issuer": "",
15+
"pem_keys": []string{testRSACert, testECCert},
16+
"kubernetes_host": "host",
17+
"kubernetes_ca_cert": testCACert,
18+
"issuer": "",
19+
"disable_iss_validation": false,
1920
}
2021

2122
req := &logical.Request{
@@ -135,10 +136,11 @@ func TestConfig(t *testing.T) {
135136
}
136137

137138
expected := &kubeConfig{
138-
PublicKeys: []interface{}{},
139-
PEMKeys: []string{},
140-
Host: "host",
141-
CACert: testCACert,
139+
PublicKeys: []interface{}{},
140+
PEMKeys: []string{},
141+
Host: "host",
142+
CACert: testCACert,
143+
DisableISSValidation: false,
142144
}
143145

144146
conf, err := b.(*kubeAuthBackend).config(context.Background(), storage)
@@ -175,11 +177,12 @@ func TestConfig(t *testing.T) {
175177
}
176178

177179
expected = &kubeConfig{
178-
PublicKeys: []interface{}{},
179-
PEMKeys: []string{},
180-
Host: "host",
181-
CACert: testCACert,
182-
TokenReviewerJWT: jwtData,
180+
PublicKeys: []interface{}{},
181+
PEMKeys: []string{},
182+
Host: "host",
183+
CACert: testCACert,
184+
TokenReviewerJWT: jwtData,
185+
DisableISSValidation: false,
183186
}
184187

185188
conf, err = b.(*kubeAuthBackend).config(context.Background(), storage)
@@ -216,10 +219,11 @@ func TestConfig(t *testing.T) {
216219
}
217220

218221
expected = &kubeConfig{
219-
PublicKeys: []interface{}{cert},
220-
PEMKeys: []string{testRSACert},
221-
Host: "host",
222-
CACert: testCACert,
222+
PublicKeys: []interface{}{cert},
223+
PEMKeys: []string{testRSACert},
224+
Host: "host",
225+
CACert: testCACert,
226+
DisableISSValidation: false,
223227
}
224228

225229
conf, err = b.(*kubeAuthBackend).config(context.Background(), storage)
@@ -261,10 +265,52 @@ func TestConfig(t *testing.T) {
261265
}
262266

263267
expected = &kubeConfig{
264-
PublicKeys: []interface{}{cert, cert2},
265-
PEMKeys: []string{testRSACert, testECCert},
266-
Host: "host",
267-
CACert: testCACert,
268+
PublicKeys: []interface{}{cert, cert2},
269+
PEMKeys: []string{testRSACert, testECCert},
270+
Host: "host",
271+
CACert: testCACert,
272+
DisableISSValidation: false,
273+
}
274+
275+
conf, err = b.(*kubeAuthBackend).config(context.Background(), storage)
276+
if err != nil {
277+
t.Fatal(err)
278+
}
279+
280+
if !reflect.DeepEqual(expected, conf) {
281+
t.Fatalf("expected did not match actual: expected %#v\n got %#v\n", expected, conf)
282+
}
283+
284+
// Test success with disabled iss validation
285+
data = map[string]interface{}{
286+
"kubernetes_host": "host",
287+
"kubernetes_ca_cert": testCACert,
288+
"disable_iss_validation": true,
289+
}
290+
291+
req = &logical.Request{
292+
Operation: logical.CreateOperation,
293+
Path: configPath,
294+
Storage: storage,
295+
Data: data,
296+
}
297+
298+
resp, err = b.HandleRequest(context.Background(), req)
299+
if err != nil || (resp != nil && resp.IsError()) {
300+
t.Fatalf("err:%s resp:%#v\n", err, resp)
301+
}
302+
303+
cert, err = parsePublicKeyPEM([]byte(testRSACert))
304+
if err != nil {
305+
t.Fatal(err)
306+
}
307+
308+
expected = &kubeConfig{
309+
PublicKeys: []interface{}{},
310+
PEMKeys: []string{},
311+
Host: "host",
312+
CACert: testCACert,
313+
DisableISSValidation: true,
268314
}
269315

270316
conf, err = b.(*kubeAuthBackend).config(context.Background(), storage)

path_login.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,14 @@ func (b *kubeAuthBackend) parseAndValidateJWT(jwtStr string, role *roleStorageEn
203203
},
204204
}
205205

206-
// set the expected issuer to the default kubernetes issuer if the config doesn't specify it
207-
if config.Issuer != "" {
208-
validator.SetIssuer(config.Issuer)
209-
} else {
210-
validator.SetIssuer(defaultJWTIssuer)
206+
// perform ISS Claim validation if configured
207+
if !config.DisableISSValidation {
208+
// set the expected issuer to the default kubernetes issuer if the config doesn't specify it
209+
if config.Issuer != "" {
210+
validator.SetIssuer(config.Issuer)
211+
} else {
212+
validator.SetIssuer(defaultJWTIssuer)
213+
}
211214
}
212215

213216
// validate the audience if the role expects it

path_login_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,6 +561,171 @@ func TestAliasLookAhead(t *testing.T) {
561561
}
562562
}
563563

564+
func TestLoginIssValidation(t *testing.T) {
565+
b, storage := setupBackend(t, testNoPEMs, testName, testNamespace)
566+
567+
// test iss validation enabled with default "kubernetes/serviceaccount" issuer
568+
data := map[string]interface{}{
569+
"kubernetes_host": "host",
570+
"kubernetes_ca_cert": testCACert,
571+
"disable_iss_validation": false,
572+
}
573+
574+
req := &logical.Request{
575+
Operation: logical.CreateOperation,
576+
Path: "config",
577+
Storage: storage,
578+
Data: data,
579+
}
580+
581+
resp, err := b.HandleRequest(context.Background(), req)
582+
if err != nil || (resp != nil && resp.IsError()) {
583+
t.Fatalf("err:%s resp:%#v\n", err, resp)
584+
}
585+
586+
// test successful login with default issuer
587+
data = map[string]interface{}{
588+
"role": "plugin-test",
589+
"jwt": jwtData,
590+
}
591+
592+
req = &logical.Request{
593+
Operation: logical.UpdateOperation,
594+
Path: "login",
595+
Storage: storage,
596+
Data: data,
597+
Connection: &logical.Connection{
598+
RemoteAddr: "127.0.0.1",
599+
},
600+
}
601+
602+
// test iss validation enabled with explicitly defined issuer
603+
data = map[string]interface{}{
604+
"kubernetes_host": "host",
605+
"kubernetes_ca_cert": testCACert,
606+
"disable_iss_validation": false,
607+
"issuer": "kubernetes/serviceaccount",
608+
}
609+
610+
req = &logical.Request{
611+
Operation: logical.CreateOperation,
612+
Path: "config",
613+
Storage: storage,
614+
Data: data,
615+
}
616+
617+
resp, err = b.HandleRequest(context.Background(), req)
618+
if err != nil || (resp != nil && resp.IsError()) {
619+
t.Fatalf("err:%s resp:%#v\n", err, resp)
620+
}
621+
622+
// test successful login with explicitly defined issuer
623+
data = map[string]interface{}{
624+
"role": "plugin-test",
625+
"jwt": jwtData,
626+
}
627+
628+
req = &logical.Request{
629+
Operation: logical.UpdateOperation,
630+
Path: "login",
631+
Storage: storage,
632+
Data: data,
633+
Connection: &logical.Connection{
634+
RemoteAddr: "127.0.0.1",
635+
},
636+
}
637+
638+
resp, err = b.HandleRequest(context.Background(), req)
639+
if err != nil || (resp != nil && resp.IsError()) {
640+
t.Fatalf("err:%s resp:%#v\n", err, resp)
641+
}
642+
643+
// test iss validation enabled with custom issuer
644+
data = map[string]interface{}{
645+
"kubernetes_host": "host",
646+
"kubernetes_ca_cert": testCACert,
647+
"disable_iss_validation": false,
648+
"issuer": "custom-issuer",
649+
}
650+
651+
req = &logical.Request{
652+
Operation: logical.CreateOperation,
653+
Path: "config",
654+
Storage: storage,
655+
Data: data,
656+
}
657+
658+
resp, err = b.HandleRequest(context.Background(), req)
659+
if err != nil || (resp != nil && resp.IsError()) {
660+
t.Fatalf("err:%s resp:%#v\n", err, resp)
661+
}
662+
663+
// test login fail with enabled iss validation and custom issuer
664+
data = map[string]interface{}{
665+
"role": "plugin-test",
666+
"jwt": jwtData,
667+
}
668+
669+
req = &logical.Request{
670+
Operation: logical.UpdateOperation,
671+
Path: "login",
672+
Storage: storage,
673+
Data: data,
674+
Connection: &logical.Connection{
675+
RemoteAddr: "127.0.0.1",
676+
},
677+
}
678+
679+
resp, err = b.HandleRequest(context.Background(), req)
680+
if err == nil {
681+
t.Fatal("expected error")
682+
}
683+
if err.Error() != "claim \"iss\" is invalid" {
684+
t.Fatalf("unexpected error: %s", err)
685+
}
686+
687+
// test iss validation disabled with custom issuer
688+
data = map[string]interface{}{
689+
"kubernetes_host": "host",
690+
"kubernetes_ca_cert": testCACert,
691+
"disable_iss_validation": true,
692+
"issuer": "custom-issuer",
693+
}
694+
695+
req = &logical.Request{
696+
Operation: logical.CreateOperation,
697+
Path: "config",
698+
Storage: storage,
699+
Data: data,
700+
}
701+
702+
resp, err = b.HandleRequest(context.Background(), req)
703+
if err != nil || (resp != nil && resp.IsError()) {
704+
t.Fatalf("err:%s resp:%#v\n", err, resp)
705+
}
706+
707+
// test login success with disabled iss validation and custom issuer
708+
data = map[string]interface{}{
709+
"role": "plugin-test",
710+
"jwt": jwtData,
711+
}
712+
713+
req = &logical.Request{
714+
Operation: logical.UpdateOperation,
715+
Path: "login",
716+
Storage: storage,
717+
Data: data,
718+
Connection: &logical.Connection{
719+
RemoteAddr: "127.0.0.1",
720+
},
721+
}
722+
723+
resp, err = b.HandleRequest(context.Background(), req)
724+
if err != nil || (resp != nil && resp.IsError()) {
725+
t.Fatalf("err:%s resp:%#v\n", err, resp)
726+
}
727+
}
728+
564729
var jwtData = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InZhdWx0LWF1dGgtdG9rZW4tdDVwY24iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoidmF1bHQtYXV0aCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImQ3N2Y4OWJjLTkwNTUtMTFlNy1hMDY4LTA4MDAyNzZkOTliZiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnZhdWx0LWF1dGgifQ.HKUcqgrvan5ZC_mnpaMEx4RW3KrhfyH_u8G_IA2vUfkLK8tH3T7fJuJaPr7W6K_BqCrbeM5y3owszOzb4NR0Lvw6GBt2cFcen2x1Ua4Wokr0bJjTT7xQOIOw7UvUDyVS17wAurlfUnmWMwMMMOebpqj5K1t6GnyqghH1wPdHYRGX-q5a6C323dBCgM5t6JY_zTTaBgM6EkFq0poBaifmSMiJRPrdUN_-IgyK8fgQRiFYYkgS6DMIU4k4nUOb_sUFf5xb8vMs3SMteKiuWFAIt4iszXTj5IyBUNqe0cXA3zSY3QiNCV6bJ2CWW0Qf9WDtniT79VAqcR4GYaTC_gxjNA"
565730

566731
var jwtBadServiceAccount = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6InZhdWx0LWludmFsaWQtdG9rZW4tZ3ZxcHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC5uYW1lIjoidmF1bHQtaW52YWxpZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjA0NGZkNGYxLTk3NGQtMTFlNy05YTE1LTA4MDAyNzZkOTliZiIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDpkZWZhdWx0OnZhdWx0LWludmFsaWQifQ.BcoOdu5BrIchp66Zl8-dY7HcGHJrVXrUh4SNTlIHR6vDaNH29B7JuI_-B1pvW9GpzQnc-XjZyua_wfSssqe-KYJcq--Qh0yQfbbLE5rvEipBCHH341IqGaTHaBVip8zXqYE-bt-7J6vAH8Azvw46iatDC73tKxh46xDuxK0gKjdprW4cOklDx6ZSxEHpu63ftLYgAgk9c0MUJxKWhu9Jk0aye5pTj_iyBbBy8llZNGaw2gxvhPzFVUEHZUlTRiSIbmPmNqep48RiJoWrq6FM1lijvrtT5y-E7aFk6TpW2BH3VDHy8k10sMIxuRAYrGB3tpUKNyVDI3tJOi_xY7iJvw"

0 commit comments

Comments
 (0)