Skip to content

Commit

Permalink
Merge pull request #8 from jr200/feature/support_multiple_idps
Browse files Browse the repository at this point in the history
feature: support multiple identity providers
  • Loading branch information
jr200 authored Sep 18, 2024
2 parents 5950ddf + d60da1f commit a680bc3
Show file tree
Hide file tree
Showing 12 changed files with 176 additions and 87 deletions.
2 changes: 1 addition & 1 deletion charts/nats-iam-broker/Chart.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ dependencies:
repository: https://jr200.github.io/helm-charts
version: 0.1.0
digest: sha256:97cb234857c08f073577d9ef5926ec4279177d2178dcb9e7d7b44600f704ed91
generated: "2024-09-07T11:48:09.203889+01:00"
generated: "2024-09-18T14:32:55.799136+01:00"
2 changes: 1 addition & 1 deletion charts/nats-iam-broker/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ version: 0.1.0
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "v0.1.5"
appVersion: "v0.1.6"

dependencies:
- name: vault-actions
Expand Down
44 changes: 22 additions & 22 deletions charts/nats-iam-broker/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -68,28 +68,28 @@ config:

# configuration of the third-party identity provider that supplied the JWT token
idp:
# The client ID for the identity provider
client_id: public

# URLs of the identity provider issuer
issuer_url:
- https://oidctest.wsweet.org/

validation:
# claims required to be present on the incoming JWT validation
claims:
- aud
- iat
- exp
- sub
# Expected values for the audience claim of the JWT
# to skip this check set it to []
aud:
- public
# acceptable expiration bounds for incoming JWT
exp:
min: 1m0s
max: 2h
- description: oidc-public
# The client ID for the identity provider
client_id: public

# URLs of the identity provider issuer
issuer_url: https://oidctest.wsweet.org/

validation:
# claims required to be present on the incoming JWT validation
claims:
- aud
- iat
- exp
- sub
# Expected values for the audience claim of the JWT
# to skip this check set it to []
aud:
- public
# acceptable expiration bounds for incoming JWT
exp:
min: 1m0s
max: 2h

# role-based access control configuration section
rbac:
Expand Down
44 changes: 30 additions & 14 deletions configs/idp_private.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
idp:
client_id: private
issuer_url:
- https://oidctest.wsweet.org/
- description: oidctest-private
client_id: private
issuer_url: https://oidctest.wsweet.org/

validation:
claims:
- aud
- iat
- exp
- sub
aud:
- private
exp:
min: 1m0s
max: 2h
validation:
claims:
- aud
- iat
- exp
- sub
aud:
- private
exp:
min: 1m0s
max: 2h

- description: random-ununused-item
client_id: random
issuer_url: https://oidctest.wsweet.org/

validation:
claims:
- aud
- iat
- exp
- sub
aud:
- random-aud
exp:
min: 1m0s
max: 2h
60 changes: 46 additions & 14 deletions configs/idp_public.yaml
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
idp:
client_id: public
issuer_url:
- https://oidctest.wsweet.org/
- description: random-ununused-item
client_id: random-start
issuer_url: https://oidctest.wsweet.org/

validation:
claims:
- aud
- iat
- exp
- sub
aud:
- public
exp:
min: 1m0s
max: 2h
validation:
claims:
- aud
- iat
- exp
- sub
aud:
- random-aud
exp:
min: 1m0s
max: 2h

- description: oidctest-public
client_id: public
issuer_url: https://oidctest.wsweet.org/

validation:
claims:
- aud
- iat
- exp
- sub
aud:
- public
exp:
min: 1m0s
max: 2h

- description: random-ununused-item
client_id: random-end
issuer_url: https://oidctest.wsweet.org/

validation:
claims:
- aud
- iat
- exp
- sub
aud:
- random-aud
exp:
min: 1m0s
max: 2h
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN apk update && apk add --no-cache git bash curl jq make

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

WORKDIR /usr/src/app

Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN apk update && apk add --no-cache git bash curl jq make

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

WORKDIR /usr/src/app

Expand Down
20 changes: 11 additions & 9 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
module github.com/jr200/nats-iam-broker

go 1.22
go 1.22.0

toolchain go1.22.5

