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

chore: drops Transaction.ProcessRequest into an own package. #296

Merged
merged 4 commits into from
Jul 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
71 changes: 71 additions & 0 deletions http/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright 2022 Juan Pablo Tosso
//
// 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 http

import (
"io"
"net/http"
"strconv"
"strings"

"github.com/corazawaf/coraza/v3"
"github.com/corazawaf/coraza/v3/types"
)

// ProcessRequest fills all transaction variables from an http.Request object
// Most implementations of Coraza will probably use http.Request objects
// so this will implement all phase 0, 1 and 2 variables
// Note: This function will stop after an interruption
// Note: Do not manually fill any request variables
func ProcessRequest(tx *coraza.Transaction, req *http.Request) (*types.Interruption, error) {
var client string
cport := 0
// IMPORTANT: Some http.Request.RemoteAddr implementations will not contain port or contain IPV6: [2001:db8::1]:8080
spl := strings.Split(req.RemoteAddr, ":")
if len(spl) > 1 {
client = strings.Join(spl[0:len(spl)-1], "")
cport, _ = strconv.Atoi(spl[len(spl)-1])
}
var in *types.Interruption
// There is no socket access in the request object so we don't know the server client or port
tx.ProcessConnection(client, cport, "", 0)
tx.ProcessURI(req.URL.String(), req.Method, req.Proto)
for k, vr := range req.Header {
for _, v := range vr {
tx.AddRequestHeader(k, v)
}
}
// Host will always be removed from req.Headers(), so we manually add it
if req.Host != "" {
tx.AddRequestHeader("Host", req.Host)
}

in = tx.ProcessRequestHeaders()
if in != nil {
return in, nil
}
if req.Body != nil {
_, err := io.Copy(tx.RequestBodyBuffer, req.Body)
if err != nil {
return tx.Interruption, err
}
reader, err := tx.RequestBodyBuffer.Reader()
if err != nil {
return tx.Interruption, err
}
req.Body = io.NopCloser(reader)
}
return tx.ProcessRequestBody()
}
116 changes: 116 additions & 0 deletions http/http_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright 2022 Juan Pablo Tosso
//
// 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 http

import (
"bufio"
"bytes"
"context"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"strings"
"testing"

"github.com/corazawaf/coraza/v3"
)

func TestRequestExtractionSuccess(t *testing.T) {
req, _ := http.NewRequest("POST", "https://www.coraza.io/test", strings.NewReader("test=456"))
waf := coraza.NewWaf()
tx := waf.NewTransaction(context.Background())
if _, err := ProcessRequest(tx, req); err != nil {
t.Fatal(err)
}
if tx.Variables.RequestMethod.String() != "POST" {
t.Fatal("failed to set request from request object")
}
if err := tx.Clean(); err != nil {
t.Fatal(err)
}
}

func TestProcessRequestMultipart(t *testing.T) {
req, _ := http.NewRequest("POST", "/some", nil)
if err := multipartRequest(req); err != nil {
t.Fatal(err)
}
tx := makeTransaction(t)
tx.RequestBodyAccess = true
if _, err := ProcessRequest(tx, req); err != nil {
t.Fatal(err)
}
if req.Body == nil {
t.Error("failed to process multipart request")
}
reader := bufio.NewReader(req.Body)
if _, err := reader.ReadString('\n'); err != nil {
t.Error("failed to read multipart request", err)
}
if err := tx.Clean(); err != nil {
t.Error(err)
}
}

func multipartRequest(req *http.Request) error {
var b bytes.Buffer
w := multipart.NewWriter(&b)
tempfile, err := os.CreateTemp("/tmp", "tmpfile*")
if err != nil {
return err
}
defer os.Remove(tempfile.Name())
for i := 0; i < 1024*5; i++ {
// this should create a 5mb file
if _, err := tempfile.Write([]byte(strings.Repeat("A", 1024))); err != nil {
return err
}
}
var fw io.Writer
if fw, err = w.CreateFormFile("fupload", tempfile.Name()); err != nil {
return err
}
if _, err := tempfile.Seek(0, 0); err != nil {
return err
}
if _, err = io.Copy(fw, tempfile); err != nil {
return err
}
req.Body = ioutil.NopCloser(&b)
req.Header.Set("Content-Type", w.FormDataContentType())
req.Method = "POST"
return nil
}

func makeTransaction(t *testing.T) *coraza.Transaction {
t.Helper()
tx := coraza.NewWaf().NewTransaction(context.Background())
tx.RequestBodyAccess = true
ht := []string{
"POST /testurl.php?id=123&b=456 HTTP/1.1",
"Host: www.test.com:80",
"Cookie: test=123",
"Content-Type: application/x-www-form-urlencoded",
"X-Test-Header: test456",
"Content-Length: 13",
"",
"testfield=456",
}
data := strings.Join(ht, "\r\n")
_, _ = tx.ParseRequestReader(strings.NewReader(data))
return tx
}
2 changes: 1 addition & 1 deletion rule.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// 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
// 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,
Expand Down
2 changes: 1 addition & 1 deletion seclang/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// 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
// 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,
Expand Down
3 changes: 2 additions & 1 deletion seclang/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"testing"

"github.com/corazawaf/coraza/v3"
txhttp "github.com/corazawaf/coraza/v3/http"
"github.com/corazawaf/coraza/v3/types"
)

