Repository: corazawaf/coraza (Go)

Issue:
Validate audit log parts
## Summary

Right now we don't validate audit log parts, instead we just accept what is coming. Types package should not only declare them but also validate them.

For example in https://github.com/corazawaf/coraza/blob/c68f247ffac02b2706053643b80453de7d01de31/internal/seclang/directives.go#L753 and https://github.com/corazawaf/coraza/blob/c41dbfc3563e19e12d2c61bf1335c034a6870069/actions/ctl.go#L114-L115 do the casting directly without validating the actual values.


Discussion:
if the Opts contains invalid AuditLogPart,if AuditLogParts should be null and return error or just remove the invalid AuditLogPart
It should return an error to fail the parsing. Thanks for asking!

On Tue, 25 Apr 2023, 06:09 Hayake, ***@***.***> wrote:

> if the Opts contains invalid AuditLogPart,if AuditLogParts should be null
> and return error or just remove the invalid AuditLogPart
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/corazawaf/coraza/issues/678#issuecomment-1521129545>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAXOYASEY3O3JW3LH5ZYN33XC5FAPANCNFSM6AAAAAAVIOB42A>
> .
> You are receiving this because you authored the thread.Message ID:
> ***@***.***>
>

After looking at docs (and actually the code too) part `A` is always required (see https://coraza.io/docs/seclang/directives/#secauditlogparts). Do you mind taking a stab @Hayak3 ?
> After looking at docs (and actually the code too) part `A` is always required (see https://coraza.io/docs/seclang/directives/#secauditlogparts). Do you mind taking a stab @Hayak3 ?

I will read the doc and fix it soon
I also find end with `Z` is mandatory,is it necessary? @jcchavezs 
Totally and we should also condition this assignment only when `Z` is
reached
https://github.com/corazawaf/coraza/blob/v3/dev/internal/corazawaf/transaction.go#L1414.
Could you please also take care of that?

On Sat, May 27, 2023 at 8:51 AM Hayake ***@***.***> wrote:

> I also find end with Z is mandatory,is it necessary? @jcchavezs
> <https://github.com/jcchavezs>
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/corazawaf/coraza/issues/678#issuecomment-1565237997>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AAXOYAQ6W7YMFQOHK3TXGKTXIGP77ANCNFSM6AAAAAAVIOB42A>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>




Patch:
diff --git a/internal/actions/ctl.go b/internal/actions/ctl.go
index a375afd61..c062d2066 100644
--- a/internal/actions/ctl.go
+++ b/internal/actions/ctl.go
@@ -111,8 +111,16 @@ func (a *ctlFn) Evaluate(_ plugintypes.RuleMetadata, txS plugintypes.Transaction
 		}
 		tx.AuditEngine = ae
 	case ctlAuditLogParts:
-		// TODO lets switch it to a string
-		tx.AuditLogParts = types.AuditLogParts(a.value)
+		AuditLogParts, err := types.ParseAuditLogParts(a.value)
+		if err != nil {
+			tx.DebugLogger().Error().
+				Str("ctl", "AuditLogParts").
+				Str("value", a.value).
+				Err(err).
+				Msg("Invalid audit log part")
+			return
+		}
+		tx.AuditLogParts = AuditLogParts
 	case ctlForceRequestBodyVariable:
 		val, ok := parseOnOff(a.value)
 		if !ok {
diff --git a/internal/seclang/directives.go b/internal/seclang/directives.go
index 189b6b97f..e1c8ff26f 100644
--- a/internal/seclang/directives.go
+++ b/internal/seclang/directives.go
@@ -779,8 +779,9 @@ func directiveSecAuditLogParts(options *DirectiveOptions) error {
 		return errEmptyOptions
 	}
 
-	options.WAF.AuditLogParts = types.AuditLogParts(options.Opts)
-	return nil
+	var err error
+	options.WAF.AuditLogParts, err = types.ParseAuditLogParts(options.Opts)
+	return err
 }
 
 // Description: Configures the audit logging engine.
diff --git a/types/waf.go b/types/waf.go
index b54e76e83..9960cff90 100644
--- a/types/waf.go
+++ b/types/waf.go
@@ -106,6 +106,30 @@ type AuditLogPart byte
 // Z: Final boundary, signifies the end of the entry (mandatory).
 type AuditLogParts []AuditLogPart
 
+var validOpts = map[AuditLogPart]struct{}{
+	AuditLogPartAuditLogHeader:              {},
+	AuditLogPartRequestHeaders:              {},
+	AuditLogPartRequestBody:                 {},
+	AuditLogPartIntermediaryResponseHeaders: {},
+	AuditLogPartIntermediaryResponseBody:    {},
+	AuditLogPartResponseHeaders:             {},
+	AuditLogPartResponseBody:                {},
+	AuditLogPartAuditLogTrailer:             {},
+	AuditLogPartRequestBodyAlternative:      {},
+	AuditLogPartUploadedFiles:               {},
+	AuditLogPartRulesMatched:                {},
+	AuditLogPartFinalBoundary:               {},
+}
+
+func ParseAuditLogParts(opts string) (AuditLogParts, error) {
+	for _, opt := range opts {
+		if _, ok := validOpts[AuditLogPart(opt)]; !ok {
+			return AuditLogParts(""), fmt.Errorf("invalid audit log part: %s", opts)
+		}
+	}
+	return AuditLogParts(opts), nil
+}
+
 const (
 	// AuditLogPartAuditLogHeader is the mandatory header part
 	AuditLogPartAuditLogHeader AuditLogPart = 'A'


Test patch:
diff --git a/internal/corazawaf/transaction_test.go b/internal/corazawaf/transaction_test.go
index 2118d4a40..032e745d1 100644
--- a/internal/corazawaf/transaction_test.go
+++ b/internal/corazawaf/transaction_test.go
@@ -429,6 +429,29 @@ func TestAuditLog(t *testing.T) {
 	}
 }
 
+func TestParseAuditLog(t *testing.T) {
+	AuditLogParts, err := types.ParseAuditLogParts("ABCDEFGHIJK")
+	if err != nil {
+		t.Error("unexpected audit log parts")
+	}
+	expected := types.AuditLogParts("ABCDEFGHIJK")
+	if len(AuditLogParts) != len(expected) {
+		t.Error("AuditLogParts has different length than expected")
+
+	}
+	for i := 0; i < len(AuditLogParts); i++ {
+		if AuditLogParts[i] != expected[i] {
+			t.Errorf("Byte at position %d differs", i)
+		}
+	}
+}
+func TestInvalidAuditLog(t *testing.T) {
+	AuditLogParts, err := types.ParseAuditLogParts("ABCDEFGHIJKLMN")
+	if err == nil || len(AuditLogParts) != 0 {
+		t.Error("AuditLogParts should fail of invalid part")
+	}
+}
+
 var responseBodyWriters = map[string]func(tx *Transaction, body string) (*types.Interruption, int, error){
 	"WriteResponsequestBody": func(tx *Transaction, body string) (*types.Interruption, int, error) {
 		return tx.WriteResponseBody([]byte(body))