Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(kuma-cp): using policy name with "." causes hash to be inserted in the wrong place on the zone #8240

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
414 changes: 414 additions & 0 deletions docs/guides/kds-sync-name.md

Large diffs are not rendered by default.

26 changes: 21 additions & 5 deletions pkg/core/resources/model/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import (
"k8s.io/kube-openapi/pkg/validation/spec"

common_api "github.com/kumahq/kuma/api/common/v1alpha1"
"github.com/kumahq/kuma/api/mesh/v1alpha1"
"github.com/kumahq/kuma/pkg/kds/hash"
util_k8s "github.com/kumahq/kuma/pkg/util/k8s"
)

const (
Expand Down Expand Up @@ -289,6 +287,18 @@ func (a ByMeta) Less(i, j int) bool {

func (a ByMeta) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

const (
// K8sNamespaceComponent identifies the namespace component of a resource name on Kubernetes.
// The value is considered a part of user-facing Kuma API and should not be changed lightly.
// The value has a format of a Kubernetes label name.
K8sNamespaceComponent = "k8s.kuma.io/namespace"

// K8sNameComponent identifies the name component of a resource name on Kubernetes.
// The value is considered a part of user-facing Kuma API and should not be changed lightly.
// The value has a format of a Kubernetes label name.
K8sNameComponent = "k8s.kuma.io/name"
)

type ResourceType string

// ResourceNameExtensions represents an composite resource name in environments
Expand Down Expand Up @@ -324,15 +334,21 @@ type ResourceMeta interface {
// IsReferenced check if `refMeta` references with `refName` the entity `resourceMeta`
// This is required because in multi-zone policies may have names different from the name they are defined with.
func IsReferenced(refMeta ResourceMeta, refName string, resourceMeta ResourceMeta) bool {
if refMeta.GetMesh() != resourceMeta.GetMesh() {
return false
}

if len(refMeta.GetNameExtensions()) == 0 {
return equalNames(refMeta.GetMesh(), refName, resourceMeta.GetName())
}

if ns := refMeta.GetNameExtensions()[v1alpha1.KubeNamespaceTag]; ns != "" {
return equalNames(refMeta.GetMesh(), util_k8s.K8sNamespacedNameToCoreName(refName, ns), resourceMeta.GetName())
nsRef := refMeta.GetNameExtensions()[K8sNamespaceComponent]
nsRes := refMeta.GetNameExtensions()[K8sNamespaceComponent]
if nsRef == "" || nsRef != nsRes {
return false
}

return false
return equalNames(refMeta.GetMesh(), refName, resourceMeta.GetNameExtensions()[K8sNameComponent])
lobkovilya marked this conversation as resolved.
Show resolved Hide resolved
}

func equalNames(mesh, n1, n2 string) bool {
Expand Down
64 changes: 64 additions & 0 deletions pkg/core/resources/model/resource_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package model_test

import (
"fmt"
"reflect"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/kumahq/kuma/pkg/core/resources/model"
"github.com/kumahq/kuma/pkg/kds/hash"
"github.com/kumahq/kuma/pkg/plugins/common/k8s"
policies_api "github.com/kumahq/kuma/pkg/plugins/policies/meshaccesslog/api/v1alpha1"
test_model "github.com/kumahq/kuma/pkg/test/resources/model"
)

var _ = Describe("Resource", func() {
Expand All @@ -22,3 +27,62 @@ var _ = Describe("Resource", func() {
Expect(reflect.ValueOf(obj.GetSpec()).IsNil()).To(BeFalse())
})
})

var _ = Describe("IsReferenced", func() {
Context("Universal", func() {
meta := func(mesh, name string) *test_model.ResourceMeta {
return &test_model.ResourceMeta{Mesh: mesh, Name: name}
}
It("should return true when t1 is referencing route-1", func() {
Expect(model.IsReferenced(meta("m1", "t1"), "route-1", meta("m1", "route-1"))).To(BeTrue())
})
It("should return false when t1 is referencing route-2", func() {
Expect(model.IsReferenced(meta("m1", "t1"), "route-2", meta("m1", "route-1"))).To(BeFalse())
})
It("should return false when meshes are different", func() {
Expect(model.IsReferenced(meta("m1", "t1"), "route-1", meta("m2", "route-1"))).To(BeFalse())
})
})
Context("Kubernetes", func() {
meta := func(mesh, name string) *test_model.ResourceMeta {
return &test_model.ResourceMeta{
Mesh: mesh,
Name: fmt.Sprintf("%s.foo", name),
NameExtensions: k8s.ResourceNameExtensions("foo", name),
}
}
It("should return true when t1 is referencing route-1", func() {
Expect(model.IsReferenced(meta("m1", "t1"), "route-1", meta("m1", "route-1"))).To(BeTrue())
})
It("should return false when t1 is referencing route-2", func() {
Expect(model.IsReferenced(meta("m1", "t1"), "route-2", meta("m1", "route-1"))).To(BeFalse())
})
It("should return false when meshes are different", func() {
Expect(model.IsReferenced(meta("m1", "t1"), "route-1", meta("m2", "route-1"))).To(BeFalse())
})
})
Context("Kubernetes Zone", func() {
meta := func(mesh, name string) *test_model.ResourceMeta {
return &test_model.ResourceMeta{
Mesh: mesh,
Name: fmt.Sprintf("%s.foo", hash.SyncedNameInZone(mesh, name)),
NameExtensions: k8s.ResourceNameExtensions("foo", hash.SyncedNameInZone(mesh, name)),
}
}
It("should return true when t1 is referencing route-1", func() {
Expect(model.IsReferenced(meta("m1", "t1"), "route-1", meta("m1", "route-1"))).To(BeTrue())
})
It("should return true when route name has max allowed length", func() {
longRouteName := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" +
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"
Expect(model.IsReferenced(meta("m1", "t1"), longRouteName, meta("m1", longRouteName))).To(BeTrue())
})
It("should return false when t1 is referencing route-2", func() {
Expect(model.IsReferenced(meta("m1", "t1"), "route-2", meta("m1", "route-1"))).To(BeFalse())
})
It("should return false when meshes are different", func() {
Expect(model.IsReferenced(meta("m1", "t1"), "route-1", meta("m2", "route-1"))).To(BeFalse())
})
})
})
4 changes: 4 additions & 0 deletions pkg/kds/context/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ func AddHashSuffix(r model.Resource) (model.Resource, error) {
}

newObj := r.Descriptor().NewObject()

// When syncing mesh-scoped resources from Global to Zone, the only possible namespace on Global is system namespace.
// We always trim system namespace in RemoveK8sSystemNamespaceSuffixFromPluginOriginatedResourcesMapper,
// that's why r.GetMeta().GetName() never has a namespace suffix, so we can safely call SyncedNameInZone with it.
newObj.SetMeta(util.NewResourceMeta(hash.SyncedNameInZone(r.GetMeta().GetMesh(), r.GetMeta().GetName()), r.GetMeta().GetMesh()))
_ = newObj.SetSpec(r.GetSpec())

Expand Down
28 changes: 6 additions & 22 deletions pkg/kds/context/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ var _ = Describe("Context", func() {
name string
expectedName string
isResourcePluginOriginated bool
scope model.ResourceScope
}

genConfig := func(caseCfg config) kuma_cp.Config {
Expand All @@ -470,6 +471,7 @@ var _ = Describe("Context", func() {
descriptor := model.ResourceTypeDescriptor{
IsPluginOriginated: given.isResourcePluginOriginated,
Resource: core_mesh.NewCircuitBreakerResource(),
Scope: given.scope,
}

meta := &test_model.ResourceMeta{Name: given.name}
Expand Down Expand Up @@ -501,6 +503,7 @@ var _ = Describe("Context", func() {
},
name: "foo.custom-namespace",
expectedName: "foo-zxw6c95d42zfz9cc",
scope: model.ScopeMesh,
}),
Entry("shouldn't be removed when store type is kubernetes "+
"and resource isn't plugin originated", testCase{
Expand All @@ -509,28 +512,9 @@ var _ = Describe("Context", func() {
storeType: config_store.KubernetesStore,
k8sSystemNamespace: "custom-namespace",
},
name: "foo.custom-namespace",
expectedName: "foo-zxw6c95d42zfz9cc.custom-namespace",
}),
Entry("shouldn't be removed when store type is not kubernetes",
testCase{
isResourcePluginOriginated: true,
config: config{
storeType: config_store.PostgresStore,
k8sSystemNamespace: "custom-namespace",
},
name: "foo.custom-namespace",
expectedName: "foo-zxw6c95d42zfz9cc.custom-namespace",
}),
Entry("shouldn't be removed when suffix is not k8s system "+
"namespace", testCase{
isResourcePluginOriginated: true,
config: config{
storeType: config_store.KubernetesStore,
k8sSystemNamespace: "kuma-system",
},
name: "foo.custom-namespace",
expectedName: "foo-zxw6c95d42zfz9cc.custom-namespace",
name: "foo.default",
expectedName: "foo.default",
scope: model.ScopeGlobal,
}),
)
})
Expand Down
8 changes: 1 addition & 7 deletions pkg/kds/hash/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,13 @@ import (

"k8s.io/apimachinery/pkg/util/rand"
k8s_strings "k8s.io/utils/strings"

util_k8s "github.com/kumahq/kuma/pkg/util/k8s"
)

// SyncedNameInZone returns the resource's name after syncing from Global to Zone.
// It creates a new name by adding a hash suffix constructed from the 'mesh' and
// the original 'name'.
func SyncedNameInZone(mesh, name string) string {
if n, ns, err := util_k8s.CoreNameToK8sName(name); err == nil {
return util_k8s.K8sNamespacedNameToCoreName(addSuffix(n, hash(mesh, n)), ns)
} else {
return addSuffix(name, hash(mesh, name))
}
return addSuffix(name, hash(mesh, name))
}

func addSuffix(name, hash string) string {
Expand Down
14 changes: 2 additions & 12 deletions pkg/plugins/common/k8s/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,6 @@ import (
)

const (
// k8sNamespaceComponent identifies the namespace component of a resource name on Kubernetes.
// The value is considered a part of user-facing Kuma API and should not be changed lightly.
// The value has a format of a Kubernetes label name.
k8sNamespaceComponent = "k8s.kuma.io/namespace"

// k8sNameComponent identifies the name component of a resource name on Kubernetes.
// The value is considered a part of user-facing Kuma API and should not be changed lightly.
// The value has a format of a Kubernetes label name.
k8sNameComponent = "k8s.kuma.io/name"

// K8sMeshDefaultsGenerated identifies that default resources for mesh were successfully generated
K8sMeshDefaultsGenerated = "k8s.kuma.io/mesh-defaults-generated"

Expand All @@ -27,7 +17,7 @@ const (

func ResourceNameExtensions(namespace, name string) core_model.ResourceNameExtensions {
return core_model.ResourceNameExtensions{
k8sNamespaceComponent: namespace,
k8sNameComponent: name,
core_model.K8sNamespaceComponent: namespace,
core_model.K8sNameComponent: name,
}
}
17 changes: 4 additions & 13 deletions pkg/plugins/resources/k8s/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/kumahq/kuma/pkg/core/resources/model"
k8s_model "github.com/kumahq/kuma/pkg/plugins/resources/k8s/native/pkg/model"
util_k8s "github.com/kumahq/kuma/pkg/util/k8s"
)

type ResourceMapperFunc func(resource model.Resource, namespace string) (k8s_model.KubernetesObject, error)
Expand Down Expand Up @@ -33,21 +32,13 @@ func NewInferenceMapper(systemNamespace string, kubeFactory KubeFactory) Resourc
return nil, err
}
if rs.Scope() == k8s_model.ScopeNamespace {
name, ns, err := util_k8s.CoreNameToK8sName(resource.GetMeta().GetName())
if err != nil {
// if the original resource doesn't look like a kubernetes name ("name"."namespace")`, just use the default namespace.
// this is in the case where someone calls this on a universal cluster. Exporting is a use-case for this.
ns = systemNamespace
name = resource.GetMeta().GetName()
}
if namespace != "" { // If the user is forcing the namespace accept it.
ns = namespace
rs.SetNamespace(namespace)
} else {
rs.SetNamespace(systemNamespace)
}
rs.SetName(name)
rs.SetNamespace(ns)
} else {
rs.SetName(resource.GetMeta().GetName())
}
rs.SetName(resource.GetMeta().GetName())
rs.SetMesh(resource.GetMeta().GetMesh())
rs.SetCreationTimestamp(v1.NewTime(resource.GetMeta().GetCreationTime()))
rs.SetSpec(resource.GetSpec())
Expand Down
13 changes: 0 additions & 13 deletions pkg/plugins/resources/k8s/mapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,6 @@ var _ = Describe("KubernetesStore template", func() {
Expect(res.GetNamespace()).To(Equal("core-ns"))
})

It("works with namespace in name on inference", func() {
lobkovilya marked this conversation as resolved.
Show resolved Hide resolved
in := core_mtp.NewMeshTrafficPermissionResource()
in.SetMeta(&rest_v1alpha1.ResourceMeta{Name: "foo.namespace", Mesh: "default"})

mapper := k8s.NewInferenceMapper("core-ns", &k8s.SimpleKubeFactory{KubeTypes: kubeTypes})
res, err := mapper(in, "")

Expect(err).ToNot(HaveOccurred())
Expect(res.GetMesh()).To(Equal("default"))
Expect(res.GetName()).To(Equal("foo"))
Expect(res.GetNamespace()).To(Equal("namespace"))
})

It("works passing namespace on inference", func() {
in := core_mtp.NewMeshTrafficPermissionResource()
in.SetMeta(&rest_v1alpha1.ResourceMeta{Name: "foo", Mesh: "default"})
Expand Down
Loading