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

feat(plugins) allow cross-namespace plugin references #5965

Merged
merged 12 commits into from
May 16, 2024
3 changes: 3 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ issues:
- (Expect directory permissions to be 0750 or less|Expect file permissions to be 0600 or less)
# EXC0010 gosec: False positive is triggered by 'src, err := ioutil.ReadFile(filename)'
- Potential file inclusion via variable
# G601 Implicit memory aliasing in for loop
# Irrelevant as of Go 1.22
- (G601)

exclude-rules:
# Ignore insecure TLS in tests and hardcoded credentials
Expand Down
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,16 @@ Adding a new version? You'll need three changes:
- add a flag `--enable-controller-gwapi-grpcroute` to control whether enable or disable GRPCRoute controller.
- add support for `GRPCRoute` v1, which requires users to upgrade the Gateway API's CRD to v1.1.
[#5918](https://github.com/Kong/kubernetes-ingress-controller/pull/5918)

- The `konghq.com/plugins` annotation supports a new `<namespace>:<name>`
format. This format requests a KongPlugin from a remote namespace. Binding
plugins across namespaces requires a ReferenceGrant from the requesting
resource to KongPlugins in the target namespace. This approach is useful for
some plugins bound to different types of entities, such as a set of
rate-limiting plugins applied to a service and various consumers. The
cross-namespace grant allows the service manager to define different limits
for consumers managed by other users without requiring those users to create
consumers in the Service's namespace.
[#5965](https://github.com/Kong/kubernetes-ingress-controller/pull/5965)

### Fixed

Expand Down
12 changes: 2 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ mise:
@mise -V >/dev/null || (echo "mise - https://github.com/jdx/mise - not found. Please install it." && exit 1)

.PHONY: tools
tools: controller-gen kustomize client-gen golangci-lint.download gotestsum crd-ref-docs skaffold looppointer.download staticcheck.download
tools: controller-gen kustomize client-gen golangci-lint.download gotestsum crd-ref-docs skaffold staticcheck.download

export MISE_DATA_DIR = $(PROJECT_DIR)/bin/

Expand Down Expand Up @@ -161,11 +161,6 @@ go-junit-report: ## Download go-junit-report locally if necessary.
@$(MAKE) mise-plugin-install DEP=go-junit-report URL=https://github.com/pmalek/asdf-go-junit-report.git
@$(MISE) install go-junit-report@$(GOJUNIT_REPORT_VERSION)

LOOPPOINTER= $(PROJECT_DIR)/bin/looppointer
.PHONY: looppointer.download
looppointer.download: ## Download looppointer locally if necessary.
@$(MAKE) _download_tool TOOL=looppointer

# ------------------------------------------------------------------------------
# Build
# ------------------------------------------------------------------------------
Expand Down Expand Up @@ -216,7 +211,7 @@ fmt:
go fmt ./...

.PHONY: lint
lint: verify.tidy golangci-lint staticcheck looppointer
lint: verify.tidy golangci-lint staticcheck

.PHONY: golangci-lint
golangci-lint: golangci-lint.download
Expand All @@ -229,9 +224,6 @@ staticcheck: staticcheck.download
grep -F -e internal/konnect/controlplanes -v | \
xargs $(STATICCHECK) -tags envtest,e2e_tests,integration_tests,istio_tests,conformance_tests -f stylish

looppointer: looppointer.download
$(LOOPPOINTER) -v ./internal/... ./test/...

.PHONY: verify.tidy
verify.tidy:
./scripts/verify-tidy.sh
Expand Down
166 changes: 166 additions & 0 deletions examples/cross-namespace-plugin-grant.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Service A, Consumer 1 in Namespace Foo, Service B in Namespace Bar (KongPlugin)
# With the original behavior, KIC provided no means for the Service in Bar to use the KongPlugin in Foo.
# With the modified behavior, the Service in Bar can use namespace:plugin_name to request another
# namespace's plugin. Functionally, the Service in Bar behaves the same as a Service in Foo for the
# purpose of plugin binding, i.e. the Service in Bar can create a multi-entity plugin with the KongConsumer
# in Foo same as the Service in Foo can, if it has permission. Permissions are ReferenceGrants to
# the KongPlugin:
# - In namespace Foo, grant from Bar:Services to KongPlugins.
# These grants may optionally use the standard ReferenceGrant specific target syntax to limit which
# plugins external resources can bind to. Because this KongPlugin inherently has access to only
# resources in its namespace, permissions to it are a clearer indication of access to those resources
# than the ambiguous KongClusterPlugin situation, and we do not need bidirectional permissions.

# Service A in Foo, Service B in Bar, Consumer in Baz
# This doesn't differ much for KongPlugins either, in that you simply need an additional grant from
# both other resource namespaces. You do need to choose whether the plugin resides in the KongConsumer
# namespace or the Service namespaces. Functionally this doesn't change much other than the permission
# configuration, though it may make sense to choose one or the other based other resources in the from
# namespace. Note that you can still vary configuration if you choose the consumer namespace, you just
# need multiple KongPlugins to do so.
---
apiVersion: v1
kind: Service
metadata:
name: one
namespace: pwuh
labels:
app: httpbin
annotations:
konghq.com/plugins: test
spec:
ports:
- name: http
port: 80
targetPort: 80
selector:
app: httpbin
---
apiVersion: v1
kind: Service
metadata:
name: two
namespace: fwuh
labels:
app: httpbin
annotations:
konghq.com/plugins: pwuh:test
spec:
ports:
- name: http
port: 80
targetPort: 80
selector:
app: httpbin
---
apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
name: admin
namespace: pwuh
annotations:
kubernetes.io/ingress.class: kong
konghq.com/plugins: test
username: admin
---
apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
name: test
namespace: pwuh
plugin: udp-log
config:
host: example.com
port: 80
---
kind: ReferenceGrant
metadata:
name: example
namespace: pwuh
spec:
from:
- group: ""
kind: Service
namespace: fwuh
to:
- group: "configuration.konghq.com"
kind: KongPlugin
name: test # Optional, to limit external resources to a specific KongPlugin only.
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
namespace: pwuh
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
spec:
containers:
- image: docker.io/kennethreitz/httpbin
name: httpbin
ports:
- containerPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httpbin
namespace: pwuh
spec:
ingressClassName: kong
rules:
- http:
paths:
- path: /one
pathType: ImplementationSpecific
backend:
service:
name: one
port:
number: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
namespace: fwuh
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
spec:
containers:
- image: docker.io/kennethreitz/httpbin
name: httpbin
ports:
- containerPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: httpbin
namespace: fwuh
spec:
ingressClassName: kong
rules:
- http:
paths:
- path: /two
pathType: ImplementationSpecific
backend:
service:
name: two
port:
number: 80
29 changes: 29 additions & 0 deletions internal/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/samber/lo"
netv1 "k8s.io/api/networking/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"

kongv1beta1 "github.com/kong/kubernetes-ingress-controller/v3/pkg/apis/configuration/v1beta1"
)
Expand Down Expand Up @@ -155,6 +156,34 @@ func ExtractKongPluginsFromAnnotations(anns map[string]string) []string {
return kongPluginCRs
}

type NamespacedKongPlugin k8stypes.NamespacedName

// ExtractNamespacedKongPluginsFromAnnotations extracts a KongPlugin name and optional namespace from an annotation
// value. Plugins are delimited by ",". Values are either colon-delimited "namespace:name" strings or name-only
// strings.
func ExtractNamespacedKongPluginsFromAnnotations(anns map[string]string) []NamespacedKongPlugin {
rainest marked this conversation as resolved.
Show resolved Hide resolved
v := pluginsFromAnnotations(anns)
if v == "" {
return nil
}
split := strings.Split(v, ",")
plugins := make([]NamespacedKongPlugin, 0, len(split))
for _, s := range split {
if s != "" {
plugin := NamespacedKongPlugin{}
if strings.Contains(s, ":") {
split := strings.Split(s, ":")
plugin.Namespace = strings.TrimSpace(split[0])
plugin.Name = strings.TrimSpace(split[1])
} else {
plugin.Name = strings.TrimSpace(s)
}
plugins = append(plugins, plugin)
}
}
return plugins
}

// ExtractConfigurationName extracts the name of the KongIngress object that holds
// information about the configuration to use in Routes, Services and Upstreams.
func ExtractConfigurationName(anns map[string]string) string {
Expand Down
46 changes: 46 additions & 0 deletions internal/annotations/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,52 @@ func TestExtractKongPluginsFromAnnotations(t *testing.T) {
}
}

func TestExtractNamespacedKongPluginsFromAnnotations(t *testing.T) {
type args struct {
anns map[string]string
}
tests := []struct {
name string
args args
want []NamespacedKongPlugin
}{
{
name: "empty",
args: args{
anns: map[string]string{
"konghq.com/nothing": "whatever",
},
},
want: nil,
},
{
name: "basic",
args: args{
anns: map[string]string{
"konghq.com/plugins": "kp-rl, kp-cors",
},
},
want: []NamespacedKongPlugin{{Name: "kp-rl"}, {Name: "kp-cors"}},
},
{
name: "mixed",
args: args{
anns: map[string]string{
"konghq.com/plugins": "default:kp-rl, kp-cors",
},
},
want: []NamespacedKongPlugin{{Name: "kp-rl", Namespace: "default"}, {Name: "kp-cors"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ExtractNamespacedKongPluginsFromAnnotations(tt.args.anns); !reflect.DeepEqual(got, tt.want) {
t.Errorf("ExtractNamespacedKongPluginsFromAnnotations() = %v, want %v", got, tt.want)
}
})
}
}

func TestExtractConfigurationName(t *testing.T) {
type args struct {
anns map[string]string
Expand Down
Loading
Loading