cubefs
1// Copyright 2022 The CubeFS Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12// implied. See the License for the specific language governing
13// permissions and limitations under the License.
14
15package auditlog16
17import (18"bytes"19"encoding/json"20"errors"21"io"22"net"23"net/http"24
25"github.com/cubefs/cubefs/blobstore/common/rpc"26"github.com/cubefs/cubefs/blobstore/common/rpc/auth"27)
28
29const maxSeekableBodyLength = 1 << 1030
31var DefaultRequestHeaderKeys = []string{32"User-Agent",33"Range",34"Refer",35"Referer",36"Origin",37"Content-Length",38"Accept-Encoding",39"If-None-Match",40"If-Modified-Since",41
42"Cdn",43"Cdn-Src-Ip",44"Cdn-Scheme",45
46"X-Real-Ip",47"X-Forwarded-For",48"X-Scheme",49"X-Remote-Ip",50"X-From-Cdn",51"X-Id",52"X-From-Proxy-Getter",53"X-From-Fsrcproxy",54"X-Upload-Encoding",55"X-Src",56
57auth.TokenHeaderKey,58}
59
60type DecodedReq struct {61Path string62Header M
63Params []byte64}
65
66type ReadCloser struct {67io.Reader68io.Closer69}
70
71type M map[string]interface{}72
73func (m M) Encode() []byte {74if len(m) > 0 {75ret, _ := json.Marshal(m)76return ret77}78return nil79}
80
81type defaultDecoder struct{}82
83func (d *defaultDecoder) DecodeReq(req *http.Request) *DecodedReq {84header := req.Header85clientIP, _, err := net.SplitHostPort(req.RemoteAddr)86if err != nil {87clientIP = req.RemoteAddr88}89decodedReq := &DecodedReq{90Header: M{"IP": clientIP, "Host": req.Host},91Path: req.URL.Path,92}93
94// decode header information95if req.URL.RawQuery != "" {96decodedReq.Header["RawQuery"] = req.URL.RawQuery97}98cm := req.Header.Get("Content-MD5")99if cm != "" {100decodedReq.Header["Content-MD5"] = cm101}102
103for _, key := range DefaultRequestHeaderKeys {104if v, ok := header[key]; ok {105decodedReq.Header[key] = v[0]106}107}108// crc check header109if encoded, ok := req.Header[rpc.HeaderCrcEncoded]; ok {110decodedReq.Header[rpc.HeaderCrcEncoded] = encoded[0]111}112
113// decode request params, including:114// 1. form-urlencoded115// 2. json116contentType, ok := req.Header["Content-Type"]117if ok {118decodedReq.Header["Content-Type"] = contentType[0]119}120if ok {121switch contentType[0] {122case rpc.MIMEPOSTForm:123buff, err := d.readFull(req)124if err == nil {125req.Body = ReadCloser{bytes.NewReader(buff), req.Body}126req.ParseForm()127params := make(M)128for k, v := range req.Form {129if len(v) == 1 {130params[k] = v[0]131} else {132params[k] = v133}134}135decodedReq.Params = params.Encode()136}137case rpc.MIMEJSON:138buff, err := d.readFull(req)139if err == nil {140req.Body = ReadCloser{bytes.NewReader(buff), req.Body}141// check if request body is valid or not, and do not print invalid request body in audit log142if json.Valid(buff) {143decodedReq.Params = compactNewline(buff)144}145}146default:147}148}149return decodedReq150}
151
152func (d *defaultDecoder) readFull(req *http.Request) ([]byte, error) {153if req.ContentLength > maxSeekableBodyLength {154return nil, errors.New("too large body for form or json")155}156
157buff := make([]byte, req.ContentLength)158_, err := io.ReadFull(req.Body, buff)159if err != nil {160return nil, err161}162return buff, nil163}
164
165// compactNewline json compact if buffer has '\n'(0x0a).
166func compactNewline(src []byte) []byte {167if bytes.IndexByte(src, 0x0a) < 0 {168return src169}170newBuff := bytes.NewBuffer(make([]byte, 0, len(src)))171if err := json.Compact(newBuff, src); err == nil {172return newBuff.Bytes()173}174return src175}
176