Skip to content

Commit

Permalink
handle permissions jwt claims
Browse files Browse the repository at this point in the history
  • Loading branch information
roncodingenthusiast committed May 26, 2023
1 parent aa9bf08 commit c4bcb35
Show file tree
Hide file tree
Showing 17 changed files with 673 additions and 87 deletions.
79 changes: 57 additions & 22 deletions agent/xds/jwt_authn.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ import (
)

var (
jwt_envoy_filter = "envoy.filters.http.jwt_authn"
jwt_payload = "jwt_payload"
jwtEnvoyFilter = "envoy.filters.http.jwt_authn"
jwtPayload = "jwt_payload"
)

type JWTAuthnProvider struct {
ComputedName string
Provider *structs.IntentionJWTProvider
}

func makeJWTAuthFilter(pCE map[string]*structs.JWTProviderConfigEntry, intentions structs.SimplifiedIntentions) (*envoy_http_v3.HttpFilter, error) {
providers := map[string]*envoy_http_jwt_authn_v3.JwtProvider{}
var rules []*envoy_http_jwt_authn_v3.RequirementRule
Expand All @@ -29,21 +34,21 @@ func makeJWTAuthFilter(pCE map[string]*structs.JWTProviderConfigEntry, intention
if intention.JWT == nil && !hasJWTconfig(intention.Permissions) {
continue
}
for _, jwtReq := range collectJWTRequirements(intention) {
if _, ok := providers[jwtReq.Name]; ok {
for _, jwtReq := range collectJWTAuthnProviders(intention) {
if _, ok := providers[jwtReq.ComputedName]; ok {
continue
}

jwtProvider, ok := pCE[jwtReq.Name]
jwtProvider, ok := pCE[jwtReq.Provider.Name]

if !ok {
return nil, fmt.Errorf("provider specified in intention does not exist. Provider name: %s", jwtReq.Name)
return nil, fmt.Errorf("provider specified in intention does not exist. Provider name: %s", jwtReq.Provider.Name)
}
envoyCfg, err := buildJWTProviderConfig(jwtProvider)
envoyCfg, err := buildJWTProviderConfig(jwtProvider, jwtReq.ComputedName)
if err != nil {
return nil, err
}
providers[jwtReq.Name] = envoyCfg
providers[jwtReq.ComputedName] = envoyCfg
}

for _, perm := range intention.Permissions {
Expand All @@ -59,7 +64,6 @@ func makeJWTAuthFilter(pCE map[string]*structs.JWTProviderConfigEntry, intention
if intention.JWT != nil {
for _, provider := range intention.JWT.Providers {
// The top-level provider applies to all requests.
// TODO(roncodingenthusiast): Handle provider.VerifyClaims
rule := buildRouteRule(provider, nil, "/")
rules = append(rules, rule)
}
Expand All @@ -75,38 +79,69 @@ func makeJWTAuthFilter(pCE map[string]*structs.JWTProviderConfigEntry, intention
Providers: providers,
Rules: rules,
}
return makeEnvoyHTTPFilter(jwt_envoy_filter, cfg)
return makeEnvoyHTTPFilter(jwtEnvoyFilter, cfg)
}

func collectJWTRequirements(i *structs.Intention) []*structs.IntentionJWTProvider {
var jReqs []*structs.IntentionJWTProvider
func collectJWTAuthnProviders(i *structs.Intention) []*JWTAuthnProvider {
var reqs []*JWTAuthnProvider

if i.JWT != nil {
jReqs = append(jReqs, i.JWT.Providers...)
for _, prov := range i.JWT.Providers {
reqs = append(reqs, &JWTAuthnProvider{Provider: prov, ComputedName: prov.Name})
}
}

jReqs = append(jReqs, getPermissionsProviders(i.Permissions)...)
reqs = append(reqs, getPermissionsProviders(i.Permissions)...)

return jReqs
return reqs
}

func getPermissionsProviders(p []*structs.IntentionPermission) []*structs.IntentionJWTProvider {
intentionProviders := []*structs.IntentionJWTProvider{}
func getPermissionsProviders(p []*structs.IntentionPermission) []*JWTAuthnProvider {
var reqs []*JWTAuthnProvider
for _, perm := range p {
if perm.JWT == nil {
continue
}
intentionProviders = append(intentionProviders, perm.JWT.Providers...)
for _, prov := range perm.JWT.Providers {
reqs = append(reqs, &JWTAuthnProvider{Provider: prov, ComputedName: makeComputedProviderName(prov.Name, perm)})
}
}

return reqs
}

func makeComputedProviderName(name string, perm *structs.IntentionPermission) string {
if perm == nil || perm.HTTP == nil {
return name
}
var suffix string
if perm.HTTP.PathPrefix != "" {
suffix = perm.HTTP.PathPrefix
}
if perm.HTTP.PathRegex != "" {
suffix = perm.HTTP.PathRegex
}
if perm.HTTP.PathExact != "" {
suffix = perm.HTTP.PathExact
}
if suffix == "" {
return name
}
return fmt.Sprintf("%s_%s", name, suffix)
}

return intentionProviders
func buildPayloadInMetadataKey(providerName string, perm *structs.IntentionPermission) string {
if perm == nil {
return fmt.Sprintf("%s_%s", jwtPayload, providerName)
}
return fmt.Sprintf("%s_%s", jwtPayload, makeComputedProviderName(providerName, perm))
}

func buildJWTProviderConfig(p *structs.JWTProviderConfigEntry) (*envoy_http_jwt_authn_v3.JwtProvider, error) {
func buildJWTProviderConfig(p *structs.JWTProviderConfigEntry, payloadSuffix string) (*envoy_http_jwt_authn_v3.JwtProvider, error) {
envoyCfg := envoy_http_jwt_authn_v3.JwtProvider{
Issuer: p.Issuer,
Audiences: p.Audiences,
PayloadInMetadata: jwt_payload,
PayloadInMetadata: buildPayloadInMetadataKey(payloadSuffix, nil),
}

if p.Forwarding != nil {
Expand Down Expand Up @@ -230,7 +265,7 @@ func buildRouteRule(provider *structs.IntentionJWTProvider, perm *structs.Intent
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
ProviderName: provider.Name,
ProviderName: makeComputedProviderName(provider.Name, perm),
},
},
},
Expand Down
78 changes: 45 additions & 33 deletions agent/xds/jwt_authn_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,21 +65,21 @@ var (
pWithOktaProvider = &structs.IntentionPermission{
Action: structs.IntentionActionAllow,
HTTP: &structs.IntentionHTTPPermission{
PathPrefix: "/some-special-path",
PathPrefix: "some-special-path",
},
JWT: oktaIntention,
}
pWithMultiProviders = &structs.IntentionPermission{
Action: structs.IntentionActionAllow,
HTTP: &structs.IntentionHTTPPermission{
PathPrefix: "/some-special-path",
PathPrefix: "some-special-path",
},
JWT: multiProviderIntentions,
}
pWithNoJWT = &structs.IntentionPermission{
Action: structs.IntentionActionAllow,
HTTP: &structs.IntentionHTTPPermission{
PathPrefix: "/some-special-path",
PathPrefix: "some-special-path",
},
}
fullRetryPolicy = &structs.JWKSRetryPolicy{
Expand Down Expand Up @@ -206,51 +206,58 @@ func TestMakeJWTAUTHFilters(t *testing.T) {
}
}

func TestCollectJWTRequirements(t *testing.T) {
var (
emptyReq = []*structs.IntentionJWTProvider{}
oneReq = []*structs.IntentionJWTProvider{&oktaProvider}
multiReq = append(oneReq, &auth0Provider)
)

func TestCollectJWTAuthnProviders(t *testing.T) {
tests := map[string]struct {
intention *structs.Intention
expected []*structs.IntentionJWTProvider
expected []*JWTAuthnProvider
}{
"empty-top-level-jwt-and-empty-permissions": {
intention: makeTestIntention(t, ixnOpts{src: "web"}),
expected: emptyReq,
expected: []*JWTAuthnProvider{},
},
"top-level-jwt-and-empty-permissions": {
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: oktaIntention}),
expected: oneReq,
expected: []*JWTAuthnProvider{{Provider: &oktaProvider, ComputedName: oktaProvider.Name}},
},
"multi-top-level-jwt-and-empty-permissions": {
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: multiProviderIntentions}),
expected: multiReq,
expected: []*JWTAuthnProvider{
{Provider: &oktaProvider, ComputedName: oktaProvider.Name},
{Provider: &auth0Provider, ComputedName: auth0Provider.Name},
},
},
"top-level-jwt-and-one-jwt-permission": {
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: auth0Intention, perms: pWithOktaProvider}),
expected: multiReq,
expected: []*JWTAuthnProvider{
{Provider: &auth0Provider, ComputedName: auth0Provider.Name},
{Provider: &oktaProvider, ComputedName: "okta_some-special-path"},
},
},
"top-level-jwt-and-multi-jwt-permissions": {
intention: makeTestIntention(t, ixnOpts{src: "web", jwt: fakeIntention, perms: pWithMultiProviders}),
expected: append(multiReq, &fakeProvider),
expected: []*JWTAuthnProvider{
{Provider: &fakeProvider, ComputedName: fakeProvider.Name},
{Provider: &oktaProvider, ComputedName: "okta_some-special-path"},
{Provider: &auth0Provider, ComputedName: "auth0_some-special-path"},
},
},
"empty-top-level-jwt-and-one-jwt-permission": {
intention: makeTestIntention(t, ixnOpts{src: "web", perms: pWithOktaProvider}),
expected: oneReq,
expected: []*JWTAuthnProvider{{Provider: &oktaProvider, ComputedName: "okta_some-special-path"}},
},
"empty-top-level-jwt-and-multi-jwt-permission": {
intention: makeTestIntention(t, ixnOpts{src: "web", perms: pWithMultiProviders}),
expected: multiReq,
expected: []*JWTAuthnProvider{
{Provider: &oktaProvider, ComputedName: "okta_some-special-path"},
{Provider: &auth0Provider, ComputedName: "auth0_some-special-path"},
},
},
}

for name, tt := range tests {
tt := tt
t.Run(name, func(t *testing.T) {
reqs := collectJWTRequirements(tt.intention)
reqs := collectJWTAuthnProviders(tt.intention)
require.ElementsMatch(t, reqs, tt.expected)
})
}
Expand All @@ -259,27 +266,32 @@ func TestCollectJWTRequirements(t *testing.T) {
func TestGetPermissionsProviders(t *testing.T) {
tests := map[string]struct {
perms []*structs.IntentionPermission
expected []*structs.IntentionJWTProvider
expected []*JWTAuthnProvider
}{
"empty-permissions": {
perms: []*structs.IntentionPermission{},
expected: []*structs.IntentionJWTProvider{},
expected: []*JWTAuthnProvider{},
},
"nil-permissions": {
perms: nil,
expected: []*structs.IntentionJWTProvider{},
expected: []*JWTAuthnProvider{},
},
"permissions-with-no-jwt": {
perms: []*structs.IntentionPermission{pWithNoJWT},
expected: []*structs.IntentionJWTProvider{},
expected: []*JWTAuthnProvider{},
},
"permissions-with-one-jwt": {
perms: []*structs.IntentionPermission{pWithOktaProvider, pWithNoJWT},
expected: []*structs.IntentionJWTProvider{&oktaProvider},
perms: []*structs.IntentionPermission{pWithOktaProvider, pWithNoJWT},
expected: []*JWTAuthnProvider{
{Provider: &oktaProvider, ComputedName: "okta_some-special-path"},
},
},
"permissions-with-multiple-jwt": {
perms: []*structs.IntentionPermission{pWithMultiProviders, pWithNoJWT},
expected: []*structs.IntentionJWTProvider{&oktaProvider, &auth0Provider},
perms: []*structs.IntentionPermission{pWithMultiProviders, pWithNoJWT},
expected: []*JWTAuthnProvider{
{Provider: &auth0Provider, ComputedName: "auth0_some-special-path"},
{Provider: &oktaProvider, ComputedName: "okta_some-special-path"},
},
},
}

Expand Down Expand Up @@ -338,7 +350,7 @@ func TestBuildJWTProviderConfig(t *testing.T) {
Issuer: fullCE.Issuer,
Audiences: fullCE.Audiences,
ForwardPayloadHeader: "user-token",
PayloadInMetadata: jwt_payload,
PayloadInMetadata: buildPayloadInMetadataKey(ceRemoteJWKS.Name, nil),
PadForwardPayloadHeader: false,
Forward: true,
JwksSourceSpecifier: &envoy_http_jwt_authn_v3.JwtProvider_LocalJwks{
Expand All @@ -356,7 +368,7 @@ func TestBuildJWTProviderConfig(t *testing.T) {
expected: &envoy_http_jwt_authn_v3.JwtProvider{
Issuer: fullCE.Issuer,
Audiences: fullCE.Audiences,
PayloadInMetadata: jwt_payload,
PayloadInMetadata: buildPayloadInMetadataKey(ceRemoteJWKS.Name, nil),
JwksSourceSpecifier: &envoy_http_jwt_authn_v3.JwtProvider_RemoteJwks{
RemoteJwks: &envoy_http_jwt_authn_v3.RemoteJwks{
HttpUri: &envoy_core_v3.HttpUri{
Expand All @@ -376,7 +388,7 @@ func TestBuildJWTProviderConfig(t *testing.T) {
for name, tt := range tests {
tt := tt
t.Run(name, func(t *testing.T) {
res, err := buildJWTProviderConfig(tt.ce)
res, err := buildJWTProviderConfig(tt.ce, tt.ce.GetName())

if tt.expectedError != "" {
require.Error(t, err)
Expand Down Expand Up @@ -587,7 +599,7 @@ func TestBuildRouteRule(t *testing.T) {
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
ProviderName: oktaProvider.Name,
ProviderName: makeComputedProviderName(oktaProvider.Name, pWithMultiProviders),
},
},
},
Expand All @@ -604,7 +616,7 @@ func TestBuildRouteRule(t *testing.T) {
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
ProviderName: oktaProvider.Name,
ProviderName: makeComputedProviderName(oktaProvider.Name, pWithExactPath),
},
},
},
Expand All @@ -621,7 +633,7 @@ func TestBuildRouteRule(t *testing.T) {
RequirementType: &envoy_http_jwt_authn_v3.RequirementRule_Requires{
Requires: &envoy_http_jwt_authn_v3.JwtRequirement{
RequiresType: &envoy_http_jwt_authn_v3.JwtRequirement_ProviderName{
ProviderName: oktaProvider.Name,
ProviderName: makeComputedProviderName(oktaProvider.Name, pWithRegex),
},
},
},
Expand Down
Loading

0 comments on commit c4bcb35

Please sign in to comment.