require (
github.com/coreos/go-oidc/v3 v3.11.0
github.com/go-playground/validator v9.31.0+incompatible
github.com/nats-io/jwt/v2 v2.5.8
github.com/nats-io/jwt/v2 v2.7.0
github.com/nats-io/nats.go v1.37.0
github.com/nats-io/nkeys v0.4.7
github.com/rs/zerolog v1.33.0
Expand All @@ -18,7 +20,7 @@ require (
github.com/go-jose/go-jose/v4 v4.0.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.0 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
Expand All @@ -28,12 +30,12 @@ require (
github.com/nats-io/nuid v1.0.1 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.24.0 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/oauth2 v0.23.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/text v0.18.0 // indirect
golang.org/x/tools v0.25.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
)
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ github.com/go-playground/validator v9.31.0+incompatible h1:UA72EPEogEnq76ehGdEDp
github.com/go-playground/validator v9.31.0+incompatible/go.mod h1:yrEkQXlcI+PugkyDjY2bRrL/UBU4f3rvrgkN3V8JEig=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
Expand All @@ -45,6 +47,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nats-io/jwt/v2 v2.5.8 h1:uvdSzwWiEGWGXf+0Q+70qv6AQdvcvxrv9hPM0RiPamE=
github.com/nats-io/jwt/v2 v2.5.8/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A=
github.com/nats-io/jwt/v2 v2.7.0 h1:J+ZnaaMGQi3xSB8iOhVM5ipiWCDrQvgEoitTwWFyOYw=
github.com/nats-io/jwt/v2 v2.7.0/go.mod h1:ZdWS1nZa6WMZfFwwgpEaqBV8EPGVgOTDHN/wTbz0Y5A=
github.com/nats-io/nats.go v1.36.0 h1:suEUPuWzTSse/XhESwqLxXGuj8vGRuPRoG7MoRN/qyU=
github.com/nats-io/nats.go v1.36.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8=
github.com/nats-io/nats.go v1.37.0 h1:07rauXbVnnJvv1gfIyghFEo6lUcYRY0WXc3x7x0vUxE=
Expand Down Expand Up @@ -83,6 +87,8 @@ golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
Expand All @@ -97,8 +103,12 @@ golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA=
golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
Expand All @@ -111,12 +121,16 @@ golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
Expand All @@ -128,6 +142,8 @@ golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
13 changes: 3 additions & 10 deletions internal/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Config struct {
AppParams ConfigParams `yaml:"params"`
NATS NATS `yaml:"nats" validate:"required"`
Service Service `yaml:"service" validate:"required"`
Idp Idp `yaml:"idp" validate:"required"`
Idp []Idp `yaml:"idp" validate:"required"`
NatsJwt NatsJwt `yaml:"nats_jwt" validate:"required"`
Rbac Rbac `yaml:"rbac" validate:"required"`
}
Expand Down Expand Up @@ -54,7 +54,8 @@ type Encryption struct {
}

type Idp struct {
IssuerURL []string `yaml:"issuer_url" validate:"required"`
Description string `yaml:"description"`
IssuerURL string `yaml:"issuer_url" validate:"required"`
ClientID string `yaml:"client_id" validate:"required"`
ValidationSpec IdpJwtValidationSpec `yaml:"validation"`
}
Expand Down Expand Up @@ -151,14 +152,6 @@ func readConfigFiles(files []string, mappings map[string]interface{}) (*Config,
LeftDelim: "{{",
RightDelim: "}}",
},
Idp: Idp{
ValidationSpec: IdpJwtValidationSpec{
Expiry: DurationBounds{
Min: Duration{time.Duration(0)},
Max: Duration{time.Duration(24 * time.Hour)},
},
},
},
}

if err := yaml.Unmarshal([]byte(mergedYAML), &cfg); err != nil {
Expand Down
44 changes: 41 additions & 3 deletions internal/server/idp_jwt_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,58 @@ import (
"github.com/rs/zerolog/log"
)

type IdpAndJwtVerifier struct {
verifier *IdpJwtVerifier
config *Idp
}

func NewIdpVerifiers(config *Config) ([]IdpAndJwtVerifier, error) {
idpVerifiers := make([]IdpAndJwtVerifier, 0, len(config.Idp))
for _, idp := range config.Idp {
idpVerifier, err := NewJwtVerifier(context.Background(), idp.ClientID, idp.IssuerURL)
if err != nil {
return nil, err
}
idpVerifiers = append(idpVerifiers, IdpAndJwtVerifier{idpVerifier, &idp})
}
return idpVerifiers, nil
}

func runVerification(jwtToken string, items []IdpAndJwtVerifier) (*IdpJwtClaims, error) {
for _, item := range items {
reqClaims, err := item.verifier.verifyJWT(jwtToken)
if err != nil {
log.Trace().Err(err).Msg("error verifying idp-jwt")
continue
}

err = item.verifier.validateAgainstSpec(reqClaims, item.config.ValidationSpec)
if err != nil {
log.Trace().Err(err).Msg("failed checks in idp validation")
continue
}

return reqClaims, nil
}

return nil, errors.New("no idp verifier found for jwtToken")
}

type IdpJwtVerifier struct {
*oidc.IDTokenVerifier
MaxTokenLifetime time.Duration
ClockSkew time.Duration
}

func NewJwtVerifier(ctx context.Context, clientID string, issuerUrl []string) (*IdpJwtVerifier, error) {
// TODO: support multiple issuers
provider, err := oidc.NewProvider(ctx, issuerUrl[0])
func NewJwtVerifier(ctx context.Context, clientID string, issuerUrl string) (*IdpJwtVerifier, error) {
provider, err := oidc.NewProvider(ctx, issuerUrl)
if err != nil {
log.Err(err)
return nil, err
}

log.Trace().Msgf("NewJwtVerifier (config-params) clientId=%s, issuerUrl=%s", clientID, issuerUrl)

return &IdpJwtVerifier{provider.Verifier(&oidc.Config{ClientID: clientID}), time.Hour * 24, time.Minute * 5}, nil
}

Expand Down
Loading

0 comments on commit a680bc3

Please sign in to comment.