You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by kv...@apache.org on 2021/06/09 08:32:08 UTC
[apisix-ingress-controller] branch master updated: chore: add
authentication for ApisixRoute (#528)
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 <ta...@163.com>
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)
})
})