Skip to content

Commit a680bc3

Browse files
authored
Merge pull request #8 from jr200/feature/support_multiple_idps
feature: support multiple identity providers
2 parents 5950ddf + d60da1f commit a680bc3

File tree

12 files changed

+176
-87
lines changed

12 files changed

+176
-87
lines changed

charts/nats-iam-broker/Chart.lock

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ dependencies:
33
repository: https://jr200.github.io/helm-charts
44
version: 0.1.0
55
digest: sha256:97cb234857c08f073577d9ef5926ec4279177d2178dcb9e7d7b44600f704ed91
6-
generated: "2024-09-07T11:48:09.203889+01:00"
6+
generated: "2024-09-18T14:32:55.799136+01:00"

charts/nats-iam-broker/Chart.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ version: 0.1.0
2121
# incremented each time you make changes to the application. Versions are not expected to
2222
# follow Semantic Versioning. They should reflect the version the application is using.
2323
# It is recommended to use it with quotes.
24-
appVersion: "v0.1.5"
24+
appVersion: "v0.1.6"
2525

2626
dependencies:
2727
- name: vault-actions

charts/nats-iam-broker/values.yaml

+22-22
Original file line numberDiff line numberDiff line change
@@ -68,28 +68,28 @@ config:
6868

6969
# configuration of the third-party identity provider that supplied the JWT token
7070
idp:
71-
# The client ID for the identity provider
72-
client_id: public
73-
74-
# URLs of the identity provider issuer
75-
issuer_url:
76-
- https://oidctest.wsweet.org/
77-
78-
validation:
79-
# claims required to be present on the incoming JWT validation
80-
claims:
81-
- aud
82-
- iat
83-
- exp
84-
- sub
85-
# Expected values for the audience claim of the JWT
86-
# to skip this check set it to []
87-
aud:
88-
- public
89-
# acceptable expiration bounds for incoming JWT
90-
exp:
91-
min: 1m0s
92-
max: 2h
71+
- description: oidc-public
72+
# The client ID for the identity provider
73+
client_id: public
74+
75+
# URLs of the identity provider issuer
76+
issuer_url: https://oidctest.wsweet.org/
77+
78+
validation:
79+
# claims required to be present on the incoming JWT validation
80+
claims:
81+
- aud
82+
- iat
83+
- exp
84+
- sub
85+
# Expected values for the audience claim of the JWT
86+
# to skip this check set it to []
87+
aud:
88+
- public
89+
# acceptable expiration bounds for incoming JWT
90+
exp:
91+
min: 1m0s
92+
max: 2h
9393

9494
# role-based access control configuration section
9595
rbac:

configs/idp_private.yaml

+30-14
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
idp:
2-
client_id: private
3-
issuer_url:
4-
- https://oidctest.wsweet.org/
2+
- description: oidctest-private
3+
client_id: private
4+
issuer_url: https://oidctest.wsweet.org/
55

6-
validation:
7-
claims:
8-
- aud
9-
- iat
10-
- exp
11-
- sub
12-
aud:
13-
- private
14-
exp:
15-
min: 1m0s
16-
max: 2h
6+
validation:
7+
claims:
8+
- aud
9+
- iat
10+
- exp
11+
- sub
12+
aud:
13+
- private
14+
exp:
15+
min: 1m0s
16+
max: 2h
17+
18+
- description: random-ununused-item
19+
client_id: random
20+
issuer_url: https://oidctest.wsweet.org/
21+
22+
validation:
23+
claims:
24+
- aud
25+
- iat
26+
- exp
27+
- sub
28+
aud:
29+
- random-aud
30+
exp:
31+
min: 1m0s
32+
max: 2h

configs/idp_public.yaml

+46-14
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,48 @@
11
idp:
2-
client_id: public
3-
issuer_url:
4-
- https://oidctest.wsweet.org/
2+
- description: random-ununused-item
3+
client_id: random-start
4+
issuer_url: https://oidctest.wsweet.org/
55

6-
validation:
7-
claims:
8-
- aud
9-
- iat
10-
- exp
11-
- sub
12-
aud:
13-
- public
14-
exp:
15-
min: 1m0s
16-
max: 2h
6+
validation:
7+
claims:
8+
- aud
9+
- iat
10+
- exp
11+
- sub
12+
aud:
13+
- random-aud
14+
exp:
15+
min: 1m0s
16+
max: 2h
17+
18+
- description: oidctest-public
19+
client_id: public
20+
issuer_url: https://oidctest.wsweet.org/
21+
22+
validation:
23+
claims:
24+
- aud
25+
- iat
26+
- exp
27+
- sub
28+
aud:
29+
- public
30+
exp:
31+
min: 1m0s
32+
max: 2h
33+
34+
- description: random-ununused-item
35+
client_id: random-end
36+
issuer_url: https://oidctest.wsweet.org/
37+
38+
validation:
39+
claims:
40+
- aud
41+
- iat
42+
- exp
43+
- sub
44+
aud:
45+
- random-aud
46+
exp:
47+
min: 1m0s
48+
max: 2h

