This is an automated email from the ASF dual-hosted git repository.

kvn 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 fca6211  chore: add authentication for ApisixRoute (#528)
fca6211 is described below

commit fca62110b81958935263c816f71be96c4500a84e
Author: Jintao Zhang <[email protected]>
AuthorDate: Wed Jun 9 16:31:57 2021 +0800

    chore: add authentication for ApisixRoute (#528)
---
 pkg/kube/apisix/apis/config/v2alpha1/types.go      |  21 +-
 .../apis/config/v2alpha1/zz_generated.deepcopy.go  |  38 +++
 pkg/kube/translation/apisix_route.go               |  13 +
 samples/deploy/crd/v1beta1/ApisixRoute.yaml        |  15 +
 test/e2e/features/consumer.go                      | 378 ++++++++++++++++++++-
 5 files changed, 456 insertions(+), 9 deletions(-)

diff --git a/pkg/kube/apisix/apis/config/v2alpha1/types.go 
b/pkg/kube/apisix/apis/config/v2alpha1/types.go
index 83ecd88..f5d08c2 100644
--- a/pkg/kube/apisix/apis/config/v2alpha1/types.go
+++ b/pkg/kube/apisix/apis/config/v2alpha1/types.go
@@ -96,9 +96,10 @@ type ApisixRouteHTTP struct {
        // Backends represents potential backends to proxy after the route
        // rule matched. When number of backends are more than one, 
traffic-split
        // plugin in APISIX will be used to split traffic based on the backend 
weight.
-       Backends  []*ApisixRouteHTTPBackend `json:"backends" yaml:"backends"`
-       Websocket bool                      `json:"websocket" yaml:"websocket"`
-       Plugins   []*ApisixRouteHTTPPlugin  `json:"plugins,omitempty" 
yaml:"plugins,omitempty"`
+       Backends       []*ApisixRouteHTTPBackend  `json:"backends" 
yaml:"backends"`
+       Websocket      bool                       `json:"websocket" 
yaml:"websocket"`
+       Plugins        []*ApisixRouteHTTPPlugin   `json:"plugins,omitempty" 
yaml:"plugins,omitempty"`
+       Authentication *ApisixRouteAuthentication 
`json:"authentication,omitempty" yaml:"authentication,omitempty"`
 }
 
 // ApisixRouteHTTPMatch represents the match condition for hitting this route.
@@ -194,6 +195,20 @@ type ApisixRouteHTTPPlugin struct {
 // any plugins.
 type ApisixRouteHTTPPluginConfig map[string]interface{}
 
+// 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"`
+}
+
+// ApisixRouteAuthenticationKeyAuth is the keyAuth-related
+// configuration in ApisixRouteAuthentication.
+type ApisixRouteAuthenticationKeyAuth struct {
+       Header string `json:"header,omitempty" yaml:"header,omitempty"`
+}
+
 func (p ApisixRouteHTTPPluginConfig) DeepCopyInto(out 
*ApisixRouteHTTPPluginConfig) {
        b, _ := json.Marshal(&p)
        _ = json.Unmarshal(b, out)
diff --git a/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go 
b/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go
index ee97f83..107459b 100644
--- a/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go
+++ b/pkg/kube/apisix/apis/config/v2alpha1/zz_generated.deepcopy.go
@@ -400,6 +400,39 @@ func (in *ApisixRoute) DeepCopyObject() runtime.Object {
 }
 
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ApisixRouteAuthentication) DeepCopyInto(out 
*ApisixRouteAuthentication) {
+       *out = *in
+       out.KeyAuth = in.KeyAuth
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ApisixRouteAuthentication.
+func (in *ApisixRouteAuthentication) DeepCopy() *ApisixRouteAuthentication {
+       if in == nil {
+               return nil
+       }
+       out := new(ApisixRouteAuthentication)
+       in.DeepCopyInto(out)
+       return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, 
writing into out. in must be non-nil.
+func (in *ApisixRouteAuthenticationKeyAuth) DeepCopyInto(out 
*ApisixRouteAuthenticationKeyAuth) {
+       *out = *in
+       return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new ApisixRouteAuthenticationKeyAuth.
+func (in *ApisixRouteAuthenticationKeyAuth) DeepCopy() 
*ApisixRouteAuthenticationKeyAuth {
+       if in == nil {
+               return nil
+       }
+       out := new(ApisixRouteAuthenticationKeyAuth)
+       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
        if in.Match != nil {
@@ -434,6 +467,11 @@ func (in *ApisixRouteHTTP) DeepCopyInto(out 
*ApisixRouteHTTP) {
                        }
                }
        }
+       if in.Authentication != nil {
+               in, out := &in.Authentication, &out.Authentication
+               *out = new(ApisixRouteAuthentication)
+               **out = **in
+       }
        return
 }
 
diff --git a/pkg/kube/translation/apisix_route.go 
b/pkg/kube/translation/apisix_route.go
index 9285bb6..ba2ef2b 100644
--- a/pkg/kube/translation/apisix_route.go
+++ b/pkg/kube/translation/apisix_route.go
@@ -130,6 +130,19 @@ func (t *translator) translateHTTPRoute(ctx 
*TranslateContext, ar *configv2alpha
                                pluginMap[plugin.Name] = 
make(map[string]interface{})
                        }
                }
+
+               // add KeyAuth and basicAuth plugin
+               if part.Authentication != nil && part.Authentication.Enable {
+                       switch part.Authentication.Type {
+                       case "keyAuth":
+                               pluginMap["key-auth"] = 
part.Authentication.KeyAuth
+                       case "basicAuth":
+                               pluginMap["basic-auth"] = 
make(map[string]interface{})
+                       default:
+                               pluginMap["basic-auth"] = 
make(map[string]interface{})
+                       }
+               }
+
                var exprs [][]apisixv1.StringOrSlice
                if part.Match.NginxVars != nil {
                        exprs, err = 
t.translateRouteMatchExprs(part.Match.NginxVars)
diff --git a/samples/deploy/crd/v1beta1/ApisixRoute.yaml 
b/samples/deploy/crd/v1beta1/ApisixRoute.yaml
index 974795e..619213e 100644
--- a/samples/deploy/crd/v1beta1/ApisixRoute.yaml
+++ b/samples/deploy/crd/v1beta1/ApisixRoute.yaml
@@ -210,6 +210,21 @@ spec:
                     required:
                       - name
                       - enable
+                  authentication:
+                    type: object
+                    properties:
+                      enable:
+                        type: boolean
+                      type:
+                        type: string
+                        enum: ["basicAuth", "keyAuth"]
+                      keyAuth:
+                        type: object
+                        properties:
+                          header:
+                            type: string
+                    required:
+                      - enable
             tcp:
               type: array
               minItems: 1
diff --git a/test/e2e/features/consumer.go b/test/e2e/features/consumer.go
index b4eade6..0ee672c 100644
--- a/test/e2e/features/consumer.go
+++ b/test/e2e/features/consumer.go
@@ -15,6 +15,8 @@
 package features
 
 import (
+       "fmt"
+       "net/http"
        "time"
 
        "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
@@ -25,7 +27,7 @@ import (
 var _ = ginkgo.Describe("ApisixConsumer", func() {
        s := scaffold.NewDefaultV2Scaffold()
 
-       ginkgo.It("create basicAuth consumer using value", func() {
+       ginkgo.It("ApisixRoute with basicAuth consumer", func() {
                ac := `
 apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixConsumer
@@ -38,12 +40,103 @@ spec:
         username: foo
         password: bar
 `
-               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac), 
"creating ApisixConsumer")
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac), 
"creating basicAuth ApisixConsumer")
 
-               defer func() {
-                       err := s.RemoveResourceByString(ac)
-                       assert.Nil(ginkgo.GinkgoT(), err)
-               }()
+               // 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)
+               basicAuth, _ := grs[0].Plugins["basic-auth"]
+               assert.Equal(ginkgo.GinkgoT(), basicAuth, 
map[string]interface{}{
+                       "username": "foo",
+                       "password": "bar",
+               })
+
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2alpha1
+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
+   backend:
+     serviceName: %s
+     servicePort: %d
+   authentication:
+     enable: true
+     type: basicAuth
+`, backendSvc, backendPorts[0])
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar), 
"creating ApisixRoute with basicAuth")
+               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").
+                       WithHeader("Authorization", "Basic Zm9vOmJhcg==").
+                       Expect().
+                       Status(http.StatusOK)
+
+               msg := s.NewAPISIXClient().GET("/ip").
+                       WithHeader("Host", "httpbin.org").
+                       WithHeader("X-Foo", "bar").
+                       Expect().
+                       Status(http.StatusUnauthorized).
+                       Body().
+                       Raw()
+               assert.Contains(ginkgo.GinkgoT(), msg, "Missing authorization 
in request")
+
+               msg = s.NewAPISIXClient().GET("/ip").
+                       WithHeader("Host", "httpbin.org").
+                       WithHeader("X-Foo", "baz").
+                       WithHeader("Authorization", "Basic Zm9vOmJhcg==").
+                       Expect().
+                       Status(http.StatusNotFound).
+                       Body().
+                       Raw()
+               assert.Contains(ginkgo.GinkgoT(), msg, "404 Route Not Found")
+       })
+
+       ginkgo.It("ApisixRoute with basicAuth consumer using secret", func() {
+               secret := `
+apiVersion: v1
+kind: Secret
+metadata:
+  name: basic
+data:
+  password: YmFy
+  username: Zm9v
+`
+               assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromString(secret), "creating basic secret for ApisixConsumer")
+
+               ac := `
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixConsumer
+metadata:
+  name: basicvalue
+spec:
+  authParameter:
+    basicAuth:
+      secretRef:
+        name: basic
+`
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac), 
"creating basicAuth ApisixConsumer")
 
                // Wait until the ApisixConsumer create event was delivered.
                time.Sleep(6 * time.Second)
@@ -57,5 +150,278 @@ spec:
                        "username": "foo",
                        "password": "bar",
                })
+
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2alpha1
+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
+   backend:
+     serviceName: %s
+     servicePort: %d
+   authentication:
+     enable: true
+     type: basicAuth
+`, backendSvc, backendPorts[0])
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar), 
"creating ApisixRoute with basicAuth")
+               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").
+                       WithHeader("Authorization", "Basic Zm9vOmJhcg==").
+                       Expect().
+                       Status(http.StatusOK)
+
+               msg := s.NewAPISIXClient().GET("/ip").
+                       WithHeader("Host", "httpbin.org").
+                       WithHeader("X-Foo", "bar").
+                       Expect().
+                       Status(http.StatusUnauthorized).
+                       Body().
+                       Raw()
+               assert.Contains(ginkgo.GinkgoT(), msg, "Missing authorization 
in request")
+
+               msg = s.NewAPISIXClient().GET("/ip").
+                       WithHeader("Host", "httpbin.org").
+                       WithHeader("X-Foo", "baz").
+                       WithHeader("Authorization", "Basic Zm9vOmJhcg==").
+                       Expect().
+                       Status(http.StatusNotFound).
+                       Body().
+                       Raw()
+               assert.Contains(ginkgo.GinkgoT(), msg, "404 Route Not Found")
+       })
+
+       ginkgo.It("ApisixRoute with keyAuth consumer", func() {
+               ac := `
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixConsumer
+metadata:
+  name: keyvalue
+spec:
+  authParameter:
+    keyAuth:
+      value:
+        key: foo
+`
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac), 
"creating keyAuth 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)
+               basicAuth, _ := grs[0].Plugins["key-auth"]
+               assert.Equal(ginkgo.GinkgoT(), basicAuth, 
map[string]interface{}{
+                       "key": "foo",
+               })
+
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2alpha1
+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
+   backend:
+     serviceName: %s
+     servicePort: %d
+   authentication:
+     enable: true
+     type: keyAuth
+`, backendSvc, backendPorts[0])
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar), 
"creating ApisixRoute with keyAuth")
+               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").
+                       WithHeader("apikey", "foo").
+                       Expect().
+                       Status(http.StatusOK)
+
+               msg := s.NewAPISIXClient().GET("/ip").
+                       WithHeader("Host", "httpbin.org").
+                       WithHeader("X-Foo", "bar").
+                       Expect().
+                       Status(http.StatusUnauthorized).
+                       Body().
+                       Raw()
+               assert.Contains(ginkgo.GinkgoT(), msg, "Missing API key found 
in request")
+
+               msg = s.NewAPISIXClient().GET("/ip").
+                       WithHeader("Host", "httpbin.org").
+                       WithHeader("X-Foo", "baz").
+                       WithHeader("apikey", "baz").
+                       Expect().
+                       Status(http.StatusNotFound).
+                       Body().
+                       Raw()
+               assert.Contains(ginkgo.GinkgoT(), msg, "404 Route Not Found")
+       })
+
+       ginkgo.It("ApisixRoute with keyAuth consumer using secret", func() {
+               secret := `
+apiVersion: v1
+kind: Secret
+metadata:
+  name: keyauth
+data:
+  key: Zm9v
+`
+               assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromString(secret), "creating keyauth secret for 
ApisixConsumer")
+
+               ac := `
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixConsumer
+metadata:
+  name: keyvalue
+spec:
+  authParameter:
+    keyAuth:
+      secretRef:
+        name: keyauth
+`
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ac), 
"creating keyAuth 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)
+               basicAuth, _ := grs[0].Plugins["key-auth"]
+               assert.Equal(ginkgo.GinkgoT(), basicAuth, 
map[string]interface{}{
+                       "key": "foo",
+               })
+
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2alpha1
+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
+   backend:
+     serviceName: %s
+     servicePort: %d
+   authentication:
+     enable: true
+     type: keyAuth
+`, backendSvc, backendPorts[0])
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar), 
"creating ApisixRoute with keyAuth")
+               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").
+                       WithHeader("apikey", "foo").
+                       Expect().
+                       Status(http.StatusOK)
+
+               msg := s.NewAPISIXClient().GET("/ip").
+                       WithHeader("Host", "httpbin.org").
+                       WithHeader("X-Foo", "bar").
+                       Expect().
+                       Status(http.StatusUnauthorized).
+                       Body().
+                       Raw()
+               assert.Contains(ginkgo.GinkgoT(), msg, "Missing API key found 
in request")
+
+               msg = s.NewAPISIXClient().GET("/ip").
+                       WithHeader("Host", "httpbin.org").
+                       WithHeader("X-Foo", "baz").
+                       WithHeader("apikey", "baz").
+                       Expect().
+                       Status(http.StatusNotFound).
+                       Body().
+                       Raw()
+               assert.Contains(ginkgo.GinkgoT(), msg, "404 Route Not Found")
+       })
+
+       ginkgo.It("ApisixRoute without authentication", func() {
+               backendSvc, backendPorts := s.DefaultHTTPBackend()
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2alpha1
+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
+   backend:
+     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)
        })
 })

Reply via email to