This is an automated email from the ASF dual-hosted git repository.
zhangjintao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push:
new bb5104e4 feat: add wolf-rbac authorization method. (#1011)
bb5104e4 is described below
commit bb5104e46911d187b2d624378b0f4c3ed5e7e38a
Author: Xin Rong <[email protected]>
AuthorDate: Wed May 18 18:57:37 2022 +0800
feat: add wolf-rbac authorization method. (#1011)
---
pkg/kube/apisix/apis/config/v2/types.go | 14 +
.../apisix/apis/config/v2/zz_generated.deepcopy.go | 47 ++++
pkg/kube/apisix/apis/config/v2beta3/types.go | 14 +
.../apis/config/v2beta3/zz_generated.deepcopy.go | 47 ++++
pkg/kube/translation/apisix_consumer.go | 6 +
pkg/kube/translation/apisix_consumer_test.go | 24 ++
pkg/kube/translation/apisix_route.go | 8 +
pkg/kube/translation/plugin.go | 22 ++
pkg/kube/translation/plugin_test.go | 92 +++++++
pkg/types/apisix/v1/plugin_types.go | 9 +
pkg/types/apisix/v1/zz_generated.deepcopy.go | 16 ++
samples/deploy/crd/v1/ApisixConsumer.yaml | 24 ++
samples/deploy/crd/v1/ApisixRoute.yaml | 2 +-
test/e2e/scaffold/wolf-rbac.go | 47 ++++
test/e2e/suite-features/consumer.go | 299 +++++++++++++++++++--
test/e2e/testdata/wolf-rbac/docker-compose.yaml | 77 ++++++
test/e2e/testdata/wolf-rbac/ip.sh | 20 ++
test/e2e/testdata/wolf-rbac/start.sh | 61 +++++
test/e2e/testdata/wolf-rbac/stop.sh | 24 ++
19 files changed, 826 insertions(+), 27 deletions(-)
diff --git a/pkg/kube/apisix/apis/config/v2/types.go
b/pkg/kube/apisix/apis/config/v2/types.go
index c3411c6f..aeace4d6 100644
--- a/pkg/kube/apisix/apis/config/v2/types.go
+++ b/pkg/kube/apisix/apis/config/v2/types.go
@@ -339,6 +339,7 @@ type ApisixConsumerSpec struct {
type ApisixConsumerAuthParameter struct {
BasicAuth *ApisixConsumerBasicAuth `json:"basicAuth,omitempty"
yaml:"basicAuth"`
KeyAuth *ApisixConsumerKeyAuth `json:"keyAuth,omitempty"
yaml:"keyAuth"`
+ WolfRBAC *ApisixConsumerWolfRBAC `json:"wolfRBAC,omitempty"
yaml:"wolfRBAC"`
JwtAuth *ApisixConsumerJwtAuth `json:"jwtAuth,omitempty"
yaml:"jwtAuth"`
}
@@ -365,6 +366,19 @@ type ApisixConsumerKeyAuthValue struct {
Key string `json:"key" yaml:"key"`
}
+// ApisixConsumerWolfRBAC defines the configuration for the wolf-rbac auth.
+type ApisixConsumerWolfRBAC struct {
+ SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"
yaml:"secretRef,omitempty"`
+ Value *ApisixConsumerWolfRBACValue `json:"value,omitempty"
yaml:"value,omitempty"`
+}
+
+// ApisixConsumerWolfRBAC defines the in-place server and appid and
header_prefix configuration for wolf-rbac auth.
+type ApisixConsumerWolfRBACValue struct {
+ Server string `json:"server,omitempty" yaml:"server,omitempty"`
+ Appid string `json:"appid,omitempty" yaml:"appid,omitempty"`
+ HeaderPrefix string `json:"header_prefix,omitempty"
yaml:"header_prefix,omitempty"`
+}
+
// ApisixConsumerJwtAuth defines the configuration for the jwt auth.
type ApisixConsumerJwtAuth struct {
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"
yaml:"secretRef,omitempty"`
diff --git a/pkg/kube/apisix/apis/config/v2/zz_generated.deepcopy.go
b/pkg/kube/apisix/apis/config/v2/zz_generated.deepcopy.go
index d4795f6f..4815aaa5 100644
--- a/pkg/kube/apisix/apis/config/v2/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v2/zz_generated.deepcopy.go
@@ -292,6 +292,11 @@ func (in *ApisixConsumerAuthParameter) DeepCopyInto(out
*ApisixConsumerAuthParam
*out = new(ApisixConsumerKeyAuth)
(*in).DeepCopyInto(*out)
}
+ if in.WolfRBAC != nil {
+ in, out := &in.WolfRBAC, &out.WolfRBAC
+ *out = new(ApisixConsumerWolfRBAC)
+ (*in).DeepCopyInto(*out)
+ }
if in.JwtAuth != nil {
in, out := &in.JwtAuth, &out.JwtAuth
*out = new(ApisixConsumerJwtAuth)
@@ -486,6 +491,48 @@ func (in *ApisixConsumerSpec) DeepCopy()
*ApisixConsumerSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
+func (in *ApisixConsumerWolfRBAC) DeepCopyInto(out *ApisixConsumerWolfRBAC) {
+ *out = *in
+ if in.SecretRef != nil {
+ in, out := &in.SecretRef, &out.SecretRef
+ *out = new(v1.LocalObjectReference)
+ **out = **in
+ }
+ if in.Value != nil {
+ in, out := &in.Value, &out.Value
+ *out = new(ApisixConsumerWolfRBACValue)
+ **out = **in
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver,
creating a new ApisixConsumerWolfRBAC.
+func (in *ApisixConsumerWolfRBAC) DeepCopy() *ApisixConsumerWolfRBAC {
+ if in == nil {
+ return nil
+ }
+ out := new(ApisixConsumerWolfRBAC)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
+func (in *ApisixConsumerWolfRBACValue) DeepCopyInto(out
*ApisixConsumerWolfRBACValue) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver,
creating a new ApisixConsumerWolfRBACValue.
+func (in *ApisixConsumerWolfRBACValue) DeepCopy() *ApisixConsumerWolfRBACValue
{
+ if in == nil {
+ return nil
+ }
+ out := new(ApisixConsumerWolfRBACValue)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
func (in *ApisixMutualTlsClientConfig) DeepCopyInto(out
*ApisixMutualTlsClientConfig) {
*out = *in
diff --git a/pkg/kube/apisix/apis/config/v2beta3/types.go
b/pkg/kube/apisix/apis/config/v2beta3/types.go
index 21fd822a..07a3a927 100644
--- a/pkg/kube/apisix/apis/config/v2beta3/types.go
+++ b/pkg/kube/apisix/apis/config/v2beta3/types.go
@@ -340,6 +340,7 @@ type ApisixConsumerSpec struct {
type ApisixConsumerAuthParameter struct {
BasicAuth *ApisixConsumerBasicAuth `json:"basicAuth,omitempty"
yaml:"basicAuth"`
KeyAuth *ApisixConsumerKeyAuth `json:"keyAuth,omitempty"
yaml:"keyAuth"`
+ WolfRBAC *ApisixConsumerWolfRBAC `json:"wolfRBAC,omitempty"
yaml:"wolfRBAC"`
JwtAuth *ApisixConsumerJwtAuth `json:"jwtAuth,omitempty"
yaml:"jwtAuth"`
}
@@ -366,6 +367,19 @@ type ApisixConsumerKeyAuthValue struct {
Key string `json:"key" yaml:"key"`
}
+// ApisixConsumerWolfRBAC defines the configuration for the wolf-rbac auth.
+type ApisixConsumerWolfRBAC struct {
+ SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"
yaml:"secretRef,omitempty"`
+ Value *ApisixConsumerWolfRBACValue `json:"value,omitempty"
yaml:"value,omitempty"`
+}
+
+// ApisixConsumerWolfRBAC defines the in-place server and appid and
header_prefix configuration for wolf-rbac auth.
+type ApisixConsumerWolfRBACValue struct {
+ Server string `json:"server,omitempty" yaml:"server,omitempty"`
+ Appid string `json:"appid,omitempty" yaml:"appid,omitempty"`
+ HeaderPrefix string `json:"header_prefix,omitempty"
yaml:"header_prefix,omitempty"`
+}
+
// ApisixConsumerJwtAuth defines the configuration for the jwt auth.
type ApisixConsumerJwtAuth struct {
SecretRef *corev1.LocalObjectReference `json:"secretRef,omitempty"
yaml:"secretRef,omitempty"`
diff --git a/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
b/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
index f5db3749..9682c936 100644
--- a/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v2beta3/zz_generated.deepcopy.go
@@ -293,6 +293,11 @@ func (in *ApisixConsumerAuthParameter) DeepCopyInto(out
*ApisixConsumerAuthParam
*out = new(ApisixConsumerKeyAuth)
(*in).DeepCopyInto(*out)
}
+ if in.WolfRBAC != nil {
+ in, out := &in.WolfRBAC, &out.WolfRBAC
+ *out = new(ApisixConsumerWolfRBAC)
+ (*in).DeepCopyInto(*out)
+ }
if in.JwtAuth != nil {
in, out := &in.JwtAuth, &out.JwtAuth
*out = new(ApisixConsumerJwtAuth)
@@ -487,6 +492,48 @@ func (in *ApisixConsumerSpec) DeepCopy()
*ApisixConsumerSpec {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
+func (in *ApisixConsumerWolfRBAC) DeepCopyInto(out *ApisixConsumerWolfRBAC) {
+ *out = *in
+ if in.SecretRef != nil {
+ in, out := &in.SecretRef, &out.SecretRef
+ *out = new(v1.LocalObjectReference)
+ **out = **in
+ }
+ if in.Value != nil {
+ in, out := &in.Value, &out.Value
+ *out = new(ApisixConsumerWolfRBACValue)
+ **out = **in
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver,
creating a new ApisixConsumerWolfRBAC.
+func (in *ApisixConsumerWolfRBAC) DeepCopy() *ApisixConsumerWolfRBAC {
+ if in == nil {
+ return nil
+ }
+ out := new(ApisixConsumerWolfRBAC)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
+func (in *ApisixConsumerWolfRBACValue) DeepCopyInto(out
*ApisixConsumerWolfRBACValue) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver,
creating a new ApisixConsumerWolfRBACValue.
+func (in *ApisixConsumerWolfRBACValue) DeepCopy() *ApisixConsumerWolfRBACValue
{
+ if in == nil {
+ return nil
+ }
+ out := new(ApisixConsumerWolfRBACValue)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
func (in *ApisixMutualTlsClientConfig) DeepCopyInto(out
*ApisixMutualTlsClientConfig) {
*out = *in
diff --git a/pkg/kube/translation/apisix_consumer.go
b/pkg/kube/translation/apisix_consumer.go
index a8f6f868..04525799 100644
--- a/pkg/kube/translation/apisix_consumer.go
+++ b/pkg/kube/translation/apisix_consumer.go
@@ -44,6 +44,12 @@ func (t *translator) TranslateApisixConsumer(ac
*configv2beta3.ApisixConsumer) (
return nil, fmt.Errorf("invalid jwt auth config: %s",
err)
}
plugins["jwt-auth"] = cfg
+ } else if ac.Spec.AuthParameter.WolfRBAC != nil {
+ cfg, err := t.translateConsumerWolfRBACPlugin(ac.Namespace,
ac.Spec.AuthParameter.WolfRBAC)
+ if err != nil {
+ return nil, fmt.Errorf("invalid wolf rbac config: %s",
err)
+ }
+ plugins["wolf-rbac"] = cfg
}
consumer := apisixv1.NewDefaultConsumer()
diff --git a/pkg/kube/translation/apisix_consumer_test.go
b/pkg/kube/translation/apisix_consumer_test.go
index 2422b1a3..775e2a3a 100644
--- a/pkg/kube/translation/apisix_consumer_test.go
+++ b/pkg/kube/translation/apisix_consumer_test.go
@@ -101,6 +101,30 @@ func TestTranslateApisixConsumer(t *testing.T) {
assert.Equal(t, "HS256", cfg3.Algorithm)
assert.Equal(t, int64(1000), cfg3.Exp)
assert.Equal(t, true, cfg3.Base64Secret)
+
+ ac = &configv2beta3.ApisixConsumer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "jack",
+ Namespace: "qa",
+ },
+ Spec: configv2beta3.ApisixConsumerSpec{
+ AuthParameter:
configv2beta3.ApisixConsumerAuthParameter{
+ WolfRBAC: &configv2beta3.ApisixConsumerWolfRBAC{
+ Value:
&configv2beta3.ApisixConsumerWolfRBACValue{
+ Server: "https://httpbin.org",
+ Appid: "test01",
+ },
+ },
+ },
+ },
+ }
+ consumer, err = (&translator{}).TranslateApisixConsumer(ac)
+ assert.Nil(t, err)
+ assert.Len(t, consumer.Plugins, 1)
+ cfg4 := consumer.Plugins["wolf-rbac"].(*apisixv1.WolfRBACConsumerConfig)
+ assert.Equal(t, "https://httpbin.org", cfg4.Server)
+ assert.Equal(t, "test01", cfg4.Appid)
+
// No test test cases for secret references as we already test them
// in plugin_test.go.
}
diff --git a/pkg/kube/translation/apisix_route.go
b/pkg/kube/translation/apisix_route.go
index 0a226bf3..e7dc23c8 100644
--- a/pkg/kube/translation/apisix_route.go
+++ b/pkg/kube/translation/apisix_route.go
@@ -272,6 +272,8 @@ func (t *translator) translateHTTPRouteV2beta3(ctx
*TranslateContext, ar *config
pluginMap["key-auth"] =
part.Authentication.KeyAuth
case "basicAuth":
pluginMap["basic-auth"] =
make(map[string]interface{})
+ case "wolfRBAC":
+ pluginMap["wolf-rbac"] =
make(map[string]interface{})
case "jwtAuth":
pluginMap["jwt-auth"] =
part.Authentication.JwtAuth
default:
@@ -404,6 +406,8 @@ func (t *translator) translateHTTPRouteV2(ctx
*TranslateContext, ar *configv2.Ap
pluginMap["key-auth"] =
part.Authentication.KeyAuth
case "basicAuth":
pluginMap["basic-auth"] =
make(map[string]interface{})
+ case "wolfRBAC":
+ pluginMap["wolf-rbac"] =
make(map[string]interface{})
case "jwtAuth":
pluginMap["jwt-auth"] =
part.Authentication.JwtAuth
default:
@@ -627,6 +631,8 @@ func (t *translator)
translateHTTPRouteV2beta3NotStrictly(ctx *TranslateContext,
pluginMap["key-auth"] =
part.Authentication.KeyAuth
case "basicAuth":
pluginMap["basic-auth"] =
make(map[string]interface{})
+ case "wolfRBAC":
+ pluginMap["wolf-rbac"] =
make(map[string]interface{})
case "jwtAuth":
pluginMap["jwt-auth"] =
part.Authentication.JwtAuth
default:
@@ -682,6 +688,8 @@ func (t *translator) translateHTTPRouteV2NotStrictly(ctx
*TranslateContext, ar *
pluginMap["key-auth"] =
part.Authentication.KeyAuth
case "basicAuth":
pluginMap["basic-auth"] =
make(map[string]interface{})
+ case "wolfRBAC":
+ pluginMap["wolf-rbac"] =
make(map[string]interface{})
case "jwtAuth":
pluginMap["jwt-auth"] =
part.Authentication.JwtAuth
default:
diff --git a/pkg/kube/translation/plugin.go b/pkg/kube/translation/plugin.go
index d6ede7c6..c2a9b109 100644
--- a/pkg/kube/translation/plugin.go
+++ b/pkg/kube/translation/plugin.go
@@ -115,6 +115,28 @@ func (t *translator)
translateConsumerBasicAuthPlugin(consumerNamespace string,
}, nil
}
+func (t *translator) translateConsumerWolfRBACPlugin(consumerNamespace string,
cfg *configv2beta3.ApisixConsumerWolfRBAC) (*apisixv1.WolfRBACConsumerConfig,
error) {
+ if cfg.Value != nil {
+ return &apisixv1.WolfRBACConsumerConfig{
+ Server: cfg.Value.Server,
+ Appid: cfg.Value.Appid,
+ HeaderPrefix: cfg.Value.HeaderPrefix,
+ }, nil
+ }
+ sec, err :=
t.SecretLister.Secrets(consumerNamespace).Get(cfg.SecretRef.Name)
+ if err != nil {
+ return nil, err
+ }
+ raw1 := sec.Data["server"]
+ raw2 := sec.Data["appid"]
+ raw3 := sec.Data["header_prefix"]
+ return &apisixv1.WolfRBACConsumerConfig{
+ Server: string(raw1),
+ Appid: string(raw2),
+ HeaderPrefix: string(raw3),
+ }, nil
+}
+
func (t *translator) translateConsumerJwtAuthPlugin(consumerNamespace string,
cfg *configv2beta3.ApisixConsumerJwtAuth) (*apisixv1.JwtAuthConsumerConfig,
error) {
if cfg.Value != nil {
// The field exp must be a positive integer, default value
86400.
diff --git a/pkg/kube/translation/plugin_test.go
b/pkg/kube/translation/plugin_test.go
index fa177e02..a6cad20f 100644
--- a/pkg/kube/translation/plugin_test.go
+++ b/pkg/kube/translation/plugin_test.go
@@ -844,3 +844,95 @@ func TestTranslateConsumerJwtAuthWithSecretRef(t
*testing.T) {
close(processCh)
close(stopCh)
}
+
+func TestTranslateConsumerWolfRBACPluginWithInPlaceValue(t *testing.T) {
+ wolfRBAC := &configv2beta3.ApisixConsumerWolfRBAC{
+ Value: &configv2beta3.ApisixConsumerWolfRBACValue{
+ Server: "https://httpbin.org",
+ Appid: "test-app",
+ },
+ }
+ cfg, err := (&translator{}).translateConsumerWolfRBACPlugin("default",
wolfRBAC)
+ assert.Nil(t, err)
+ assert.Equal(t, "https://httpbin.org", cfg.Server)
+ assert.Equal(t, "test-app", cfg.Appid)
+}
+
+func TestTranslateConsumerWolfRBACWithSecretRef(t *testing.T) {
+ sec := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "jack-wolf-rbac",
+ },
+ Data: map[string][]byte{
+ "server": []byte("http://127.0.0.1:12180"),
+ "appid": []byte("test-app"),
+ "header_prefix": []byte("X-"),
+ },
+ }
+ client := fake.NewSimpleClientset()
+ informersFactory := informers.NewSharedInformerFactory(client, 0)
+ secretInformer := informersFactory.Core().V1().Secrets().Informer()
+ secretLister := informersFactory.Core().V1().Secrets().Lister()
+ processCh := make(chan struct{})
+ stopCh := make(chan struct{})
+ secretInformer.AddEventHandler(cache.ResourceEventHandlerFuncs{
+ AddFunc: func(_ interface{}) {
+ processCh <- struct{}{}
+ },
+ UpdateFunc: func(_, _ interface{}) {
+ processCh <- struct{}{}
+ },
+ })
+ go secretInformer.Run(stopCh)
+
+ tr := &translator{
+ &TranslatorOptions{
+ SecretLister: secretLister,
+ },
+ }
+ _, err :=
client.CoreV1().Secrets("default").Create(context.Background(), sec,
metav1.CreateOptions{})
+ assert.Nil(t, err)
+
+ <-processCh
+
+ wolfRBAC := &configv2beta3.ApisixConsumerWolfRBAC{
+ SecretRef: &corev1.LocalObjectReference{Name: "jack-wolf-rbac"},
+ }
+ cfg, err := tr.translateConsumerWolfRBACPlugin("default", wolfRBAC)
+ assert.Nil(t, err)
+ assert.Equal(t, "http://127.0.0.1:12180", cfg.Server)
+ assert.Equal(t, "test-app", cfg.Appid)
+ assert.Equal(t, "X-", cfg.HeaderPrefix)
+
+ cfg, err = tr.translateConsumerWolfRBACPlugin("default2", wolfRBAC)
+ assert.Nil(t, cfg)
+ assert.NotNil(t, err)
+ assert.Contains(t, err.Error(), "not found")
+
+ delete(sec.Data, "server")
+ _, err =
client.CoreV1().Secrets("default").Update(context.Background(), sec,
metav1.UpdateOptions{})
+ assert.Nil(t, err)
+ <-processCh
+
+ cfg, err = tr.translateConsumerWolfRBACPlugin("default", wolfRBAC)
+ assert.Nil(t, err)
+
+ delete(sec.Data, "appid")
+ _, err =
client.CoreV1().Secrets("default").Update(context.Background(), sec,
metav1.UpdateOptions{})
+ assert.Nil(t, err)
+ <-processCh
+
+ cfg, err = tr.translateConsumerWolfRBACPlugin("default", wolfRBAC)
+ assert.Nil(t, err)
+
+ delete(sec.Data, "header_prefix")
+ _, err =
client.CoreV1().Secrets("default").Update(context.Background(), sec,
metav1.UpdateOptions{})
+ assert.Nil(t, err)
+ <-processCh
+
+ cfg, err = tr.translateConsumerWolfRBACPlugin("default", wolfRBAC)
+ assert.Nil(t, err)
+
+ close(processCh)
+ close(stopCh)
+}
diff --git a/pkg/types/apisix/v1/plugin_types.go
b/pkg/types/apisix/v1/plugin_types.go
index 411062c9..d46b3b50 100644
--- a/pkg/types/apisix/v1/plugin_types.go
+++ b/pkg/types/apisix/v1/plugin_types.go
@@ -88,6 +88,15 @@ type JwtAuthConsumerConfig struct {
// +k8s:deepcopy-gen=true
type BasicAuthRouteConfig struct{}
+// WolfRBACConsumerConfig is the rule config for wolf-rbac plugin
+// used in Consumer object.
+// +k8s:deepcopy-gen=true
+type WolfRBACConsumerConfig struct {
+ Server string `json:"server,omitempty"`
+ Appid string `json:"appid,omitempty"`
+ HeaderPrefix string `json:"header_prefix,omitempty"`
+}
+
// RewriteConfig is the rule config for proxy-rewrite plugin.
// +k8s:deepcopy-gen=true
type RewriteConfig struct {
diff --git a/pkg/types/apisix/v1/zz_generated.deepcopy.go
b/pkg/types/apisix/v1/zz_generated.deepcopy.go
index 4c04d914..480cd434 100644
--- a/pkg/types/apisix/v1/zz_generated.deepcopy.go
+++ b/pkg/types/apisix/v1/zz_generated.deepcopy.go
@@ -721,3 +721,19 @@ func (in *UpstreamPassiveHealthCheckUnhealthy) DeepCopy()
*UpstreamPassiveHealth
in.DeepCopyInto(out)
return out
}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
+func (in *WolfRBACConsumerConfig) DeepCopyInto(out *WolfRBACConsumerConfig) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver,
creating a new WolfRBACConsumerConfig.
+func (in *WolfRBACConsumerConfig) DeepCopy() *WolfRBACConsumerConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(WolfRBACConsumerConfig)
+ in.DeepCopyInto(out)
+ return out
+}
diff --git a/samples/deploy/crd/v1/ApisixConsumer.yaml
b/samples/deploy/crd/v1/ApisixConsumer.yaml
index 1d24703c..4c967194 100644
--- a/samples/deploy/crd/v1/ApisixConsumer.yaml
+++ b/samples/deploy/crd/v1/ApisixConsumer.yaml
@@ -49,6 +49,7 @@ spec:
oneOf:
- required: ["basicAuth"]
- required: ["keyAuth"]
+ - required: ["wolfRBAC"]
- required: ["jwtAuth"]
properties:
basicAuth:
@@ -133,3 +134,26 @@ spec:
minLength: 1
required:
- name
+ wolfRBAC:
+ type: object
+ oneOf:
+ - required: ["value"]
+ - required: ["secretRef"]
+ properties:
+ value:
+ type: object
+ properties:
+ server:
+ type: string
+ appid:
+ type: string
+ header_prefix:
+ type: string
+ secretRef:
+ type: object
+ properties:
+ name:
+ type: string
+ minLength: 1
+ required:
+ - name
diff --git a/samples/deploy/crd/v1/ApisixRoute.yaml
b/samples/deploy/crd/v1/ApisixRoute.yaml
index deee3d3a..757b8838 100644
--- a/samples/deploy/crd/v1/ApisixRoute.yaml
+++ b/samples/deploy/crd/v1/ApisixRoute.yaml
@@ -452,7 +452,7 @@ spec:
type: boolean
type:
type: string
- enum: [ "basicAuth", "keyAuth", "jwtAuth" ]
+ enum: [ "basicAuth", "keyAuth", "jwtAuth",
"wolfRBAC" ]
keyAuth:
type: object
properties:
diff --git a/test/e2e/scaffold/wolf-rbac.go b/test/e2e/scaffold/wolf-rbac.go
new file mode 100644
index 00000000..6a16a5c1
--- /dev/null
+++ b/test/e2e/scaffold/wolf-rbac.go
@@ -0,0 +1,47 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package scaffold
+
+import (
+ "fmt"
+ "os/exec"
+)
+
+func (s *Scaffold) StartWolfRBACServer() error {
+ cmd := exec.Command("sh", "testdata/wolf-rbac/start.sh")
+ return cmd.Run()
+}
+
+func (s *Scaffold) GetWolfRBACServerURL() (string, error) {
+ cmd := exec.Command("sh", "testdata/wolf-rbac/ip.sh")
+ ip, err := cmd.Output()
+ if err != nil {
+ return "", err
+ }
+ if len(ip) == 0 {
+ return "", fmt.Errorf("wolf-server start failed")
+ }
+ httpsvc := fmt.Sprintf("http://%s:12180", string(ip))
+ return httpsvc, nil
+}
+
+func (s *Scaffold) StopWolfRBACServer() error {
+ cmd := exec.Command("sh", "testdata/wolf-rbac/stop.sh")
+ err := cmd.Run()
+ return err
+}
diff --git a/test/e2e/suite-features/consumer.go
b/test/e2e/suite-features/consumer.go
index 8c6085cb..5efef35d 100644
--- a/test/e2e/suite-features/consumer.go
+++ b/test/e2e/suite-features/consumer.go
@@ -15,6 +15,8 @@
package features
import (
+ "encoding/base64"
+ "encoding/json"
"fmt"
"net/http"
"time"
@@ -388,9 +390,65 @@ spec:
assert.Contains(ginkgo.GinkgoT(), msg, "404 Route Not Found")
})
- ginkgo.It("ApisixRoute without authentication", func() {
+ ginkgo.It("ApisixRoute with wolfRBAC consumer", func() {
+ _ = s.StartWolfRBACServer()
+ wolfSvr, err := s.GetWolfRBACServerURL()
+ assert.Nil(ginkgo.GinkgoT(), err, "checking wolf-server")
+ defer s.StopWolfRBACServer()
+
+ ac := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixConsumer
+metadata:
+ name: wolf-user
+spec:
+ authParameter:
+ wolfRBAC:
+ value:
+ server: "%s"
+ appid: "test-app"
+ header_prefix: "X-"
+`, wolfSvr)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac),
"creating wolfRBAC ApisixConsumer")
+
+ // Wait until the ApisixConsumer create event was delivered.
+ time.Sleep(6 * time.Second)
+
+ grs, err := s.ListApisixConsumers()
+ assert.Nil(ginkgo.GinkgoT(), err, "listing consumer")
+ assert.Len(ginkgo.GinkgoT(), grs, 1)
+ assert.Len(ginkgo.GinkgoT(), grs[0].Plugins, 1)
+ wolfRBAC, _ :=
grs[0].Plugins["wolf-rbac"].(map[string]interface{})
+ assert.Equal(ginkgo.GinkgoT(), wolfRBAC, map[string]interface{}{
+ "server": wolfSvr,
+ "appid": "test-app",
+ "header_prefix": "X-",
+ })
+ adminSvc, adminPort := s.ApisixAdminServiceAndPort()
+ ar1 := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: default
+spec:
+ http:
+ - name: public-api
+ match:
+ paths:
+ - /apisix/plugin/wolf-rbac/login
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ plugins:
+ - name: public-api
+ enable: true
+`, adminSvc, adminPort)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar1),
"creating ApisixRoute")
+ assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1),
"Checking number of routes")
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams")
+
backendSvc, backendPorts := s.DefaultHTTPBackend()
- ar := fmt.Sprintf(`
+ ar2 := fmt.Sprintf(`
apiVersion: apisix.apache.org/v2beta3
kind: ApisixRoute
metadata:
@@ -402,41 +460,183 @@ spec:
hosts:
- httpbin.org
paths:
- - /ip
- exprs:
- - subject:
- scope: Header
- name: X-Foo
- op: Equal
- value: bar
+ - /*
backends:
- serviceName: %s
servicePort: %d
authentication:
- enable: false
+ enable: true
+ type: wolfRBAC
`, backendSvc, backendPorts[0])
- assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar),
"creating ApisixRoute without authentication")
- assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1),
"Checking number of routes")
- assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams")
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar2),
"creating ApisixRoute")
+ assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(2),
"Checking number of routes")
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(2), "Checking number of upstreams")
+ payload := []byte(`
+{
+ "appid": "test-app",
+ "username": "test",
+ "password": "test-123456",
+ "authType": 1
+}
+ `)
+ body :=
s.NewAPISIXClient().POST("/apisix/plugin/wolf-rbac/login").
+ WithHeader("Content-Type", "application/json").
+ WithBytes(payload).
+ Expect().
+ Status(http.StatusOK).
+ Body().
+ Contains("rbac_token").
+ Raw()
- _ = s.NewAPISIXClient().GET("/ip").
+ data := struct {
+ Token string `json:"rbac_token"`
+ }{}
+ _ = json.Unmarshal([]byte(body), &data)
+
+ _ = s.NewAPISIXClient().GET("").
WithHeader("Host", "httpbin.org").
- WithHeader("X-Foo", "bar").
+ WithHeader("Authorization", data.Token).
Expect().
Status(http.StatusOK)
+
+ msg401 := s.NewAPISIXClient().GET("").
+ WithHeader("Host", "httpbin.org").
+ Expect().
+ Status(http.StatusUnauthorized).
+ Body().
+ Raw()
+ assert.Contains(ginkgo.GinkgoT(), msg401, "Missing rbac token
in request")
})
- ginkgo.It("ApisixRoute with jwtAuth consumer using secret", func() {
- secret := `
+ ginkgo.It("ApisixRoute with wolfRBAC consumer using secret", func() {
+ _ = s.StartWolfRBACServer()
+ wolfSvr, err := s.GetWolfRBACServerURL()
+ assert.Nil(ginkgo.GinkgoT(), err, "checking wolf-server")
+ defer s.StopWolfRBACServer()
+
+ secret := fmt.Sprintf(`
apiVersion: v1
kind: Secret
metadata:
- name: jwt
+ name: rbac
data:
- key: Zm9vLWtleQ==
+ server: %s
+ appid: dGVzdC1hcHA=
+ header_prefix: WC0=
+`, base64.StdEncoding.EncodeToString([]byte(wolfSvr)))
+ assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(secret), "creating wolfRBAC secret for
ApisixConsumer")
+
+ ac := `
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixConsumer
+metadata:
+ name: wolf-user
+spec:
+ authParameter:
+ wolfRBAC:
+ secretRef:
+ name: rbac
`
- assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(secret), "creating jwtAuth secret for
ApisixConsumer")
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac),
"creating wolfRBAC ApisixConsumer")
+ // Wait until the ApisixConsumer create event was delivered.
+ time.Sleep(6 * time.Second)
+
+ grs, err := s.ListApisixConsumers()
+ assert.Nil(ginkgo.GinkgoT(), err, "listing consumer")
+ assert.Len(ginkgo.GinkgoT(), grs, 1)
+ assert.Len(ginkgo.GinkgoT(), grs[0].Plugins, 1)
+ wolfRBAC, _ :=
grs[0].Plugins["wolf-rbac"].(map[string]interface{})
+ assert.Equal(ginkgo.GinkgoT(), wolfRBAC, map[string]interface{}{
+ "server": wolfSvr,
+ "appid": "test-app",
+ "header_prefix": "X-",
+ })
+ adminSvc, adminPort := s.ApisixAdminServiceAndPort()
+ ar1 := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: default
+spec:
+ http:
+ - name: public-api
+ match:
+ paths:
+ - /apisix/plugin/wolf-rbac/login
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ plugins:
+ - name: public-api
+ enable: true
+`, adminSvc, adminPort)
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar1),
"creating ApisixRoute")
+ assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1),
"Checking number of routes")
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams")
+
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ ar2 := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /*
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ authentication:
+ enable: true
+ type: wolfRBAC
+`, backendSvc, backendPorts[0])
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar2),
"creating ApisixRoute")
+ assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(2),
"Checking number of routes")
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(2), "Checking number of upstreams")
+ payload := []byte(`
+{
+ "appid": "test-app",
+ "username": "test",
+ "password": "test-123456",
+ "authType": 1
+}
+ `)
+ body :=
s.NewAPISIXClient().POST("/apisix/plugin/wolf-rbac/login").
+ WithHeader("Content-Type", "application/json").
+ WithBytes(payload).
+ Expect().
+ Status(http.StatusOK).
+ Body().
+ Contains("rbac_token").
+ Raw()
+
+ data := struct {
+ Token string `json:"rbac_token"`
+ }{}
+ _ = json.Unmarshal([]byte(body), &data)
+
+ _ = s.NewAPISIXClient().GET("").
+ WithHeader("Host", "httpbin.org").
+ WithHeader("Authorization", data.Token).
+ Expect().
+ Status(http.StatusOK)
+
+ msg401 := s.NewAPISIXClient().GET("").
+ WithHeader("Host", "httpbin.org").
+ Expect().
+ Status(http.StatusUnauthorized).
+ Body().
+ Raw()
+ assert.Contains(ginkgo.GinkgoT(), msg401, "Missing rbac token
in request")
+ })
+
+ ginkgo.It("ApisixRoute with jwtAuth consumer", func() {
ac := `
apiVersion: apisix.apache.org/v2beta3
kind: ApisixConsumer
@@ -445,8 +645,8 @@ metadata:
spec:
authParameter:
jwtAuth:
- secretRef:
- name: jwt
+ value:
+ key: foo-key
`
assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac),
"creating jwtAuth ApisixConsumer")
@@ -531,8 +731,17 @@ spec:
Status(http.StatusOK)
})
- ginkgo.It("ApisixRoute with jwtAuth consumer", func() {
- //time.Sleep(time.Minute * 30)
+ ginkgo.It("ApisixRoute with jwtAuth consumer using secret", func() {
+ secret := `
+apiVersion: v1
+kind: Secret
+metadata:
+ name: jwt
+data:
+ key: Zm9vLWtleQ==
+`
+ assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(secret), "creating jwtAuth secret for
ApisixConsumer")
+
ac := `
apiVersion: apisix.apache.org/v2beta3
kind: ApisixConsumer
@@ -541,8 +750,8 @@ metadata:
spec:
authParameter:
jwtAuth:
- value:
- key: foo-key
+ secretRef:
+ name: jwt
`
assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac),
"creating jwtAuth ApisixConsumer")
@@ -626,4 +835,42 @@ spec:
Expect().
Status(http.StatusOK)
})
+
+ ginkgo.It("ApisixRoute without authentication", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2beta3
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ exprs:
+ - subject:
+ scope: Header
+ name: X-Foo
+ op: Equal
+ value: bar
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ authentication:
+ enable: false
+`, backendSvc, backendPorts[0])
+ assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar),
"creating ApisixRoute without authentication")
+ assert.Nil(ginkgo.GinkgoT(), s.EnsureNumApisixRoutesCreated(1),
"Checking number of routes")
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams")
+
+ _ = s.NewAPISIXClient().GET("/ip").
+ WithHeader("Host", "httpbin.org").
+ WithHeader("X-Foo", "bar").
+ Expect().
+ Status(http.StatusOK)
+ })
})
diff --git a/test/e2e/testdata/wolf-rbac/docker-compose.yaml
b/test/e2e/testdata/wolf-rbac/docker-compose.yaml
new file mode 100644
index 00000000..9e5404eb
--- /dev/null
+++ b/test/e2e/testdata/wolf-rbac/docker-compose.yaml
@@ -0,0 +1,77 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+version: '3'
+services:
+ database:
+ container_name: wolf-database
+ image: postgres:10.7
+ restart: always
+ environment:
+ POSTGRES_USER: wolfroot
+ POSTGRES_PASSWORD: 123456
+ POSTGRES_DB: wolf
+ volumes:
+ - ./db-psql.sql:/docker-entrypoint-initdb.d/db.sql:ro
+ server:
+ container_name: wolf-server
+ image: igeeky/wolf-server
+ restart: always
+ ports:
+ - "12180:12180"
+ depends_on:
+ - database
+ environment:
+ RBAC_ROOT_PASSWORD: wolf-123456
+ RBAC_TOKEN_KEY: f40215a5f25cbb6d36df07629aaf1172240fe48d
+ WOLF_CRYPT_KEY: fbd4962351924792cb5e5b131435cd30b24e3570
+ RBAC_SQL_URL: postgres://wolfroot:123456@database:5432/wolf
+ CLIENT_CHANGE_PWD: "no"
+ command: npm run start
+ agent-or:
+ container_name: wolf-agent-or
+ image: igeeky/wolf-agent
+ restart: always
+ ports:
+ - "12182:12182"
+ depends_on:
+ - server
+ environment:
+ BACKEND_URL: http://openresty.org
+ RBAC_SERVER_URL: http://server:12180
+ RBAC_APP_ID: openresty
+ restful-demo:
+ container_name: restful-demo
+ image: igeeky/restful-demo:latest
+ restart: always
+ ports:
+ - "10090:10090"
+ agent-demo:
+ container_name: wolf-agent-demo
+ image: igeeky/wolf-agent
+ restart: always
+ ports:
+ - "12184:12184"
+ depends_on:
+ - server
+ - restful-demo
+ environment:
+ BACKEND_URL: http://restful-demo:10090
+ RBAC_SERVER_URL: http://server:12180
+ RBAC_APP_ID: restful-demo
+ AGENT_PORT: 12184
+ EXTENSION_CONFIG: include /opt/wolf/agent/conf/no-permission-demo.conf;
\ No newline at end of file
diff --git a/test/e2e/testdata/wolf-rbac/ip.sh
b/test/e2e/testdata/wolf-rbac/ip.sh
new file mode 100644
index 00000000..47f5ba15
--- /dev/null
+++ b/test/e2e/testdata/wolf-rbac/ip.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+echo -n `docker inspect -f '{{range
.NetworkSettings.Networks}}{{.Gateway}}{{end}}' wolf-server`
diff --git a/test/e2e/testdata/wolf-rbac/start.sh
b/test/e2e/testdata/wolf-rbac/start.sh
new file mode 100644
index 00000000..d8de22ef
--- /dev/null
+++ b/test/e2e/testdata/wolf-rbac/start.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+cd testdata/wolf-rbac/
+
+wget
https://raw.githubusercontent.com/iGeeky/wolf/f6ddeb75a37bff90406f0f0a2b7ae5d16f6f3bd4/server/script/db-psql.sql
+
+# start database
+docker-compose up -d database
+
+# start wolf-server
+docker-compose up -d server restful-demo agent-or agent-demo
+
+sleep 6
+
+WOLF_TOKEN=`curl http://127.0.0.1:12180/wolf/user/login -H "Content-Type:
application/json" -d '{ "username": "root", "password": "wolf-123456"}' -s |
grep token| tr -d ':",' | awk '{print $2}'`
+
+curl http://127.0.0.1:12180/wolf/application \
+-H "Content-Type: application/json" \
+-H "x-rbac-token: $WOLF_TOKEN" \
+-d '{
+ "id": "test-app",
+ "name": "application for test"
+}'
+
+curl http://127.0.0.1:12180/wolf/resource \
+-H "Content-Type: application/json" \
+-H "x-rbac-token: $WOLF_TOKEN" \
+-d '{
+ "appID": "test-app",
+ "matchType": "prefix",
+ "name": "/",
+ "action": "GET",
+ "permID": "ALLOW_ALL"
+}'
+
+curl http://127.0.0.1:12180/wolf/user \
+-H "Content-Type: application/json" \
+-H "x-rbac-token: $WOLF_TOKEN" \
+-d '{
+ "username": "test",
+ "nickname": "test",
+ "password": "test-123456",
+ "appIDs": ["test-app"]
+}'
diff --git a/test/e2e/testdata/wolf-rbac/stop.sh
b/test/e2e/testdata/wolf-rbac/stop.sh
new file mode 100644
index 00000000..db4b916b
--- /dev/null
+++ b/test/e2e/testdata/wolf-rbac/stop.sh
@@ -0,0 +1,24 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+cd testdata/wolf-rbac/
+
+docker-compose -f 'docker-compose.yaml' -p 'wolf-rbac' down
+
+rm db-psql.sql