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 33d42c31 feat: add ldap-auth authorization method (#1588)
33d42c31 is described below
commit 33d42c31dbc70e43b08d9d4c95ca7ecd8a572633
Author: machinly <[email protected]>
AuthorDate: Tue Jan 31 17:00:36 2023 +0800
feat: add ldap-auth authorization method (#1588)
---
Makefile | 13 +-
pkg/kube/apisix/apis/config/v2/types.go | 30 ++-
.../apisix/apis/config/v2/zz_generated.deepcopy.go | 64 ++++++
.../apisix/translation/apisix_consumer.go | 6 +
.../apisix/translation/apisix_consumer_test.go | 21 ++
pkg/providers/apisix/translation/apisix_plugin.go | 22 +++
.../apisix/translation/apisix_plugin_test.go | 55 ++++++
pkg/providers/apisix/translation/apisix_route.go | 4 +
pkg/types/apisix/v1/plugin_types.go | 7 +
pkg/types/apisix/v1/zz_generated.deepcopy.go | 16 ++
samples/deploy/crd/v1/ApisixConsumer.yaml | 21 ++
samples/deploy/crd/v1/ApisixRoute.yaml | 12 ++
.../suite-plugins-authentication/ldap.go | 218 +++++++++++++++++++++
test/e2e/testdata/ldap/cmd.sh | 36 ++++
test/e2e/testdata/ldap/docker-compose.yaml | 33 ++++
15 files changed, 553 insertions(+), 5 deletions(-)
diff --git a/Makefile b/Makefile
index 04e38989..ad32b818 100644
--- a/Makefile
+++ b/Makefile
@@ -135,7 +135,7 @@ unit-test:
### e2e-test: Run e2e test cases (in existing clusters directly)
.PHONY: e2e-test
-e2e-test: ginkgo-check pack-images e2e-wolf-rbac install install-gateway-api
+e2e-test: ginkgo-check pack-images e2e-wolf-rbac e2e-ldap install
install-gateway-api
cd test/e2e \
&& go mod download \
&& export REGISTRY=$(REGISTRY) \
@@ -266,6 +266,17 @@ ifneq ("$(E2E_FOCUS)", "")
&& ./test/e2e/testdata/wolf-rbac/cmd.sh start
endif
+.PHONY: e2e-ldap
+e2e-ldap:
+ifeq ("$(E2E_FOCUS)", "")
+ chmod +x ./test/e2e/testdata/ldap/cmd.sh &&
./test/e2e/testdata/ldap/cmd.sh start
+endif
+ifneq ("$(E2E_FOCUS)", "")
+ echo $(E2E_FOCUS) | grep -E
'suite-plugins-authentication|consumer|ldap' || exit 0 \
+ && chmod +x ./test/e2e/testdata/ldap/cmd.sh \
+ && ./test/e2e/testdata/ldap/cmd.sh start
+endif
+
### kind-load-images: Load the images to the kind cluster
.PHONY: kind-load-images
kind-load-images:
diff --git a/pkg/kube/apisix/apis/config/v2/types.go
b/pkg/kube/apisix/apis/config/v2/types.go
index 5f223f62..1f92785c 100644
--- a/pkg/kube/apisix/apis/config/v2/types.go
+++ b/pkg/kube/apisix/apis/config/v2/types.go
@@ -199,10 +199,11 @@ func (p *ApisixRoutePluginConfig) DeepCopy()
*ApisixRoutePluginConfig {
// ApisixRouteAuthentication is the authentication-related
// configuration in ApisixRoute.
type ApisixRouteAuthentication struct {
- Enable bool `json:"enable" yaml:"enable"`
- Type string `json:"type" yaml:"type"`
- KeyAuth ApisixRouteAuthenticationKeyAuth `json:"keyAuth,omitempty"
yaml:"keyAuth,omitempty"`
- JwtAuth ApisixRouteAuthenticationJwtAuth `json:"jwtAuth,omitempty"
yaml:"jwtAuth,omitempty"`
+ Enable bool `json:"enable" yaml:"enable"`
+ Type string `json:"type" yaml:"type"`
+ KeyAuth ApisixRouteAuthenticationKeyAuth `json:"keyAuth,omitempty"
yaml:"keyAuth,omitempty"`
+ JwtAuth ApisixRouteAuthenticationJwtAuth `json:"jwtAuth,omitempty"
yaml:"jwtAuth,omitempty"`
+ LDAPAuth ApisixRouteAuthenticationLDAPAuth `json:"ldapAuth,omitempty"
yaml:"ldapAuth,omitempty"`
}
// ApisixRouteAuthenticationKeyAuth is the keyAuth-related
@@ -219,6 +220,15 @@ type ApisixRouteAuthenticationJwtAuth struct {
Cookie string `json:"cookie,omitempty" yaml:"cookie,omitempty"`
}
+// ApisixRouteAuthenticationLDAPAuth is the LDAP auth related
+// configuration in ApisixRouteAuthentication.
+type ApisixRouteAuthenticationLDAPAuth struct {
+ BaseDN string `json:"base_dn,omitempty" yaml:"base_dn,omitempty"`
+ LDAPURI string `json:"ldap_uri,omitempty" yaml:"ldap_uri,omitempty"`
+ UseTLS bool `json:"use_tls,omitempty" yaml:"use_tls,omitempty"`
+ UID string `json:"uid,omitempty" yaml:"uid,omitempty"`
+}
+
// ApisixRouteStream is the configuration for level 4 route
type ApisixRouteStream struct {
// The rule name, cannot be empty.
@@ -356,6 +366,7 @@ type ApisixConsumerAuthParameter struct {
WolfRBAC *ApisixConsumerWolfRBAC `json:"wolfRBAC,omitempty"
yaml:"wolfRBAC"`
JwtAuth *ApisixConsumerJwtAuth `json:"jwtAuth,omitempty"
yaml:"jwtAuth"`
HMACAuth *ApisixConsumerHMACAuth `json:"hmacAuth,omitempty"
yaml:"hmacAuth"`
+ LDAPAuth *ApisixConsumerLDAPAuth `json:"ldapAuth,omitempty"
yaml:"ldapAuth"`
}
// ApisixConsumerBasicAuth defines the configuration for basic auth.
@@ -430,6 +441,17 @@ type ApisixConsumerHMACAuthValue struct {
MaxReqBody int64 `json:"max_req_body,omitempty"
yaml:"max_req_body,omitempty"`
}
+// ApisixConsumerLDAPAuth defines the configuration for the ldap auth.
+type ApisixConsumerLDAPAuth struct {
+ SecretRef *corev1.LocalObjectReference `json:"secretRef" yaml:"secret"`
+ Value *ApisixConsumerLDAPAuthValue `json:"value,omitempty"
yaml:"value,omitempty"`
+}
+
+// ApisixConsumerLDAPAuthValue defines the in-place configuration for ldap
auth.
+type ApisixConsumerLDAPAuthValue struct {
+ UserDN string `json:"user_dn" yaml:"user_dn"`
+}
+
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ApisixConsumerList contains a list of ApisixConsumer.
type ApisixConsumerList struct {
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 f83b3648..d12816d3 100644
--- a/pkg/kube/apisix/apis/config/v2/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v2/zz_generated.deepcopy.go
@@ -308,6 +308,11 @@ func (in *ApisixConsumerAuthParameter) DeepCopyInto(out
*ApisixConsumerAuthParam
*out = new(ApisixConsumerHMACAuth)
(*in).DeepCopyInto(*out)
}
+ if in.LDAPAuth != nil {
+ in, out := &in.LDAPAuth, &out.LDAPAuth
+ *out = new(ApisixConsumerLDAPAuth)
+ (*in).DeepCopyInto(*out)
+ }
return
}
@@ -494,6 +499,48 @@ func (in *ApisixConsumerKeyAuthValue) DeepCopy()
*ApisixConsumerKeyAuthValue {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
+func (in *ApisixConsumerLDAPAuth) DeepCopyInto(out *ApisixConsumerLDAPAuth) {
+ *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(ApisixConsumerLDAPAuthValue)
+ **out = **in
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver,
creating a new ApisixConsumerLDAPAuth.
+func (in *ApisixConsumerLDAPAuth) DeepCopy() *ApisixConsumerLDAPAuth {
+ if in == nil {
+ return nil
+ }
+ out := new(ApisixConsumerLDAPAuth)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
+func (in *ApisixConsumerLDAPAuthValue) DeepCopyInto(out
*ApisixConsumerLDAPAuthValue) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver,
creating a new ApisixConsumerLDAPAuthValue.
+func (in *ApisixConsumerLDAPAuthValue) DeepCopy() *ApisixConsumerLDAPAuthValue
{
+ if in == nil {
+ return nil
+ }
+ out := new(ApisixConsumerLDAPAuthValue)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
func (in *ApisixConsumerList) DeepCopyInto(out *ApisixConsumerList) {
*out = *in
@@ -804,6 +851,7 @@ func (in *ApisixRouteAuthentication) DeepCopyInto(out
*ApisixRouteAuthentication
*out = *in
out.KeyAuth = in.KeyAuth
out.JwtAuth = in.JwtAuth
+ out.LDAPAuth = in.LDAPAuth
return
}
@@ -849,6 +897,22 @@ func (in *ApisixRouteAuthenticationKeyAuth) DeepCopy()
*ApisixRouteAuthenticatio
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
+func (in *ApisixRouteAuthenticationLDAPAuth) DeepCopyInto(out
*ApisixRouteAuthenticationLDAPAuth) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver,
creating a new ApisixRouteAuthenticationLDAPAuth.
+func (in *ApisixRouteAuthenticationLDAPAuth) DeepCopy()
*ApisixRouteAuthenticationLDAPAuth {
+ if in == nil {
+ return nil
+ }
+ out := new(ApisixRouteAuthenticationLDAPAuth)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
func (in *ApisixRouteHTTP) DeepCopyInto(out *ApisixRouteHTTP) {
*out = *in
diff --git a/pkg/providers/apisix/translation/apisix_consumer.go
b/pkg/providers/apisix/translation/apisix_consumer.go
index 802662b8..c27eae64 100644
--- a/pkg/providers/apisix/translation/apisix_consumer.go
+++ b/pkg/providers/apisix/translation/apisix_consumer.go
@@ -100,6 +100,12 @@ func (t *translator) TranslateApisixConsumerV2(ac
*configv2.ApisixConsumer) (*ap
return nil, fmt.Errorf("invaild hmac auth config: %s",
err)
}
plugins["hmac-auth"] = cfg
+ } else if ac.Spec.AuthParameter.LDAPAuth != nil {
+ cfg, err := t.translateConsumerLDAPAuthPluginV2(ac.Namespace,
ac.Spec.AuthParameter.LDAPAuth)
+ if err != nil {
+ return nil, fmt.Errorf("invalid ldap auth config: %s",
err)
+ }
+ plugins["ldap-auth"] = cfg
}
consumer := apisixv1.NewDefaultConsumer()
diff --git a/pkg/providers/apisix/translation/apisix_consumer_test.go
b/pkg/providers/apisix/translation/apisix_consumer_test.go
index 82c45450..c85e5dbd 100644
--- a/pkg/providers/apisix/translation/apisix_consumer_test.go
+++ b/pkg/providers/apisix/translation/apisix_consumer_test.go
@@ -277,6 +277,27 @@ func TestTranslateApisixConsumerV2(t *testing.T) {
assert.Equal(t, "foo", cfg5.AccessKey)
assert.Equal(t, "bar", cfg5.SecretKey)
+ ac = &configv2.ApisixConsumer{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "jack",
+ Namespace: "qa",
+ },
+ Spec: configv2.ApisixConsumerSpec{
+ AuthParameter: configv2.ApisixConsumerAuthParameter{
+ LDAPAuth: &configv2.ApisixConsumerLDAPAuth{
+ Value:
&configv2.ApisixConsumerLDAPAuthValue{
+ UserDN:
"cn=user01,ou=users,dc=example,dc=org",
+ },
+ },
+ },
+ },
+ }
+ consumer, err = (&translator{}).TranslateApisixConsumerV2(ac)
+ assert.Nil(t, err)
+ assert.Len(t, consumer.Plugins, 1)
+ cfg6 := consumer.Plugins["ldap-auth"].(*apisixv1.LDAPAuthConsumerConfig)
+ assert.Equal(t, "cn=user01,ou=users,dc=example,dc=org", cfg6.UserDN)
+
// No test test cases for secret references as we already test them
// in plugin_test.go.
}
diff --git a/pkg/providers/apisix/translation/apisix_plugin.go
b/pkg/providers/apisix/translation/apisix_plugin.go
index 9a93281f..96142b91 100644
--- a/pkg/providers/apisix/translation/apisix_plugin.go
+++ b/pkg/providers/apisix/translation/apisix_plugin.go
@@ -520,3 +520,25 @@ func (t *translator)
translateConsumerHMACAuthPluginV2(consumerNamespace string,
MaxReqBody: maxReqBody,
}, nil
}
+
+func (t *translator) translateConsumerLDAPAuthPluginV2(consumerNamespace
string, cfg *configv2.ApisixConsumerLDAPAuth)
(*apisixv1.LDAPAuthConsumerConfig, error) {
+ if cfg.Value != nil {
+ return &apisixv1.LDAPAuthConsumerConfig{
+ UserDN: cfg.Value.UserDN,
+ }, nil
+ }
+
+ sec, err :=
t.SecretLister.Secrets(consumerNamespace).Get(cfg.SecretRef.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ userDNRaw, ok := sec.Data["user_dn"]
+ if !ok || len(userDNRaw) == 0 {
+ return nil, _errKeyNotFoundOrInvalid
+ }
+
+ return &apisixv1.LDAPAuthConsumerConfig{
+ UserDN: string(userDNRaw),
+ }, nil
+}
diff --git a/pkg/providers/apisix/translation/apisix_plugin_test.go
b/pkg/providers/apisix/translation/apisix_plugin_test.go
index 7c334873..e39b9bdd 100644
--- a/pkg/providers/apisix/translation/apisix_plugin_test.go
+++ b/pkg/providers/apisix/translation/apisix_plugin_test.go
@@ -1039,3 +1039,58 @@ func TestTranslateConsumerHMACAuthPluginWithSecretRef(t
*testing.T) {
close(processCh)
close(stopCh)
}
+
+func TestTranslateConsumerLDAPAuthPluginWithInPlaceValue(t *testing.T) {
+ ldapAuth := &configv2.ApisixConsumerLDAPAuth{
+ Value: &configv2.ApisixConsumerLDAPAuthValue{
+ UserDN: "cn=user01,ou=users,dc=example,dc=org",
+ },
+ }
+ cfg, err :=
(&translator{}).translateConsumerLDAPAuthPluginV2("default", ldapAuth)
+ assert.Nil(t, err)
+ assert.Equal(t, "cn=user01,ou=users,dc=example,dc=org", cfg.UserDN)
+}
+
+func TestTranslateConsumerLDAPAuthPluginWithSecretRef(t *testing.T) {
+ sec := &corev1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "fatpa-ldap-auth",
+ },
+ Data: map[string][]byte{
+ "user_dn":
[]byte("cn=user01,ou=users,dc=example,dc=org"),
+ },
+ }
+
+ 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,
+ }, translation.NewTranslator(nil)}
+
+ _, err :=
client.CoreV1().Secrets("default").Create(context.Background(), sec,
metav1.CreateOptions{})
+ assert.Nil(t, err)
+
+ <-processCh
+
+ ldapAuth := &configv2.ApisixConsumerLDAPAuth{
+ SecretRef: &corev1.LocalObjectReference{Name:
"fatpa-ldap-auth"},
+ }
+ cfg, err := tr.translateConsumerLDAPAuthPluginV2("default", ldapAuth)
+ assert.Nil(t, err)
+ assert.Equal(t, "cn=user01,ou=users,dc=example,dc=org", cfg.UserDN)
+}
diff --git a/pkg/providers/apisix/translation/apisix_route.go
b/pkg/providers/apisix/translation/apisix_route.go
index 6d1c3f26..ebcac1ba 100644
--- a/pkg/providers/apisix/translation/apisix_route.go
+++ b/pkg/providers/apisix/translation/apisix_route.go
@@ -285,6 +285,8 @@ func (t *translator) translateHTTPRouteV2(ctx
*translation.TranslateContext, ar
pluginMap["jwt-auth"] =
part.Authentication.JwtAuth
case "hmacAuth":
pluginMap["hmac-auth"] =
make(map[string]interface{})
+ case "ldapAuth":
+ pluginMap["ldap-auth"] =
part.Authentication.LDAPAuth
default:
pluginMap["basic-auth"] =
make(map[string]interface{})
}
@@ -670,6 +672,8 @@ func (t *translator) generateHTTPRouteV2DeleteMark(ctx
*translation.TranslateCon
pluginMap["jwt-auth"] =
part.Authentication.JwtAuth
case "hmacAuth":
pluginMap["hmac-auth"] =
make(map[string]interface{})
+ case "ldapAuth":
+ pluginMap["ldap-auth"] =
part.Authentication.LDAPAuth
default:
pluginMap["basic-auth"] =
make(map[string]interface{})
}
diff --git a/pkg/types/apisix/v1/plugin_types.go
b/pkg/types/apisix/v1/plugin_types.go
index 73eda73e..dd6bf9b9 100644
--- a/pkg/types/apisix/v1/plugin_types.go
+++ b/pkg/types/apisix/v1/plugin_types.go
@@ -110,6 +110,13 @@ type HMACAuthConsumerConfig struct {
MaxReqBody int64 `json:"max_req_body,omitempty"
yaml:"max_req_body,omitempty"`
}
+// LDAPAuthConsumerConfig is the rule config for ldap-auth plugin
+// used in Consumer object.
+// +k8s:deepcopy-gen=true
+type LDAPAuthConsumerConfig struct {
+ UserDN string `json:"user_dn"`
+}
+
// BasicAuthRouteConfig is the rule config for basic-auth plugin
// used in Route object.
// +k8s:deepcopy-gen=true
diff --git a/pkg/types/apisix/v1/zz_generated.deepcopy.go
b/pkg/types/apisix/v1/zz_generated.deepcopy.go
index ea41a3ca..df2e8216 100644
--- a/pkg/types/apisix/v1/zz_generated.deepcopy.go
+++ b/pkg/types/apisix/v1/zz_generated.deepcopy.go
@@ -271,6 +271,22 @@ func (in *KeyAuthConsumerConfig) DeepCopy()
*KeyAuthConsumerConfig {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
+func (in *LDAPAuthConsumerConfig) DeepCopyInto(out *LDAPAuthConsumerConfig) {
+ *out = *in
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver,
creating a new LDAPAuthConsumerConfig.
+func (in *LDAPAuthConsumerConfig) DeepCopy() *LDAPAuthConsumerConfig {
+ if in == nil {
+ return nil
+ }
+ out := new(LDAPAuthConsumerConfig)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver,
writing into out. in must be non-nil.
func (in *Metadata) DeepCopyInto(out *Metadata) {
*out = *in
diff --git a/samples/deploy/crd/v1/ApisixConsumer.yaml
b/samples/deploy/crd/v1/ApisixConsumer.yaml
index 90346304..e69f030f 100644
--- a/samples/deploy/crd/v1/ApisixConsumer.yaml
+++ b/samples/deploy/crd/v1/ApisixConsumer.yaml
@@ -220,6 +220,7 @@ spec:
- required: ["wolfRBAC"]
- required: ["jwtAuth"]
- required: ["hmacAuth"]
+ - required: ["ldapAuth"]
properties:
basicAuth:
type: object
@@ -366,3 +367,23 @@ spec:
minLength: 1
required:
- name
+ ldapAuth:
+ type: object
+ oneOf:
+ - required: ["value"]
+ - required: ["secretRef"]
+ properties:
+ value:
+ type: object
+ properties:
+ user_dn:
+ type: string
+ required:
+ - user_dn
+ secretRef:
+ type: object
+ properties:
+ name:
+ type: string
+ minLength: 1
+ required:
diff --git a/samples/deploy/crd/v1/ApisixRoute.yaml
b/samples/deploy/crd/v1/ApisixRoute.yaml
index 4d1f9870..55dd1e5f 100644
--- a/samples/deploy/crd/v1/ApisixRoute.yaml
+++ b/samples/deploy/crd/v1/ApisixRoute.yaml
@@ -524,6 +524,7 @@ spec:
- "jwtAuth"
- "wolfRBAC"
- "hmacAuth"
+ - "ldapAuth"
keyAuth:
type: object
properties:
@@ -538,6 +539,17 @@ spec:
type: string
cookie:
type: string
+ ldapAuth:
+ type: object
+ properties:
+ base_dn:
+ type: string
+ ldap_uri:
+ type: string
+ use_tls:
+ type: boolean
+ uid:
+ type: string
required:
- enable
stream:
diff --git a/test/e2e/suite-plugins/suite-plugins-authentication/ldap.go
b/test/e2e/suite-plugins/suite-plugins-authentication/ldap.go
new file mode 100644
index 00000000..107e42e9
--- /dev/null
+++ b/test/e2e/suite-plugins/suite-plugins-authentication/ldap.go
@@ -0,0 +1,218 @@
+// 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 plugins
+
+import (
+ "fmt"
+ "net/http"
+ "os/exec"
+ "time"
+
+ "github.com/onsi/ginkgo/v2"
+ "github.com/stretchr/testify/assert"
+
+ "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = ginkgo.Describe("suite-plugins-authentication: ApisixConsumer with
ldap", func() {
+ suites := func(scaffoldFunc func() *scaffold.Scaffold) {
+ s := scaffoldFunc()
+ getLDAPServerURL := func() (string, error) {
+ cmd := exec.Command("sh", "testdata/ldap/cmd.sh", "ip")
+ ip, err := cmd.Output()
+ if err != nil {
+ return "", err
+ }
+ if len(ip) == 0 {
+ return "", fmt.Errorf("ldap-server start
failed")
+ }
+ return fmt.Sprintf("%s:1389", string(ip)), nil
+ }
+
+ ginkgo.It("ApisixRoute with ldapAuth consumer", func() {
+ ac := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixConsumer
+metadata:
+ name: jack
+spec:
+ authParameter:
+ ldapAuth:
+ value:
+ user_dn: "cn=jack,ou=users,dc=ldap,dc=example,dc=org"
+`
+ assert.Nil(ginkgo.GinkgoT(),
s.CreateVersionedApisixResource(ac), "creating ldapAuth 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)
+ ldapAuth, _ :=
grs[0].Plugins["ldap-auth"].(map[string]interface{})
+ assert.Equal(ginkgo.GinkgoT(), ldapAuth["user_dn"],
"cn=jack,ou=users,dc=ldap,dc=example,dc=org")
+
+ ldapSvr, err := getLDAPServerURL()
+ assert.Nil(ginkgo.GinkgoT(), err, "check ldap server")
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ authentication:
+ enable: true
+ type: ldapAuth
+ ldapAuth:
+ ldap_uri: %s
+ base_dn: "ou=users,dc=ldap,dc=example,dc=org"
+ use_tls: false
+ uid: "cn"
+`, backendSvc, backendPorts[0], ldapSvr)
+ assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(ar), "Creating ApisixRoute with ldapAuth")
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixRoutesCreated(1), "Checking number of routes")
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams")
+
+ msg401CourseMissing := s.NewAPISIXClient().GET("/ip").
+ WithHeader("Host", "httpbin.org").
+ Expect().
+ Status(http.StatusUnauthorized).
+ Body().
+ Raw()
+ assert.Contains(ginkgo.GinkgoT(), msg401CourseMissing,
"Missing authorization in request")
+
+ msg401CouseInvalid := s.NewAPISIXClient().GET("/ip").
+ WithHeader("Host", "httpbin.org").
+ WithBasicAuth("jack", "invalid").
+ Expect().
+ Status(http.StatusUnauthorized).
+ Body().
+ Raw()
+ assert.Contains(ginkgo.GinkgoT(), msg401CouseInvalid,
"Invalid user authorization")
+
+ _ = s.NewAPISIXClient().GET("/ip").
+ WithHeader("Host", "httpbin.org").
+ WithBasicAuth("jack", "jackPassword").
+ Expect().
+ Status(http.StatusOK)
+ })
+
+ ginkgo.It("ApisixRoute with ldapAuth consumer using secret",
func() {
+ secret := `
+apiVersion: v1
+kind: Secret
+metadata:
+ name: ldap
+data:
+ user_dn: Y249amFjayxvdT11c2VycyxkYz1sZGFwLGRjPWV4YW1wbGUsZGM9b3Jn
+`
+ assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(secret), "creating ldapAuth secret for
ApisixConsumer")
+
+ ac := `
+apiVersion: apisix.apache.org/v2
+kind: ApisixConsumer
+metadata:
+ name: jack
+spec:
+ authParameter:
+ ldapAuth:
+ secretRef:
+ name: ldap
+`
+ assert.Nil(ginkgo.GinkgoT(),
s.CreateVersionedApisixResource(ac), "creating ldapAuth 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)
+ ldapAuth, _ :=
grs[0].Plugins["ldap-auth"].(map[string]interface{})
+ assert.Equal(ginkgo.GinkgoT(), ldapAuth["user_dn"],
"cn=jack,ou=users,dc=ldap,dc=example,dc=org")
+
+ ldapSvr, err := getLDAPServerURL()
+ assert.Nil(ginkgo.GinkgoT(), err, "check ldap server")
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ authentication:
+ enable: true
+ type: ldapAuth
+ ldapAuth:
+ ldap_uri: %s
+ base_dn: "ou=users,dc=ldap,dc=example,dc=org"
+ use_tls: false
+ uid: "cn"
+`, backendSvc, backendPorts[0], ldapSvr)
+ assert.Nil(ginkgo.GinkgoT(),
s.CreateVersionedApisixResource(ar), "Creating ApisixRoute with ldapAuth")
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixRoutesCreated(1), "Checking number of routes")
+ assert.Nil(ginkgo.GinkgoT(),
s.EnsureNumApisixUpstreamsCreated(1), "Checking number of upstreams")
+
+ msg401CouseMissing := s.NewAPISIXClient().GET("/ip").
+ WithHeader("Host", "httpbin.org").
+ Expect().
+ Status(http.StatusUnauthorized).
+ Body().
+ Raw()
+ assert.Contains(ginkgo.GinkgoT(), msg401CouseMissing,
"Missing authorization in request")
+
+ msg401CourseInvalid := s.NewAPISIXClient().GET("/ip").
+ WithHeader("Host", "httpbin.org").
+ WithBasicAuth("jack", "invalid").
+ Expect().
+ Status(http.StatusUnauthorized).
+ Body().
+ Raw()
+ assert.Contains(ginkgo.GinkgoT(), msg401CourseInvalid,
"Invalid user authorization")
+
+ _ = s.NewAPISIXClient().GET("/ip").
+ WithHeader("Host", "httpbin.org").
+ WithBasicAuth("jack", "jackPassword").
+ Expect().
+ Status(http.StatusOK)
+ })
+ }
+
+ ginkgo.Describe("suite-plugins-authentication: scaffold v2", func() {
+ suites(scaffold.NewDefaultV2Scaffold)
+ })
+})
diff --git a/test/e2e/testdata/ldap/cmd.sh b/test/e2e/testdata/ldap/cmd.sh
new file mode 100755
index 00000000..7f2f5927
--- /dev/null
+++ b/test/e2e/testdata/ldap/cmd.sh
@@ -0,0 +1,36 @@
+#!/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 test/e2e/testdata/ldap/
+
+OPTION=$1
+
+if [ $OPTION = "ip" ]; then
+ echo -n `docker inspect -f '{{range
.NetworkSettings.Networks}}{{.Gateway}}{{end}}' openldap`
+elif [ $OPTION = "start" ]; then
+ docker-compose -f 'docker-compose.yaml' -p 'openldap' down
+
+ # start openldap
+ docker-compose -f 'docker-compose.yaml' -p 'openldap' up -d
+
+elif [ $OPTION = "stop" ]; then
+ docker-compose -f 'docker-compose.yaml' -p 'openldap' down
+else
+ echo "argument is one of [ip, start, stop]"
+fi
diff --git a/test/e2e/testdata/ldap/docker-compose.yaml
b/test/e2e/testdata/ldap/docker-compose.yaml
new file mode 100644
index 00000000..364aef39
--- /dev/null
+++ b/test/e2e/testdata/ldap/docker-compose.yaml
@@ -0,0 +1,33 @@
+#
+# 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:
+ openldap:
+ container_name: openldap
+ image: docker.io/bitnami/openldap:2.6
+ ports:
+ - '1389:1389'
+ environment:
+ - LDAP_PORT_NUMBER=1389
+ - LDAP_ENABLE_TLS=no
+ - LDAP_ADMIN_USERNAME=admin
+ - LDAP_ADMIN_PASSWORD=admin
+ - LDAP_ROOT=dc=ldap,dc=example,dc=org
+ - LDAP_USERS=jack
+ - LDAP_PASSWORDS=jackPassword