Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

api: add HTTP audit middleware #4537

Merged
merged 96 commits into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from 95 commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
3a45237
close #4494
CabinfeverB Dec 23, 2021
725345f
close #4494: add priority comment
CabinfeverB Dec 27, 2021
f30bb7b
Merge branch 'master' into http_source_ip
CabinfeverB Dec 29, 2021
198fb0b
add request info middleware
CabinfeverB Dec 30, 2021
a80af26
close #4494
CabinfeverB Dec 30, 2021
d9ed82f
Merge remote-tracking branch 'origin/http_source_ip' into http_source_ip
CabinfeverB Dec 30, 2021
5af1d1c
change service label getter and setter
CabinfeverB Dec 30, 2021
3041f1f
Merge branch 'master' into service_middleware
CabinfeverB Dec 30, 2021
232f798
add lock
CabinfeverB Dec 30, 2021
3af3e83
add audit middleware
CabinfeverB Dec 31, 2021
bd9553b
add audit middleware
CabinfeverB Jan 4, 2022
01f88c7
add audit middleware
CabinfeverB Jan 4, 2022
a4bc982
merge service middleware
CabinfeverB Jan 4, 2022
e25cc68
fix
CabinfeverB Jan 4, 2022
a313450
Merge branch 'http_source_ip' into service_middleware
CabinfeverB Jan 4, 2022
5ba83fe
#4538
CabinfeverB Jan 4, 2022
40ef486
fix return
CabinfeverB Jan 4, 2022
e3ddcd9
fix
CabinfeverB Jan 4, 2022
edd63ae
fix check
CabinfeverB Jan 4, 2022
271e58f
fix check
CabinfeverB Jan 4, 2022
2540d56
change service label placement
CabinfeverB Jan 5, 2022
ea08f30
merge master
CabinfeverB Jan 5, 2022
4800b61
merge master
CabinfeverB Jan 5, 2022
9a2eaec
Merge branch 'master' into service_middleware
nolouch Jan 12, 2022
894a0fe
add const service label and could be dynamically turned on and off s…
CabinfeverB Jan 13, 2022
befd6d3
Merge branch 'service_middleware' of github.com:CabinfeverB/pd into s…
CabinfeverB Jan 13, 2022
4a2eb02
fix statics check
CabinfeverB Jan 13, 2022
0981c7b
fix statics check
CabinfeverB Jan 13, 2022
4fdb3d4
fix statics check
CabinfeverB Jan 13, 2022
8f9d6a7
fix statics check
CabinfeverB Jan 13, 2022
b68a2ae
fix function name
CabinfeverB Jan 14, 2022
932ba4d
add benchmark test
CabinfeverB Jan 14, 2022
2652c93
fix r.Body nil panic
CabinfeverB Jan 14, 2022
2eff957
Merge branch 'service_middleware' into service_middleware_benchmark
CabinfeverB Jan 14, 2022
be4a3f3
add benchmark test
CabinfeverB Jan 14, 2022
1b6ab1c
change export
CabinfeverB Jan 14, 2022
dbaf902
change export
CabinfeverB Jan 14, 2022
e778858
Merge branch 'service_middleware_benchmark' into service_middleware
CabinfeverB Jan 14, 2022
2e8966f
fix statics check
CabinfeverB Jan 17, 2022
a112ed9
fix typo problem
CabinfeverB Jan 17, 2022
f83681b
change service label method
CabinfeverB Jan 17, 2022
3366bc7
merge master
CabinfeverB Jan 17, 2022
aa11cee
fix statics
CabinfeverB Jan 17, 2022
42d8b60
fix router
CabinfeverB Jan 17, 2022
6852387
fix router
CabinfeverB Jan 17, 2022
e186524
add http proto
CabinfeverB Jan 18, 2022
71ce331
add http proto
CabinfeverB Jan 18, 2022
743ed13
merge new service middleware
CabinfeverB Jan 18, 2022
69af07e
change some var and func name
CabinfeverB Jan 19, 2022
97025d4
change default value
CabinfeverB Jan 19, 2022
f455d58
Merge branch 'service_middleware' into audit_middleware
CabinfeverB Jan 19, 2022
d394ab9
audit middleware
CabinfeverB Jan 19, 2022
3c40ef8
use middleware func shortname
CabinfeverB Jan 19, 2022
f9385da
Merge branch 'service_middleware' into audit_middleware
CabinfeverB Jan 19, 2022
6d1b147
fix comment
CabinfeverB Jan 19, 2022
c308901
fix comment
CabinfeverB Jan 19, 2022
4216358
Merge branch 'service_middleware' into audit_middleware
CabinfeverB Jan 19, 2022
27f9a55
add switch
CabinfeverB Jan 19, 2022
b809792
fix typo
CabinfeverB Jan 19, 2022
abb2505
fix typo
CabinfeverB Jan 19, 2022
cffadda
add matcher test
CabinfeverB Jan 19, 2022
cb3d40f
add comment
CabinfeverB Jan 20, 2022
8659fc6
change const to iota
CabinfeverB Jan 20, 2022
9f24848
Merge branch 'service_middleware' into audit_middleware
CabinfeverB Jan 20, 2022
fc7c44a
change comment
CabinfeverB Jan 20, 2022
43beb8b
Merge branch 'master' into service_middleware
CabinfeverB Jan 20, 2022
64998cd
Merge branch 'master' into audit_middleware
CabinfeverB Jan 20, 2022
cc577b3
change register method
CabinfeverB Jan 20, 2022
d5460bd
merge service middleware
CabinfeverB Jan 20, 2022
6a48e2e
fix
CabinfeverB Jan 20, 2022
193b126
Merge branch 'service_middleware' into audit_middleware
CabinfeverB Jan 20, 2022
dc52e3d
fix
CabinfeverB Jan 20, 2022
f8c0098
merge service middleware
CabinfeverB Jan 20, 2022
b1f8d5b
merge service middleware
CabinfeverB Jan 20, 2022
23aafd9
add opt
CabinfeverB Jan 20, 2022
7b4f6b9
Merge branch 'master' into service_middleware
CabinfeverB Jan 20, 2022
0ca8c81
Merge branch 'master' into audit_middleware
CabinfeverB Jan 20, 2022
83c4ca9
for test
CabinfeverB Jan 20, 2022
b7ec272
for test
CabinfeverB Jan 20, 2022
3975382
fix race
CabinfeverB Jan 20, 2022
ea80ae4
Merge branch 'audit_middleware' of github.com:CabinfeverB/pd into aud…
CabinfeverB Jan 20, 2022
aaaa93e
merge service middleware
CabinfeverB Jan 20, 2022
924f302
fix race
CabinfeverB Jan 20, 2022
c726641
Merge branch 'service_middleware' into audit_middleware
CabinfeverB Jan 20, 2022
62d31ff
merge master
CabinfeverB Jan 21, 2022
f387831
merge master
CabinfeverB Jan 21, 2022
985f448
add backend Sequence
CabinfeverB Jan 21, 2022
cd8f7b7
fix router
CabinfeverB Jan 21, 2022
8b25afe
remove service middleware switch
CabinfeverB Jan 24, 2022
186fa9e
fix
CabinfeverB Jan 24, 2022
577480a
fix test
CabinfeverB Jan 24, 2022
6217a8e
log error when audit error
CabinfeverB Jan 25, 2022
e958793
log error when audit error
CabinfeverB Jan 25, 2022
36be023
change function name
CabinfeverB Jan 27, 2022
1b170fe
add comment
CabinfeverB Jan 27, 2022
436892f
Merge branch 'master' into audit_middleware
ti-chi-bot Feb 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions pkg/audit/audit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2022 TiKV Project Authors.
//
// Licensed 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 audit