docker/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ RUN apk update && apk add --no-cache git bash curl jq make
88

99
RUN go install github.com/nats-io/nats-server/[email protected]
1010
RUN go install github.com/nats-io/natscli/[email protected]
11-
RUN go install github.com/nats-io/nsc/v2@v2.8.7
11+
RUN go install github.com/nats-io/nsc/v2@v2.9.0
1212

1313
WORKDIR /usr/src/app
1414

docker/Dockerfile.example

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ RUN apk update && apk add --no-cache git bash curl jq make
88

99
RUN go install github.com/nats-io/nats-server/[email protected]
1010
RUN go install github.com/nats-io/natscli/[email protected]
11-
RUN go install github.com/nats-io/nsc/v2@v2.8.7
11+
RUN go install github.com/nats-io/nsc/v2@v2.9.0
1212

1313
WORKDIR /usr/src/app
1414

go.mod

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
module github.com/jr200/nats-iam-broker
22

3-
go 1.22
3+
go 1.22.0
4+
5+
toolchain go1.22.5
46

57
require (
68
github.com/coreos/go-oidc/v3 v3.11.0
79
github.com/go-playground/validator v9.31.0+incompatible
8-
github.com/nats-io/jwt/v2 v2.5.8
10+
github.com/nats-io/jwt/v2 v2.7.0
911
github.com/nats-io/nats.go v1.37.0
1012
github.com/nats-io/nkeys v0.4.7
1113
github.com/rs/zerolog v1.33.0
@@ -18,7 +20,7 @@ require (
1820
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
1921
github.com/go-playground/locales v0.14.1 // indirect
2022
github.com/go-playground/universal-translator v0.18.1 // indirect
21-
github.com/go-playground/validator/v10 v10.22.0 // indirect
23+
github.com/go-playground/validator/v10 v10.22.1 // indirect
2224
github.com/klauspost/compress v1.17.9 // indirect
2325
github.com/kr/pretty v0.3.1 // indirect
2426
github.com/kr/text v0.2.0 // indirect
@@ -28,12 +30,12 @@ require (
2830
github.com/nats-io/nuid v1.0.1 // indirect
2931
github.com/rogpeppe/go-internal v1.12.0 // indirect
3032
go.uber.org/multierr v1.11.0 // indirect
31-
golang.org/x/crypto v0.26.0 // indirect
33+
golang.org/x/crypto v0.27.0 // indirect
3234
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
33-
golang.org/x/net v0.28.0 // indirect
34-
golang.org/x/oauth2 v0.22.0 // indirect
35-
golang.org/x/sys v0.24.0 // indirect
36-
golang.org/x/text v0.17.0 // indirect
37-
golang.org/x/tools v0.24.0 // indirect
35+
golang.org/x/net v0.29.0 // indirect
36+
golang.org/x/oauth2 v0.23.0 // indirect
37+
golang.org/x/sys v0.25.0 // indirect
38+
golang.org/x/text v0.18.0 // indirect
39+
golang.org/x/tools v0.25.0 // indirect
3840
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
3941
)

go.sum

+16
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp
2020
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
2121
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
2222
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
23+
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
24+
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
2325
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
2426
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
2527
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -45,6 +47,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
4547
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
4648
github.com/nats-io/jwt/v2 v2.5.8 h1:uvdSzwWiEGWGXf+0Q+70qv6AQdvcvxrv9hPM0RiPamE=
4749
github.com/nats-io/jwt/v2 v2.5.8/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A=
50+
github.com/nats-io/jwt/v2 v2.7.0 h1:J+ZnaaMGQi3xSB8iOhVM5ipiWCDrQvgEoitTwWFyOYw=
51+
github.com/nats-io/jwt/v2 v2.7.0/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A=
4852
github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU=
4953
github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
5054
github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE=
@@ -83,6 +87,8 @@ golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
8387
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
8488
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
8589
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
90+
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
91+
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
8692
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
8793
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
8894
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
@@ -97,8 +103,12 @@ golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
97103
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
98104
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
99105
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
106+
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
107+
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
100108
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
101109
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
110+
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
111+
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
102112
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
103113
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
104114
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
@@ -111,12 +121,16 @@ golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
111121
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
112122
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
113123
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
124+
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
125+
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
114126
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
115127
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
116128
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
117129
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
118130
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
119131
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
132+
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
133+
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
120134
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
121135
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
122136
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
@@ -128,6 +142,8 @@ golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
128142
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
129143
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
130144
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
145+
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
146+
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
131147
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
132148
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
133149
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

internal/server/config.go

+3-10
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type Config struct {
2020
AppParams ConfigParams `yaml:"params"`
2121
NATS NATS `yaml:"nats" validate:"required"`
2222
Service Service `yaml:"service" validate:"required"`
23-
Idp Idp `yaml:"idp" validate:"required"`
23+
Idp []Idp `yaml:"idp" validate:"required"`
2424
NatsJwt NatsJwt `yaml:"nats_jwt" validate:"required"`
2525
Rbac Rbac `yaml:"rbac" validate:"required"`
2626
}
@@ -54,7 +54,8 @@ type Encryption struct {
5454
}
5555

5656
type Idp struct {
57-
IssuerURL []string `yaml:"issuer_url" validate:"required"`
57+
Description string `yaml:"description"`
58+
IssuerURL string `yaml:"issuer_url" validate:"required"`
5859
ClientID string `yaml:"client_id" validate:"required"`
5960
ValidationSpec IdpJwtValidationSpec `yaml:"validation"`
6061
}
@@ -151,14 +152,6 @@ func readConfigFiles(files []string, mappings map[string]interface{}) (*Config,
151152
LeftDelim: "{{",
152153
RightDelim: "}}",
153154
},
154-
Idp: Idp{
155-
ValidationSpec: IdpJwtValidationSpec{
156-
Expiry: DurationBounds{
157-
Min: Duration{time.Duration(0)},
158-
Max: Duration{time.Duration(24 * time.Hour)},
159-
},
160-
},
161-
},
162155
}
163156

164157
if err := yaml.Unmarshal([]byte(mergedYAML), &cfg); err != nil {

internal/server/idp_jwt_verifier.go

+41-3
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,58 @@ import (
99
"github.com/rs/zerolog/log"
1010
)
1111

12+
type IdpAndJwtVerifier struct {
13+
verifier *IdpJwtVerifier
14+
config *Idp
15+
}
16+
17+
func NewIdpVerifiers(config *Config) ([]IdpAndJwtVerifier, error) {
18+
idpVerifiers := make([]IdpAndJwtVerifier, 0, len(config.Idp))
19+
for _, idp := range config.Idp {
20+
idpVerifier, err := NewJwtVerifier(context.Background(), idp.ClientID, idp.IssuerURL)
21+
if err != nil {
22+
return nil, err
23+
}
24+
idpVerifiers = append(idpVerifiers, IdpAndJwtVerifier{idpVerifier, &idp})
25+
}
26+
return idpVerifiers, nil
27+
}
28+
29+
func runVerification(jwtToken string, items []IdpAndJwtVerifier) (*IdpJwtClaims, error) {
30+
for _, item := range items {
31+
reqClaims, err := item.verifier.verifyJWT(jwtToken)
32+
if err != nil {
33+
log.Trace().Err(err).Msg("error verifying idp-jwt")
34+
continue
35+
}
36+
37+
err = item.verifier.validateAgainstSpec(reqClaims, item.config.ValidationSpec)
38+
if err != nil {
39+
log.Trace().Err(err).Msg("failed checks in idp validation")
40+
continue
41+
}
42+
43+
return reqClaims, nil
44+
}
45+
46+
return nil, errors.New("no idp verifier found for jwtToken")
47+
}
48+
1249
type IdpJwtVerifier struct {
1350
*oidc.IDTokenVerifier
1451
MaxTokenLifetime time.Duration
1552
ClockSkew time.Duration
1653
}
1754

18-
func NewJwtVerifier(ctx context.Context, clientID string, issuerUrl []string) (*IdpJwtVerifier, error) {
19-
// TODO: support multiple issuers
20-
provider, err := oidc.NewProvider(ctx, issuerUrl[0])
55+
func NewJwtVerifier(ctx context.Context, clientID string, issuerUrl string) (*IdpJwtVerifier, error) {
56+
provider, err := oidc.NewProvider(ctx, issuerUrl)
2157
if err != nil {
2258
log.Err(err)
2359
return nil, err
2460
}
2561

62+
log.Trace().Msgf("NewJwtVerifier (config-params) clientId=%s, issuerUrl=%s", clientID, issuerUrl)
63+
2664
return &IdpJwtVerifier{provider.Verifier(&oidc.Config{ClientID: clientID}), time.Hour * 24, time.Minute * 5}, nil
2765
}
2866

0 commit comments

Comments
 (0)