Expand Down Expand Up @@ -394,7 +395,7 @@ func TestDirectiveSecAuditLog(t *testing.T) {
if err != nil {
t.Errorf("Description HTTP request parsing failed")
}
_, err = tx.ProcessRequest(req)
_, err = txhttp.ProcessRequest(tx, req)
if err != nil {
t.Errorf("Failed to load the HTTP request")
}
Expand Down
49 changes: 1 addition & 48 deletions transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// 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
// 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,
Expand All @@ -19,7 +19,6 @@ import (
"fmt"
"io"
"mime"
"net/http"
"net/url"
"path/filepath"
"strconv"
Expand Down Expand Up @@ -412,52 +411,6 @@ func (tx *Transaction) RemoveRuleByID(id int) {
tx.ruleRemoveByID = append(tx.ruleRemoveByID, id)
}

// ProcessRequest fills all transaction variables from an http.Request object
// Most implementations of Coraza will probably use http.Request objects
// so this will implement all phase 0, 1 and 2 variables
// Note: This function will stop after an interruption
// Note: Do not manually fill any request variables
func (tx *Transaction) ProcessRequest(req *http.Request) (*types.Interruption, error) {
var client string
cport := 0
// IMPORTANT: Some http.Request.RemoteAddr implementations will not contain port or contain IPV6: [2001:db8::1]:8080
spl := strings.Split(req.RemoteAddr, ":")
if len(spl) > 1 {
client = strings.Join(spl[0:len(spl)-1], "")
cport, _ = strconv.Atoi(spl[len(spl)-1])
}
var in *types.Interruption
// There is no socket access in the request object so we don't know the server client or port
tx.ProcessConnection(client, cport, "", 0)
tx.ProcessURI(req.URL.String(), req.Method, req.Proto)
for k, vr := range req.Header {
for _, v := range vr {
tx.AddRequestHeader(k, v)
}
}
// Host will always be removed from req.Headers(), so we manually add it
if req.Host != "" {
tx.AddRequestHeader("Host", req.Host)
}

in = tx.ProcessRequestHeaders()
if in != nil {
return in, nil
}
if req.Body != nil {
_, err := io.Copy(tx.RequestBodyBuffer, req.Body)
if err != nil {
return tx.Interruption, err
}
reader, err := tx.RequestBodyBuffer.Reader()
if err != nil {
return tx.Interruption, err
}
req.Body = io.NopCloser(reader)
}
return tx.ProcessRequestBody()
}

// ProcessConnection should be called at very beginning of a request process, it is
// expected to be executed prior to the virtual host resolution, when the
// connection arrives on the server.
Expand Down
74 changes: 0 additions & 74 deletions transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,8 @@
package coraza

import (
"bufio"
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
"regexp"
"runtime/debug"
"strconv"
Expand Down Expand Up @@ -227,21 +220,6 @@ func TestAuditLogFields(t *testing.T) {
}
}

func TestRequestStruct(t *testing.T) {
req, _ := http.NewRequest("POST", "https://www.coraza.io/test", strings.NewReader("test=456"))
waf := NewWaf()
tx := waf.NewTransaction(context.Background())
if _, err := tx.ProcessRequest(req); err != nil {
t.Error(err)
}
if tx.Variables.RequestMethod.String() != "POST" {
t.Error("failed to set request from request object")
}
if err := tx.Clean(); err != nil {
t.Error(err)
}
}

func TestResetCapture(t *testing.T) {
tx := makeTransaction()
tx.Capture = true
Expand Down Expand Up @@ -413,28 +391,6 @@ func TestAuditLogMessages(t *testing.T) {

}

func TestProcessRequestMultipart(t *testing.T) {
req, _ := http.NewRequest("POST", "/some", nil)
if err := multipartRequest(req); err != nil {
t.Fatal(err)
}
tx := makeTransaction()
tx.RequestBodyAccess = true
if _, err := tx.ProcessRequest(req); err != nil {
t.Error(err)
}
if req.Body == nil {
t.Error("failed to process multipart request")
}
reader := bufio.NewReader(req.Body)
if _, err := reader.ReadString('\n'); err != nil {
t.Error("failed to read multipart request", err)
}
if err := tx.Clean(); err != nil {
t.Error(err)
}
}

func TestTransactionSyncPool(t *testing.T) {
waf := NewWaf()
tx := waf.NewTransaction(context.Background())
Expand Down Expand Up @@ -577,36 +533,6 @@ func TestTXProcessURI(t *testing.T) {
}
}

func multipartRequest(req *http.Request) error {
var b bytes.Buffer
w := multipart.NewWriter(&b)
tempfile, err := os.CreateTemp("/tmp", "tmpfile*")
if err != nil {
return err
}
defer os.Remove(tempfile.Name())
for i := 0; i < 1024*5; i++ {
// this should create a 5mb file
if _, err := tempfile.Write([]byte(strings.Repeat("A", 1024))); err != nil {
return err
}
}
var fw io.Writer
if fw, err = w.CreateFormFile("fupload", tempfile.Name()); err != nil {
return err
}
if _, err := tempfile.Seek(0, 0); err != nil {
return err
}
if _, err = io.Copy(fw, tempfile); err != nil {
return err
}
req.Body = ioutil.NopCloser(&b)
req.Header.Set("Content-Type", w.FormDataContentType())
req.Method = "POST"
return nil
}

func BenchmarkTransactionCreation(b *testing.B) {
waf := NewWaf()
for i := 0; i < b.N; i++ {
Expand Down
Loading