From b64f4d12301a233c132b584840f29772f0aead6c Mon Sep 17 00:00:00 2001 From: ejthurgo Date: Fri, 14 Jun 2019 11:47:28 +0100 Subject: [PATCH 1/2] Allow hostname or IP address Currently the `Address` defined in the TOML conffig is used for both `Address` and `IPV4` fields in the API. Validation on the `IPV4` value prevents this from being a hostname. However, the `Address` can be either hostname, IPv4 or IPv6 address and is automatically parsed by the API. --- waflyctl.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/waflyctl.go b/waflyctl.go index 9afa91a..e847a16 100644 --- a/waflyctl.go +++ b/waflyctl.go @@ -394,7 +394,6 @@ func fastlyLogging(client fastly.Client, serviceID string, config TOMLConfig, ve Address: config.Weblog.Address, Port: config.Weblog.Port, UseTLS: fastly.CBool(true), - IPV4: config.Weblog.Address, TLSCACert: config.Weblog.Tlscacert, TLSHostname: config.Weblog.Tlshostname, Format: config.Weblog.Format, @@ -416,7 +415,6 @@ func fastlyLogging(client fastly.Client, serviceID string, config TOMLConfig, ve Address: config.Waflog.Address, Port: config.Waflog.Port, UseTLS: fastly.CBool(true), - IPV4: config.Waflog.Address, TLSCACert: config.Waflog.Tlscacert, TLSHostname: config.Waflog.Tlshostname, Format: config.Waflog.Format, From 81c31afc635c78119e0c6b5e7a60c42939814ad2 Mon Sep 17 00:00:00 2001 From: ejthurgo Date: Mon, 17 Jun 2019 21:18:16 +0100 Subject: [PATCH 2/2] Support no logs - Refactored AddLoggingCondition, broke out creation of web and WAF logs. - Logging and conditons now only created if config file has valid log configs. - Creation off logging endpoints can now be explicitly disabled through CLI arg `--no-logs` - Refactored CLI output to past-tense. --- waflyctl.go | 206 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 124 insertions(+), 82 deletions(-) diff --git a/waflyctl.go b/waflyctl.go index e847a16..a14d582 100644 --- a/waflyctl.go +++ b/waflyctl.go @@ -387,49 +387,59 @@ func vclSnippet(client fastly.Client, serviceID string, config TOMLConfig, versi } func fastlyLogging(client fastly.Client, serviceID string, config TOMLConfig, version int) { - _, err := client.CreateSyslog(&fastly.CreateSyslogInput{ - Service: serviceID, - Version: version, - Name: config.Weblog.Name, - Address: config.Weblog.Address, - Port: config.Weblog.Port, - UseTLS: fastly.CBool(true), - TLSCACert: config.Weblog.Tlscacert, - TLSHostname: config.Weblog.Tlshostname, - Format: config.Weblog.Format, - FormatVersion: 2, - MessageType: "blank", - }) - switch { - case err == nil: - Info.Printf("Logging endpoint %q created\n", config.Weblog.Name) - case strings.Contains(err.Error(), "Duplicate record"): - Warning.Printf("Logging endpoint %q already exists, skipping\n", config.Weblog.Name) - default: - Error.Fatalf("Cannot create logging endpoint %q: CreateSyslog: %v\n", config.Weblog.Name, err) - } - _, err = client.CreateSyslog(&fastly.CreateSyslogInput{ - Service: serviceID, - Version: version, - Name: config.Waflog.Name, - Address: config.Waflog.Address, - Port: config.Waflog.Port, - UseTLS: fastly.CBool(true), - TLSCACert: config.Waflog.Tlscacert, - TLSHostname: config.Waflog.Tlshostname, - Format: config.Waflog.Format, - FormatVersion: 2, - MessageType: "blank", - Placement: "waf_debug", - }) - switch { - case err == nil: - Info.Printf("Logging endpoint %q created\n", config.Waflog.Name) - case strings.Contains(err.Error(), "Duplicate record"): - Warning.Printf("Logging endpoint %q already exists, skipping\n", config.Waflog.Name) - default: - Error.Fatalf("Cannot create logging endpoint %q: CreateSyslog: %v\n", config.Waflog.Name, err) + if config.Weblog.Name != "" { + _, err := client.CreateSyslog(&fastly.CreateSyslogInput{ + Service: serviceID, + Version: version, + Name: config.Weblog.Name, + Address: config.Weblog.Address, + Port: config.Weblog.Port, + UseTLS: fastly.CBool(true), + TLSCACert: config.Weblog.Tlscacert, + TLSHostname: config.Weblog.Tlshostname, + Format: config.Weblog.Format, + FormatVersion: 2, + MessageType: "blank", + }) + switch { + case err == nil: + Info.Printf("Logging endpoint %q created\n", config.Weblog.Name) + case strings.Contains(err.Error(), "Duplicate record"): + Warning.Printf("Logging endpoint %q already exists, skipping\n", config.Weblog.Name) + default: + Error.Fatalf("Cannot create logging endpoint %q: CreateSyslog: %v\n", config.Weblog.Name, err) + } + } else { + Warning.Printf("Empty or invalid web log configuration, skipping\n") + } + + if config.Waflog.Name != "" { + _, err := client.CreateSyslog(&fastly.CreateSyslogInput{ + Service: serviceID, + Version: version, + Name: config.Waflog.Name, + Address: config.Waflog.Address, + Port: config.Waflog.Port, + UseTLS: fastly.CBool(true), + TLSCACert: config.Waflog.Tlscacert, + TLSHostname: config.Waflog.Tlshostname, + Format: config.Waflog.Format, + FormatVersion: 2, + MessageType: "blank", + Placement: "waf_debug", + }) + switch { + case err == nil: + Info.Printf("Logging endpoint %q created\n", config.Waflog.Name) + case strings.Contains(err.Error(), "Duplicate record"): + Warning.Printf("Logging endpoint %q already exists, skipping\n", config.Waflog.Name) + default: + Error.Fatalf("Cannot create logging endpoint %q: CreateSyslog: %v\n", config.Waflog.Name, err) + } + } else { + Warning.Printf("Empty or invalid WAF log configuration, skipping\n") } + } func wafContainer(client fastly.Client, serviceID string, config TOMLConfig, version int) string { @@ -776,9 +786,9 @@ func provisionWAF(client fastly.Client, serviceID string, config TOMLConfig, ver createOWASP(client, serviceID, config, wafID) - // In order to create the logging endpoints WAF must be - // created first. ¯\_(ツ)_/¯ - fastlyLogging(client, serviceID, config, version) + if !*omitLogs { + fastlyLogging(client, serviceID, config, version) + } return wafID } @@ -1049,36 +1059,19 @@ func DefaultRuleDisabled(apiEndpoint, apiKey, serviceID, wafID string, config TO } } -// AddLoggingCondition creates/updates logging conditions based on whether the -// user has specified withShielding, withPerimeterX and a web-log expiry. -// NOTE: PerimeterX conditions will be deprecated next major release. -func AddLoggingCondition(client fastly.Client, serviceID string, version int, config TOMLConfig, withShielding bool, withPX bool) bool { - conditions, err := client.ListConditions(&fastly.ListConditionsInput{ - Service: serviceID, - Version: version, - }) - if err != nil { - Error.Fatal(err) +// addWAFLoggingCondition adds a custom condition based on shielding & perimeterX +// requirements, assigns the condition to the logging object whilst cleaning up +// legacy conditions. +func addWAFLoggingCondition(client fastly.Client, serviceID string, version int, config TOMLConfig, conditions []*fastly.Condition, cstmts []string, msgs []string) bool { + // Ensure logging is defined in config and not being explicitly omitted + if *omitLogs || config.Waflog.Name == "" { return false } - // Create condition statement for Shielding & PX - var cstmts []string - var msgs []string - if withShielding { - msgs = append(msgs, "Shielding") - cstmts = append(cstmts, "(waf.executed || fastly_info.state !~ \"(MISS|PASS)\")") - } - if withPX { - msgs = append(msgs, "PerimeterX") - cstmts = append(cstmts, "(req.http.x-request-id)") - } - // Create WAF Log condition (drop the old one if it exists) - cn := "waf-soc-logging" + const cn string = "waf-soc-logging" if conditionExists(conditions, cn) { - Info.Printf("Updating WAF logging condition : %q\n", cn) - _, err = client.UpdateCondition(&fastly.UpdateConditionInput{ + _, err := client.UpdateCondition(&fastly.UpdateConditionInput{ Service: serviceID, Version: version, Name: cn, @@ -1090,9 +1083,9 @@ func AddLoggingCondition(client fastly.Client, serviceID string, version int, co Error.Fatal(err) return false } + Info.Printf("Updated WAF logging condition : %q\n", cn) } else { - Info.Printf("Creating WAF logging condition : %q\n", cn) - _, err = client.CreateCondition(&fastly.CreateConditionInput{ + _, err := client.CreateCondition(&fastly.CreateConditionInput{ Service: serviceID, Version: version, Name: cn, @@ -1104,11 +1097,11 @@ func AddLoggingCondition(client fastly.Client, serviceID string, version int, co Error.Fatal(err) return false } + Info.Printf("Created WAF logging condition : %q\n", cn) } // Assign the conditions to the WAF log object - Info.Printf("Assigning condition %q (%s) to WAF log %q\n", cn, strings.Join(msgs, ", "), config.Waflog.Name) - _, err = client.UpdateSyslog(&fastly.UpdateSyslogInput{ + _, err := client.UpdateSyslog(&fastly.UpdateSyslogInput{ Service: serviceID, Version: version, Name: config.Waflog.Name, @@ -1118,8 +1111,21 @@ func AddLoggingCondition(client fastly.Client, serviceID string, version int, co Error.Fatal(err) return false } + Info.Printf("Assigned condition %q (%s) to WAF log %q\n", cn, strings.Join(msgs, ", "), config.Waflog.Name) + return true +} + +// addWebLoggingCondition adds a custom condition based on shielding & perimeterX +// and log expiry requirements, assigns the condition to the logging object whilst +// cleaning up legacy conditions. +func addWebLoggingCondition(client fastly.Client, serviceID string, version int, config TOMLConfig, conditions []*fastly.Condition, cstmts []string, msgs []string) bool { + // Ensure logging is defined in config and not being explicitly omitted + if *omitLogs || config.Weblog.Name == "" { + return false + } // If a WAF Web-Log expiry has been defined, add expiry to the condition. + cn := "waf-soc-logging" if config.Weblog.Expiry > 0 { cn = "waf-soc-logging-with-expiry" exp := time.Now().AddDate(0, 0, int(config.Weblog.Expiry)).Unix() @@ -1127,8 +1133,7 @@ func AddLoggingCondition(client fastly.Client, serviceID string, version int, co msgs = append(msgs, fmt.Sprintf("%d day expiry", config.Weblog.Expiry)) if conditionExists(conditions, cn) { - Info.Printf("Updating WAF logging condition with %d day expiry : %q\n", config.Weblog.Expiry, cn) - _, err = client.UpdateCondition(&fastly.UpdateConditionInput{ + _, err := client.UpdateCondition(&fastly.UpdateConditionInput{ Service: serviceID, Version: version, Name: cn, @@ -1140,9 +1145,9 @@ func AddLoggingCondition(client fastly.Client, serviceID string, version int, co Error.Fatal(err) return false } + Info.Printf("Updated WAF logging condition with %d day expiry : %q\n", config.Weblog.Expiry, cn) } else { - Info.Printf("Creating WAF logging condition with %d day expiry : %q\n", config.Weblog.Expiry, cn) - _, err = client.CreateCondition(&fastly.CreateConditionInput{ + _, err := client.CreateCondition(&fastly.CreateConditionInput{ Service: serviceID, Version: version, Name: cn, @@ -1154,12 +1159,12 @@ func AddLoggingCondition(client fastly.Client, serviceID string, version int, co Error.Fatal(err) return false } + Info.Printf("Created WAF logging condition with %d day expiry : %q\n", config.Weblog.Expiry, cn) } } else { // Check for old Expires condition and clean if conditionExists(conditions, "waf-soc-logging-with-expiry") { - Info.Println("Deleting logging condition: 'waf-soc-logging-with-expiry'") - err = client.DeleteCondition(&fastly.DeleteConditionInput{ + err := client.DeleteCondition(&fastly.DeleteConditionInput{ Service: serviceID, Version: version, Name: "waf-soc-logging-with-expiry", @@ -1168,12 +1173,12 @@ func AddLoggingCondition(client fastly.Client, serviceID string, version int, co Error.Fatal(err) return false } + Info.Println("Deleted logging condition: 'waf-soc-logging-with-expiry'") } } // Assign the conditions to the WAF web-log object - Info.Printf("Assigning condition %q (%s) to web log %q\n", cn, strings.Join(msgs, ", "), config.Weblog.Name) - _, err = client.UpdateSyslog(&fastly.UpdateSyslogInput{ + _, err := client.UpdateSyslog(&fastly.UpdateSyslogInput{ Service: serviceID, Version: version, Name: config.Weblog.Name, @@ -1183,6 +1188,42 @@ func AddLoggingCondition(client fastly.Client, serviceID string, version int, co Error.Fatal(err) return false } + Info.Printf("Assigned condition %q (%s) to web log %q\n", cn, strings.Join(msgs, ", "), config.Weblog.Name) + + return true +} + +// AddLoggingCondition creates/updates logging conditions based on whether the +// user has specified withShielding, withPerimeterX and a web-log expiry. +// NOTE: PerimeterX conditions will be deprecated next major release. +func AddLoggingCondition(client fastly.Client, serviceID string, version int, config TOMLConfig, withShielding bool, withPX bool) bool { + + conditions, err := client.ListConditions(&fastly.ListConditionsInput{ + Service: serviceID, + Version: version, + }) + if err != nil { + Error.Fatal(err) + return false + } + + // Create condition statement for Shielding & PX + var cstmts []string + var msgs []string + if withShielding { + msgs = append(msgs, "Shielding") + cstmts = append(cstmts, "(waf.executed || fastly_info.state !~ \"(MISS|PASS)\")") + } + if withPX { + msgs = append(msgs, "PerimeterX") + cstmts = append(cstmts, "(req.http.x-request-id)") + } + + // Create WAF Log condition + addWAFLoggingCondition(client, serviceID, version, config, conditions, cstmts, msgs) + + // Create Web Log condition + addWebLoggingCondition(client, serviceID, version, config, conditions, cstmts, msgs) return true @@ -1818,6 +1859,7 @@ var ( listConfigSet = app.Flag("list-configuration-sets", "List all configuration sets and their status.").Bool() listRules = app.Flag("list-rules", "List current WAF rules and their status.").Bool() editOWASP = app.Flag("owasp", "Edit the OWASP object base on the settings in the configuration file.").Bool() + omitLogs = app.Flag("no-logs", "Provision the WAF without setting up any logging endpoints.").Bool() provision = app.Flag("provision", "Provision a new WAF or update an existing one.").Bool() publishers = app.Flag("publisher", "Which rule publisher to use in a comma delimited fashion. Overwrites publisher defined in config file. Choices are: owasp, trustwave, fastly").String() rules = app.Flag("rules", "Which rules to apply action on in a comma delimited fashion. Overwrites ruleid defined in config file. Example: 1010010,931100,931110.").String()