import (
"net/http"
)

// BackendLabels is used to store some audit backend labels.
type BackendLabels struct {
Labels []string
}

// LabelMatcher is used to help backend implement audit.Backend
type LabelMatcher struct {
backendLabel string
}

// Match is used to check whether backendLabel is in the labels
func (m *LabelMatcher) Match(labels *BackendLabels) bool {
for _, item := range labels.Labels {
if m.backendLabel == item {
return true
}
}
return false
}

// Sequence is used to help backend implement audit.Backend
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does the Sequence init?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creates and inits Sequence when creating Backend

type Sequence struct {
before bool
}

// ProcessBeforeHandler is used to help backend implement audit.Backend
func (s *Sequence) ProcessBeforeHandler() bool {
return s.before
}

// Backend defines what function audit backend should hold
type Backend interface {
// ProcessHTTPRequest is used to perform HTTP audit process
ProcessHTTPRequest(req *http.Request) bool
// Match is used to determine if the backend matches
Match(*BackendLabels) bool
// ProcessBeforeHandler is used to identify whether this backend should execute before handler
ProcessBeforeHandler() bool
}
39 changes: 39 additions & 0 deletions pkg/audit/audit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2022 TiKV Project Authors.
//
// Licensed 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 audit

import (
"testing"

. "github.com/pingcap/check"
)

