mirror of https://github.com/hashicorp/consul
Stop referenced jwt providers from being deleted
parent
fdde92c8c2
commit
d77048f1ea
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
mesh: Stop jwt providers referenced by intentions from being deleted.
|
||||
```
|
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue