Skip to content

Commit

Permalink
feat: add csrf plugin annotation in ingress resource (#1023)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlinsRan committed May 26, 2022
1 parent 59ba41a commit 25daa6e
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 0 deletions.
1 change: 1 addition & 0 deletions pkg/kube/translation/annotations.go
Expand Up @@ -31,6 +31,7 @@ var (
annotations.NewForwardAuthHandler(),
annotations.NewBasicAuthHandler(),
annotations.NewKeyAuthHandler(),
annotations.NewCSRFHandler(),
}
)

Expand Down
48 changes: 48 additions & 0 deletions pkg/kube/translation/annotations/csrf.go
@@ -0,0 +1,48 @@
// 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 annotations

import (
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)

const (
_enableCsrf = AnnotationsPrefix + "enable-csrf"
_csrfKey = AnnotationsPrefix + "csrf-key"
)

type csrf struct{}

// NewCSRFHandler creates a handler to convert annotations about
// CSRF to APISIX csrf plugin.
func NewCSRFHandler() Handler {
return &csrf{}
}

func (c *csrf) PluginName() string {
return "csrf"
}

func (c *csrf) Handle(e Extractor) (interface{}, error) {
if !e.GetBoolAnnotation(_enableCsrf) {
return nil, nil
}
var plugin apisixv1.CSRFConfig
plugin.Key = e.GetStringAnnotation(_csrfKey)
if plugin.Key != "" {
return &plugin, nil
}
return nil, nil
}
6 changes: 6 additions & 0 deletions pkg/types/apisix/v1/plugin_types.go
Expand Up @@ -49,6 +49,12 @@ type CorsConfig struct {
AllowHeaders string `json:"allow_headers,omitempty"`
}

// CSRfConfig is the rule config for csrf plugin.
// +k8s:deepcopy-gen=true
type CSRFConfig struct {
Key string `json:"key"`
}

// KeyAuthConsumerConfig is the rule config for key-auth plugin
// used in Consumer object.
// +k8s:deepcopy-gen=true
Expand Down
16 changes: 16 additions & 0 deletions pkg/types/apisix/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

195 changes: 195 additions & 0 deletions test/e2e/suite-annotations/csrf.go
@@ -0,0 +1,195 @@
// 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 annotations

import (
"fmt"
"net/http"
"time"

"github.com/onsi/ginkgo"
"github.com/stretchr/testify/assert"

"github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
)

var _ = ginkgo.Describe("suite-annotations: csrf annotations", func() {
s := scaffold.NewDefaultScaffold()

ginkgo.It("enable csrf in ingress networking/v1", func() {
backendSvc, backendPort := s.DefaultHTTPBackend()
ing := fmt.Sprintf(`
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: apisix
k8s.apisix.apache.org/enable-csrf: "true"
k8s.apisix.apache.org/csrf-key: "foo-key"
name: ingress-v1
spec:
rules:
- host: httpbin.org
http:
paths:
- path: /*
pathType: Prefix
backend:
service:
name: %s
port:
number: %d
`, backendSvc, backendPort[0])
assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ing), "creating ingress")

time.Sleep(5 * time.Second)

msg401 := s.NewAPISIXClient().
POST("/anything").
WithHeader("Host", "httpbin.org").
Expect().
Status(http.StatusUnauthorized).
Body().
Raw()
assert.Contains(ginkgo.GinkgoT(), msg401, "no csrf token in headers")

resp := s.NewAPISIXClient().
GET("/anything").
WithHeader("Host", "httpbin.org").
Expect().
Status(http.StatusOK)
resp.Header("Set-Cookie").NotEmpty()

cookie := resp.Cookie("apisix-csrf-token")
token := cookie.Value().Raw()

_ = s.NewAPISIXClient().
POST("/anything").
WithHeader("Host", "httpbin.org").
WithHeader("apisix-csrf-token", token).
WithCookie("apisix-csrf-token", token).
Expect().
Status(http.StatusOK)

})

ginkgo.It("enable csrf in ingress networking/v1beta1", func() {
backendSvc, backendPort := s.DefaultHTTPBackend()
ing := fmt.Sprintf(`
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: apisix
k8s.apisix.apache.org/enable-csrf: "true"
k8s.apisix.apache.org/csrf-key: "foo-key"
name: ingress-v1beta1
spec:
rules:
- host: httpbin.org
http:
paths:
- path: /*
pathType: Prefix
backend:
serviceName: %s
servicePort: %d
`, backendSvc, backendPort[0])
assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ing), "creating ingress")

time.Sleep(5 * time.Second)

msg401 := s.NewAPISIXClient().
POST("/anything").
WithHeader("Host", "httpbin.org").
Expect().
Status(http.StatusUnauthorized).
Body().
Raw()
assert.Contains(ginkgo.GinkgoT(), msg401, "no csrf token in headers")

resp := s.NewAPISIXClient().
GET("/anything").
WithHeader("Host", "httpbin.org").
Expect().
Status(http.StatusOK)
resp.Header("Set-Cookie").NotEmpty()

cookie := resp.Cookie("apisix-csrf-token")
token := cookie.Value().Raw()

_ = s.NewAPISIXClient().
POST("/anything").
WithHeader("Host", "httpbin.org").
WithHeader("apisix-csrf-token", token).
WithCookie("apisix-csrf-token", token).
Expect().
Status(http.StatusOK)
})

ginkgo.It("enable csrf in ingress extensions/v1beta1", func() {
backendSvc, backendPort := s.DefaultHTTPBackend()
ing := fmt.Sprintf(`
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: apisix
k8s.apisix.apache.org/enable-csrf: "true"
k8s.apisix.apache.org/csrf-key: "foo-key"
name: ingress-extensions-v1beta1
spec:
rules:
- host: httpbin.org
http:
paths:
- path: /*
pathType: Prefix
backend:
serviceName: %s
servicePort: %d
`, backendSvc, backendPort[0])
assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ing), "creating ingress")

time.Sleep(5 * time.Second)

msg401 := s.NewAPISIXClient().
POST("/anything").
WithHeader("Host", "httpbin.org").
Expect().
Status(http.StatusUnauthorized).
Body().
Raw()
assert.Contains(ginkgo.GinkgoT(), msg401, "no csrf token in headers")

resp := s.NewAPISIXClient().
GET("/anything").
WithHeader("Host", "httpbin.org").
Expect().
Status(http.StatusOK)
resp.Header("Set-Cookie").NotEmpty()

cookie := resp.Cookie("apisix-csrf-token")
token := cookie.Value().Raw()

_ = s.NewAPISIXClient().
POST("/anything").
WithHeader("Host", "httpbin.org").
WithHeader("apisix-csrf-token", token).
WithCookie("apisix-csrf-token", token).
Expect().
Status(http.StatusOK)
})
})

0 comments on commit 25daa6e

Please sign in to comment.