Skip to content
This repository has been archived by the owner on Dec 13, 2023. It is now read-only.

Commit

Permalink
publisher functionality and documentation added
Browse files Browse the repository at this point in the history
  • Loading branch information
josehelps committed Aug 21, 2018
1 parent f8669fd commit 7cf87d5
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 45 deletions.
7 changes: 7 additions & 0 deletions Documentation/EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,10 @@
## Listing all rules and their status for a service
`./waflyctl -apikey $FASTLY_TOKEN -serviceid 7YCnicdpjTvxR2JdzNxxxx -list-rules`

## Set all rules of publisher owasp to logging
`./waflyctl -apikey $FASTLY_TOKEN -serviceid 7YCnicdpjTvxR2JdzNAKCj -publisher owasp -action log`

## Disable WAF in case of an emergency
`./waflyctl -apikey $FASTLY_TOKEN -serviceid 7YCnicdpjTvxR2JdzNAKCj -publisher owasp -action log`


6 changes: 5 additions & 1 deletion Documentation/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Usage of waflyctl:
[Required] API Key to use
-config string
Location of configuration file for waflyctl. (default "/Users/jhernandez/.waflyctl.toml")
-configuration-set string
Changes WAF configuration set to the provided one]
-delete
When set removes a WAF configuration created with waflyctl.
-delete-logs
Expand All @@ -27,14 +29,16 @@ Usage of waflyctl:
List current WAF rules and their status
-owasp
When set edits the OWASP object base on the settings in the configuration file.
-publisher string
Which rule publisher to use in a comma delimited fashion, overwrites publisher defined in config file, choices are: owasp, trustwave, fastly
-rules string
Which rules to apply action on in a comma delimited fashion, overwrites ruleid defined in config file, example: 94011,93110,1000101..
-serviceid string
[Required] Service ID to Provision
-status string
Disable or Enable the WAF. A disabled WAF will not block any traffic, also disabling a WAF does not change rule statuses on its configure policy.
-tags string
Which rules tags to add to the ruleset in a comma delimited fashion, overwrites tags defined in config file, example: OWASP,wordpress,php
Which rules tags to add to the ruleset in a comma delimited fashion, overwrites tags defined in config file, example: wordpress,language-php,drupal
-with-perimeterx
Enable if the customer has perimeterX enabled on the service as well as WAF. Helps fix null value logging.
```
3 changes: 2 additions & 1 deletion config_examples/waflyctl.toml.example
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@

logpath = "waflyctl.log"
apiendpoint = "https://api.fastly.com"
tags = ["OWASP","language-html","language-htm","language-css","language-jpg","language-json"]
tags = [""]
publisher = ["owasp"]
action = "log"
rules = []

Expand Down
184 changes: 141 additions & 43 deletions waflyctl.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import (
"io"
"log"
"os"
"os/user"
"strconv"
"strings"
"time"
"os/user"
)

var (
Expand All @@ -43,11 +43,8 @@ var (

//HOMEDIRECTORY static variable
HOMEDIRECTORY string

)



// TOMLConfig is the applications config file
type TOMLConfig struct {
Logpath string
Expand Down Expand Up @@ -203,8 +200,8 @@ type Rule struct {
Status string `json:"status"`
Origin string `json:"origin"`
ParanoiaLevel int `json:"paranoia_level"`
Revision string `json:"revision"`
Severity int `json:"severity"`
Revision int `json:"revision"`
Severity interface{} `json:"severity"`
Version interface{} `json:"version"`
RuleID string `json:"rule_id"`
ModsecRuleID string `json:"modsec_rule_id"`
Expand Down Expand Up @@ -257,7 +254,7 @@ type ConfigSet struct {
ID string `json:"id"`
Type string `json:"type"`
Attributes struct {
Active bool `json:"active"`
Active bool `json:"active"`
Name string `json:"name"`
} `json:"attributes"`
}
Expand Down Expand Up @@ -991,7 +988,108 @@ func validateVersion(client fastly.Client, serviceID string, version int) bool {
Info.Printf("Config Version %v Validated successfully", version)
return true

}func tagsConfig(apiEndpoint, apiKey, serviceID, wafID string, client fastly.Client, config TOMLConfig) {
}

func publisherConfig(apiEndpoint, apiKey, serviceID, wafID string, client fastly.Client, config TOMLConfig) bool {

//cleanup action
action := strings.TrimSpace(config.Action)
action = strings.ToLower(action)

for _, publisher := range config.Publisher {

//set our API call
apiCall := apiEndpoint + "/wafs/rules?filter[publisher]=" + publisher + "&page[number]=1"

resp, err := resty.R().
SetHeader("Accept", "application/vnd.api+json").
SetHeader("Fastly-Key", apiKey).
SetHeader("Content-Type", "application/vnd.api+json").
Get(apiCall)

//check if we had an issue with our call
if err != nil {
Error.Println("Error with API call: " + apiCall)
Error.Println(resp.String())
return false
}

//unmarshal the response and extract the rules
body := RuleList{}

json.Unmarshal([]byte(resp.String()), &body)

if len(body.Data) == 0 {
Error.Println("No Fastly Rules found")
return false
}

result := PagesOfRules{[]RuleList{}}
result.page = append(result.page, body)

currentpage := body.Meta.CurrentPage
totalpages := body.Meta.TotalPages

Info.Printf("Read Total Pages: %d with %d rules", body.Meta.TotalPages, body.Meta.RecordCount)

// iterate through pages collecting all rules
for currentpage := currentpage + 1; currentpage <= totalpages; currentpage++ {

Info.Printf("Reading page: %d out of %d", currentpage, totalpages)
//set our API call
apiCall := apiEndpoint + "/wafs/rules?filter[publisher]=" + publisher + "&page[number]=" + strconv.Itoa(currentpage)

resp, err := resty.R().
SetHeader("Accept", "application/vnd.api+json").
SetHeader("Fastly-Key", apiKey).
SetHeader("Content-Type", "application/vnd.api+json").
Get(apiCall)

//check if we had an issue with our call
if err != nil {
Error.Println("Error with API call: " + apiCall)
Error.Println(resp.String())
return false
}

//unmarshal the response and extract the service id
body := RuleList{}
json.Unmarshal([]byte(resp.String()), &body)
result.page = append(result.page, body)
}
Info.Println("- Publisher ", publisher)
for _, p := range result.page {
for _, r := range p.Data {

//set rule action on our tags
apiCall := apiEndpoint + "/service/" + serviceID + "/wafs/" + wafID + "/rules/" + r.ID + "/rule_status"

resp, err := resty.R().
SetHeader("Accept", "application/vnd.api+json").
SetHeader("Fastly-Key", apiKey).
SetHeader("Content-Type", "application/vnd.api+json").
SetBody(`{"data": {"attributes": {"status": "` + action + `"},"id": "` + wafID + `-` + r.ID + `","type": "rule_status"}}`).
Patch(apiCall)

//check if we had an issue with our call
if err != nil {
Error.Println("Error with API call: " + apiCall)
Error.Println(resp.String())
os.Exit(1)
}

//check if our response was ok
if resp.Status() == "200 OK" {
Info.Printf("Rule %s was configured in the WAF with action %s", r.ID, config.Action)
} else {
Error.Println("Could not set status: "+config.Action+" on rule tag: "+r.ID+" the response was: ", resp.String())
}
}
}

}

return true

}

Expand Down Expand Up @@ -1238,14 +1336,13 @@ func PatchRules(serviceID, wafID string, client fastly.Client) bool {
// changeConfigurationSet function allows you to change a config set for a WAF object
func setConfigurationSet(wafID, configurationSet string, client fastly.Client) bool {

wafs := []fastly.ConfigSetWAFs{{ID:wafID}}
wafs := []fastly.ConfigSetWAFs{{ID: wafID}}

_, err := client.UpdateWAFConfigSet(&fastly.UpdateWAFConfigSetInput{
WAFList:wafs,
WAFList: wafs,
ConfigSetID: configurationSet,
})


//check if we had an issue with our call
if err != nil {
Error.Println("Error setting configuration set ID: " + configurationSet)
Expand Down Expand Up @@ -1283,7 +1380,6 @@ func getConfigurationSets(apiEndpoint, apiKey string) bool {
return false
}


json.Unmarshal([]byte(resp.String()), &body)

if len(body.Data) == 0 {
Expand Down Expand Up @@ -1337,35 +1433,35 @@ func getConfigurationSets(apiEndpoint, apiKey string) bool {

// getRuleInfo function
func getRuleInfo(apiEndpoint, apiKey, ruleID string) Rule {
rule := Rule{}
//set our API call
apiCall := apiEndpoint + "/wafs/rules?page[size]=10&page[number]=1&filter[rule_id]=" + ruleID
rule := Rule{}
//set our API call
apiCall := apiEndpoint + "/wafs/rules?page[size]=10&page[number]=1&filter[rule_id]=" + ruleID

resp, err := resty.R().
SetHeader("Accept", "application/vnd.api+json").
SetHeader("Fastly-Key", apiKey).
SetHeader("Content-Type", "application/vnd.api+json").
Get(apiCall)
resp, err := resty.R().
SetHeader("Accept", "application/vnd.api+json").
SetHeader("Fastly-Key", apiKey).
SetHeader("Content-Type", "application/vnd.api+json").
Get(apiCall)

//check if we had an issue with our call
if err != nil {
Error.Println("Error with API call: " + apiCall)
Error.Println(resp.String())
}
//check if we had an issue with our call
if err != nil {
Error.Println("Error with API call: " + apiCall)
Error.Println(resp.String())
}

//unmarshal the response and extract the service id
body := RuleList{}
json.Unmarshal([]byte(resp.String()), &body)
//unmarshal the response and extract the service id
body := RuleList{}
json.Unmarshal([]byte(resp.String()), &body)

if len(body.Data) == 0 {
Error.Println("No Fastly Rules found")
}
if len(body.Data) == 0 {
Error.Println("No Fastly Rules found")
}

for _, r := range body.Data{
rule = r
}
for _, r := range body.Data {
rule = r
}

return rule
return rule
}

// getRules functions lists all rules for a WAFID and their status
Expand Down Expand Up @@ -1434,7 +1530,7 @@ func getRules(apiEndpoint, apiKey, serviceID, wafID string) bool {
var block []Rule

for _, p := range result.page {
for _, r := range p.Data {
for _, r := range p.Data {
if r.Attributes.Status == "log" {
log = append(log, r)
} else if r.Attributes.Status == "block" {
Expand All @@ -1450,7 +1546,7 @@ func getRules(apiEndpoint, apiKey, serviceID, wafID string) bool {
info := getRuleInfo(apiEndpoint, apiKey, r.Attributes.ModsecRuleID)
Info.Printf("- Rule ID: %s\tStatus: %s\tParanoia: %d\tOrigin: %s\tMessage: %s\n",
r.Attributes.ModsecRuleID, r.Attributes.Status, info.Attributes.ParanoiaLevel,
info.Attributes.Origin, info.Attributes.Message)
info.Attributes.Origin, info.Attributes.Message)
}

Info.Println("- Logging Rules")
Expand Down Expand Up @@ -1677,7 +1773,7 @@ func main() {
serviceID := flag.String("serviceid", "", "[Required] Service ID to Provision")
apiKey := flag.String("apikey", "", "[Required] API Key to use")
apiEndpoint := flag.String("apiendpoint", "https://api.fastly.com", "Fastly API endpoint to use.")
configFile := flag.String("config", HOMEDIRECTORY + "/.waflyctl.toml", "Location of configuration file for waflyctl.")
configFile := flag.String("config", HOMEDIRECTORY+"/.waflyctl.toml", "Location of configuration file for waflyctl.")

ListAllRules := flag.String("list-all-rules", "", "List all rules available on the Fastly platform for a given configuration set. Must pass a configuration set ID")
ListRules := flag.Bool("list-rules", false, "List current WAF rules and their status")
Expand Down Expand Up @@ -1808,6 +1904,7 @@ func main() {
}
}


//if rule publisher is passed via CLI parse them and replace config parameters
if Publishers != "" {
config.Publisher = nil
Expand Down Expand Up @@ -1922,7 +2019,7 @@ func main() {
//enable the WAF feature if is not already on
checkWAF(*apiKey, config.APIEndpoint)

Info.Printf("currently working with config version: %v.\n*Note rule, OWASP and tags changes are versionless actions and thus do not generate a new config version*", activeVersion)
Info.Printf("currently working with config version: %v.\n*Note Publisher, Rules, OWASP Settings and Tags changes are versionless actions and thus do not generate a new config version*", activeVersion)
wafs, err := client.ListWAFs(&fastly.ListWAFsInput{
Service: *serviceID,
Version: activeVersion,
Expand Down Expand Up @@ -1965,7 +2062,7 @@ func main() {
case *ConfigurationSet != "":
Info.Printf("Changing Configuration Set to: %s", *ConfigurationSet)
ConfigID := *ConfigurationSet
setConfigurationSet(waf.ID,ConfigID, *client)
setConfigurationSet(waf.ID, ConfigID, *client)
Info.Println("Completed")
os.Exit(1)

Expand All @@ -1992,7 +2089,7 @@ func main() {
case Publishers != "":
Info.Println("Editing Publishers")
//Publisher management
PublisherConfig(config.APIEndpoint, *apiKey, *serviceID, waf.ID, *client, config)
publisherConfig(config.APIEndpoint, *apiKey, *serviceID, waf.ID, *client, config)

//patch ruleset
if PatchRules(*serviceID, waf.ID, *client) {
Expand Down Expand Up @@ -2069,11 +2166,12 @@ func main() {
//provision a new WAF service
wafID := provisionWAF(*client, *serviceID, *apiKey, config, version)

//publisher management
publisherConfig(config.APIEndpoint, *apiKey, *serviceID, wafID, *client, config)

//tags management
tagsConfig(config.APIEndpoint, *apiKey, *serviceID, wafID, *client, config)

//publisher management
publisherConfig(config.APIEndpoint, *apiKey, *serviceID, wafID, *client, config)

//rule management
rulesConfig(config.APIEndpoint, *apiKey, *serviceID, wafID, *client, config)
Expand Down

0 comments on commit 7cf87d5

Please sign in to comment.