func Test(t *testing.T) {
TestingT(t)
}

var _ = Suite(&testAuditSuite{})

type testAuditSuite struct {
}

func (s *testAuditSuite) TestLabelMatcher(c *C) {
matcher := &LabelMatcher{"testSuccess"}
labels1 := &BackendLabels{Labels: []string{"testFail", "testSuccess"}}
c.Assert(matcher.Match(labels1), Equals, true)

labels2 := &BackendLabels{Labels: []string{"testFail"}}
c.Assert(matcher.Match(labels2), Equals, false)
}
15 changes: 14 additions & 1 deletion pkg/requestutil/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ import (
type key int

const (
// requestInfoKey is the context key for the request compoenent.
// requestInfoKey is the context key for the request info.
requestInfoKey key = iota
// endTimeKey is the context key for the end time.
endTimeKey
)

// WithRequestInfo returns a copy of parent in which the request info value is set
Expand All @@ -36,3 +38,14 @@ func RequestInfoFrom(ctx context.Context) (RequestInfo, bool) {
requestInfo, ok := ctx.Value(requestInfoKey).(RequestInfo)
return requestInfo, ok
}

// WithEndTime returns a copy of parent in which the end time value is set
func WithEndTime(parent context.Context, endTime int64) context.Context {
return context.WithValue(parent, endTimeKey, endTime)
}

// EndTimeFrom returns the value of the excution info key on the ctx
func EndTimeFrom(ctx context.Context) (int64, bool) {
info, ok := ctx.Value(endTimeKey).(int64)
return info, ok
}
30 changes: 22 additions & 8 deletions pkg/requestutil/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package requestutil
import (
"context"
"testing"
"time"

. "github.com/pingcap/check"
)
Expand All @@ -34,15 +35,16 @@ func (s *testRequestContextSuite) TestRequestInfo(c *C) {
ctx := context.Background()
_, ok := RequestInfoFrom(ctx)
c.Assert(ok, Equals, false)
timeNow := time.Now().Unix()
ctx = WithRequestInfo(ctx,
RequestInfo{
ServiceLabel: "test label",
Method: "POST",
Component: "pdctl",
IP: "localhost",
URLParam: "{\"id\"=1}",
BodyParam: "{\"state\"=\"Up\"}",
TimeStamp: "2022",
ServiceLabel: "test label",
Method: "POST",
Component: "pdctl",
IP: "localhost",
URLParam: "{\"id\"=1}",
BodyParam: "{\"state\"=\"Up\"}",
StartTimeStamp: timeNow,
})
result, ok := RequestInfoFrom(ctx)
c.Assert(result, NotNil)
Expand All @@ -53,5 +55,17 @@ func (s *testRequestContextSuite) TestRequestInfo(c *C) {
c.Assert(result.IP, Equals, "localhost")
c.Assert(result.URLParam, Equals, "{\"id\"=1}")
c.Assert(result.BodyParam, Equals, "{\"state\"=\"Up\"}")
c.Assert(result.TimeStamp, Equals, "2022")
c.Assert(result.StartTimeStamp, Equals, timeNow)
}

func (s *testRequestContextSuite) TestEndTime(c *C) {
ctx := context.Background()
_, ok := EndTimeFrom(ctx)
c.Assert(ok, Equals, false)
timeNow := time.Now().Unix()
ctx = WithEndTime(ctx, timeNow)
result, ok := EndTimeFrom(ctx)
c.Assert(result, NotNil)
c.Assert(ok, Equals, true)
c.Assert(result, Equals, timeNow)
}
28 changes: 14 additions & 14 deletions pkg/requestutil/request_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,25 @@ import (

// RequestInfo holds service information from http.Request
type RequestInfo struct {
ServiceLabel string
Method string
Component string
IP string
TimeStamp string
URLParam string
BodyParam string
ServiceLabel string
Method string
Component string
IP string
URLParam string
BodyParam string
StartTimeStamp int64
}

// GetRequestInfo returns request info needed from http.Request
func GetRequestInfo(r *http.Request) RequestInfo {
return RequestInfo{
ServiceLabel: apiutil.GetRouteName(r),
Method: fmt.Sprintf("%s/%s:%s", r.Proto, r.Method, r.URL.Path),
Component: apiutil.GetComponentNameOnHTTP(r),
IP: apiutil.GetIPAddrFromHTTPRequest(r),
TimeStamp: time.Now().Local().String(),
URLParam: getURLParam(r),
BodyParam: getBodyParam(r),
ServiceLabel: apiutil.GetRouteName(r),
Method: fmt.Sprintf("%s/%s:%s", r.Proto, r.Method, r.URL.Path),
Component: apiutil.GetComponentNameOnHTTP(r),
IP: apiutil.GetIPAddrFromHTTPRequest(r),
URLParam: getURLParam(r),
BodyParam: getBodyParam(r),
StartTimeStamp: time.Now().Unix(),
}
}

Expand Down
12 changes: 6 additions & 6 deletions server/api/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,19 +140,19 @@ func (h *adminHandler) UpdateWaitAsyncTime(w http.ResponseWriter, r *http.Reques
}

// @Tags admin
// @Summary switch Service Middlewares including ServiceInfo, Audit and rate limit
// @Summary switch audit middleware
// @Param enable query string true "enable" Enums(true, false)
// @Produce json
// @Success 200 {string} string "Switching Service middleware is successful."
// @Success 200 {string} string "Switching audit middleware is successful."
// @Failure 400 {string} string "The input is invalid."
// @Router /admin/service-middleware [POST]
func (h *adminHandler) HanldeServiceMiddlewareSwitch(w http.ResponseWriter, r *http.Request) {
// @Router /admin/audit-middleware [POST]
func (h *adminHandler) HanldeAuditMiddlewareSwitch(w http.ResponseWriter, r *http.Request) {
enableStr := r.URL.Query().Get("enable")
enable, err := strconv.ParseBool(enableStr)
if err != nil {
h.rd.JSON(w, http.StatusBadRequest, "The input is invalid.")
return
}
h.svr.SetServiceMiddleware(enable)
h.rd.JSON(w, http.StatusOK, "Switching Service middleware is successful.")
h.svr.SetAuditMiddleware(enable)
h.rd.JSON(w, http.StatusOK, "Switching audit middleware is successful.")
}
21 changes: 11 additions & 10 deletions server/api/admin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,25 +193,26 @@ func (s *testServiceSuite) TearDownSuite(c *C) {
s.cleanup()
}

func (s *testServiceSuite) TestSwitchServiceMiddleware(c *C) {
urlPrefix := fmt.Sprintf("%s%s/api/v1/admin/service-middleware", s.svr.GetAddr(), apiPrefix)
disableURL := fmt.Sprintf("%s?enable=false", urlPrefix)
err := postJSON(testDialClient, disableURL, nil,
func (s *testServiceSuite) TestSwitchAuditMiddleware(c *C) {
urlPrefix := fmt.Sprintf("%s%s/api/v1/admin/audit-middleware", s.svr.GetAddr(), apiPrefix)

enableURL := fmt.Sprintf("%s?enable=true", urlPrefix)
err := postJSON(testDialClient, enableURL, nil,
func(res []byte, code int) {
c.Assert(string(res), Equals, "\"Switching Service middleware is successful.\"\n")
c.Assert(string(res), Equals, "\"Switching audit middleware is successful.\"\n")
c.Assert(code, Equals, http.StatusOK)
})

c.Assert(err, IsNil)
c.Assert(s.svr.IsServiceMiddlewareEnabled(), Equals, false)
c.Assert(s.svr.IsAuditMiddlewareEnabled(), Equals, true)

enableURL := fmt.Sprintf("%s?enable=true", urlPrefix)
err = postJSON(testDialClient, enableURL, nil,
disableURL := fmt.Sprintf("%s?enable=false", urlPrefix)
err = postJSON(testDialClient, disableURL, nil,
func(res []byte, code int) {
c.Assert(string(res), Equals, "\"Switching Service middleware is successful.\"\n")
c.Assert(string(res), Equals, "\"Switching audit middleware is successful.\"\n")
c.Assert(code, Equals, http.StatusOK)
})

c.Assert(err, IsNil)
c.Assert(s.svr.IsServiceMiddlewareEnabled(), Equals, true)
c.Assert(s.svr.IsAuditMiddlewareEnabled(), Equals, false)
}
61 changes: 60 additions & 1 deletion server/api/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,12 @@ package api
import (
"context"
"net/http"
"strings"
"time"

"github.com/pingcap/failpoint"
"github.com/pingcap/log"
"github.com/tikv/pd/pkg/audit"
"github.com/tikv/pd/pkg/errs"
"github.com/tikv/pd/pkg/requestutil"
"github.com/tikv/pd/server"
Expand All @@ -37,7 +41,7 @@ func newRequestInfoMiddleware(s *server.Server) negroni.Handler {
}

func (rm *requestInfoMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if !rm.svr.IsServiceMiddlewareEnabled() {
if !rm.svr.IsAuditMiddlewareEnabled() {
next(w, r)
return
}
Expand Down Expand Up @@ -86,3 +90,58 @@ type clusterCtxKey struct{}
func getCluster(r *http.Request) *cluster.RaftCluster {
return r.Context().Value(clusterCtxKey{}).(*cluster.RaftCluster)
}

type auditMiddleware struct {
svr *server.Server
}

func newAuditMiddleware(s *server.Server) negroni.Handler {
return &auditMiddleware{svr: s}
}

// ServeHTTP is used to implememt negroni.Handler for auditMiddleware
func (s *auditMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
if !s.svr.IsAuditMiddlewareEnabled() {
next(w, r)
return
}

requestInfo, ok := requestutil.RequestInfoFrom(r.Context())
if !ok {
log.Error("failed to get request info when auditing")
next(w, r)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to return?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. If return here, what response do we write?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add error log if can't get requestInfo

}

labels := s.svr.GetServiceAuditBackendLabels(requestInfo.ServiceLabel)
if labels == nil {
next(w, r)
return
}

failpoint.Inject("addAuditMiddleware", func() {
w.Header().Add("audit-label", strings.Join(labels.Labels, ","))
})

beforeNextBackends := make([]audit.Backend, 0)
afterNextBackends := make([]audit.Backend, 0)
for _, backend := range s.svr.GetAuditBackend() {
if backend.Match(labels) {
if backend.ProcessBeforeHandler() {
beforeNextBackends = append(beforeNextBackends, backend)
} else {
afterNextBackends = append(afterNextBackends, backend)
}
}
}
for _, backend := range beforeNextBackends {
backend.ProcessHTTPRequest(r)
}
Comment on lines +125 to +138
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
beforeNextBackends := make([]audit.Backend, 0)
afterNextBackends := make([]audit.Backend, 0)
for _, backend := range s.svr.GetAuditBackend() {
if backend.Match(labels) {
if backend.ProcessBeforeHandler() {
beforeNextBackends = append(beforeNextBackends, backend)
} else {
afterNextBackends = append(afterNextBackends, backend)
}
}
}
for _, backend := range beforeNextBackends {
backend.ProcessHTTPRequest(r)
}
for _, backend := range s.svr.GetAuditBackend() {
if backend.Match(labels) && backend.ProcessBeforeHandler() {
backend.ProcessHTTPRequest(r)
}
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backend can be divided into two types: before the service Handle and after the service Handle


next(w, r)

endTime := time.Now().Unix()
r = r.WithContext(requestutil.WithEndTime(r.Context(), endTime))
for _, backend := range afterNextBackends {
backend.ProcessHTTPRequest(r)
}
}
Loading