Skip to content

Commit

Permalink
Stop referenced jwt providers from being deleted
Browse files Browse the repository at this point in the history
  • Loading branch information
roncodingenthusiast committed Jun 15, 2023
1 parent fdde92c commit d77048f
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/17755.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
mesh: Stop jwt providers referenced by intentions from being deleted.
```
63 changes: 63 additions & 0 deletions agent/consul/state/config_entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,12 @@ func validateProposedConfigEntryInGraph(
case structs.TCPRoute:
case structs.RateLimitIPConfig:
case structs.JWTProvider:
if newEntry == nil && existingEntry != nil {
err := validateJWTProviderIsReferenced(tx, kindName, existingEntry)
if err != nil {
return err
}
}
default:
return fmt.Errorf("unhandled kind %q during validation of %q", kindName.Kind, kindName.Name)
}
Expand Down Expand Up @@ -704,6 +710,63 @@ func getReferencedProviderNames(j *structs.IntentionJWTRequirement, s []*structs
return providerNames
}

func validateJWTProviderIsReferenced(tx ReadTxn, kn configentry.KindName, ce structs.ConfigEntry) error {
meta := acl.NewEnterpriseMetaWithPartition(
kn.EnterpriseMeta.PartitionOrDefault(),
acl.DefaultNamespaceName,
)
entry, ok := ce.(*structs.JWTProviderConfigEntry)
if !ok {
return fmt.Errorf("invalid jwt provider config entry: %T", entry)
}

_, ixnEntries, err := configEntriesByKindTxn(tx, nil, structs.ServiceIntentions, &meta)
if err != nil {
return err
}

providerNames, err := collectJWTProviderNames(ixnEntries)
if err != nil {
return err
}

_, exist := providerNames[entry.Name]
if exist {
return fmt.Errorf("cannot delete jwt provider config entry referenced by an intention. Provider name:%s", entry.Name)
}

return nil
}

func collectJWTProviderNames(e []structs.ConfigEntry) (map[string]struct{}, error) {
names := make(map[string]struct{})

for _, entry := range e {
ixn, ok := entry.(*structs.ServiceIntentionsConfigEntry)
if !ok {
return names, fmt.Errorf("type %T is not a service intentions config entry", entry)
}

if ixn.JWT != nil {
for _, prov := range ixn.JWT.Providers {
names[prov.Name] = struct{}{}
}
}

for _, s := range ixn.Sources {
for _, perm := range s.Permissions {
if perm.JWT == nil {
continue
}
for _, prov := range perm.JWT.Providers {
names[prov.Name] = struct{}{}
}
}
}
}
return names, nil
}

// This fetches all the jwt-providers config entries and iterates over them
// to validate that any provider referenced exists.
// This is okay because we assume there are very few jwt-providers per partition
Expand Down
129 changes: 129 additions & 0 deletions agent/consul/state/config_entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3714,3 +3714,132 @@ func TestStateStore_DiscoveryChain_AttachVirtualIPs(t *testing.T) {
require.Equal(t, []string{"2.2.2.2", "3.3.3.3"}, chain.ManualVirtualIPs)

}

func TestCollectJWTProviderNames(t *testing.T) {
oktaProvider := structs.IntentionJWTProvider{Name: "okta"}
auth0Provider := structs.IntentionJWTProvider{Name: "auth0"}
cases := map[string]struct {
entries []structs.ConfigEntry
expected map[string]struct{}
}{
"no jwt at any level": {
entries: []structs.ConfigEntry{},
expected: map[string]struct{}{},
},
"only top level jwt with no permissions": {
entries: []structs.ConfigEntry{
&structs.ServiceIntentionsConfigEntry{
Kind: "service-intentions",
Name: "api-intention",
JWT: &structs.IntentionJWTRequirement{
Providers: []*structs.IntentionJWTProvider{&oktaProvider, &auth0Provider},
},
},
},
expected: map[string]struct{}{
"okta": {}, "auth0": {},
},
},
"top level jwt with permissions": {
entries: []structs.ConfigEntry{
&structs.ServiceIntentionsConfigEntry{
Kind: "service-intentions",
Name: "api-intention",
JWT: &structs.IntentionJWTRequirement{
Providers: []*structs.IntentionJWTProvider{&oktaProvider},
},
Sources: []*structs.SourceIntention{
{
Name: "api",
Action: "allow",
Permissions: []*structs.IntentionPermission{
{
Action: "allow",
JWT: &structs.IntentionJWTRequirement{
Providers: []*structs.IntentionJWTProvider{&oktaProvider},
},
},
},
},
{
Name: "serv",
Action: "allow",
Permissions: []*structs.IntentionPermission{
{
Action: "allow",
JWT: &structs.IntentionJWTRequirement{
Providers: []*structs.IntentionJWTProvider{&auth0Provider},
},
},
},
},
{
Name: "web",
Action: "allow",
Permissions: []*structs.IntentionPermission{
{Action: "allow"},
},
},
},
},
},
expected: map[string]struct{}{
"okta": {}, "auth0": {},
},
},
"no top level jwt and existing permissions": {
entries: []structs.ConfigEntry{
&structs.ServiceIntentionsConfigEntry{
Kind: "service-intentions",
Name: "api-intention",
Sources: []*structs.SourceIntention{
{
Name: "api",
Action: "allow",
Permissions: []*structs.IntentionPermission{
{
Action: "allow",
JWT: &structs.IntentionJWTRequirement{
Providers: []*structs.IntentionJWTProvider{&oktaProvider},
},
},
},
},
{
Name: "serv",
Action: "allow",
Permissions: []*structs.IntentionPermission{
{
Action: "allow",
JWT: &structs.IntentionJWTRequirement{
Providers: []*structs.IntentionJWTProvider{&auth0Provider},
},
},
},
},
{
Name: "web",
Action: "allow",
Permissions: []*structs.IntentionPermission{
{Action: "allow"},
},
},
},
},
},
expected: map[string]struct{}{
"okta": {}, "auth0": {},
},
},
}

for name, tt := range cases {
tt := tt
t.Run(name, func(t *testing.T) {
names, err := collectJWTProviderNames(tt.entries)

require.NoError(t, err)
require.Equal(t, tt.expected, names)
})
}
}

0 comments on commit d77048f

Please sign in to comment.