From 8dea9d3d6c37cb795edd3c90ceca2185de6f7411 Mon Sep 17 00:00:00 2001 From: Rich Abdill Date: Mon, 18 Dec 2017 16:39:40 -0600 Subject: [PATCH] Method for modifying WAF rules by tag --- fastly/errors.go | 8 +++- fastly/request.go | 6 +-- fastly/waf.go | 102 ++++++++++++++++++++++++++++++++++++++++++---- 3 files changed, 103 insertions(+), 13 deletions(-) diff --git a/fastly/errors.go b/fastly/errors.go index 4a9906eda..2988c2a60 100644 --- a/fastly/errors.go +++ b/fastly/errors.go @@ -13,10 +13,14 @@ import ( // a "Service" key, but one was not set. var ErrMissingService = errors.New("Missing required field 'Service'") -// ErrMissingService is an error that is returned when an input struct requires -// a "Service" key, but one was not set. +// ErrMissingStatus is an error that is returned when an input struct requires +// a "Status" key, but one was not set. var ErrMissingStatus = errors.New("Missing required field 'Status'") +// ErrMissingTag is an error that is returned when an input struct requires +// a "Tag" key, but one was not set. +var ErrMissingTag = errors.New("Missing required field 'Tag'") + // ErrMissingVersion is an error that is returned when an input struct requires // a "Version" key, but one was not set. var ErrMissingVersion = errors.New("Missing required field 'Version'") diff --git a/fastly/request.go b/fastly/request.go index 84fcafaef..b10d179f5 100644 --- a/fastly/request.go +++ b/fastly/request.go @@ -66,11 +66,11 @@ func (c *Client) RawRequest(verb, p string, ro *RequestOptions) (*http.Request, return request, nil } -// StraightGet combines the RawRequest and Request methods, +// SimpleGet combines the RawRequest and Request methods, // but doesn't add any parameters or change any encoding in the URL // passed to it. It's mostly for calling the URLs given to us // directly from Fastly without mangling them. -func (c *Client) StraightGet(target string) (*http.Response, error) { +func (c *Client) SimpleGet(target string) (*http.Response, error) { // We parse the URL and then convert it right back to a string // later; this just acts as a check that Fastly isn't sending // us nonsense. @@ -79,13 +79,11 @@ func (c *Client) StraightGet(target string) (*http.Response, error) { return nil, err } - // Create the request object. request, err := http.NewRequest("GET", url.String(), nil) if err != nil { return nil, err } - // Set the API key. if len(c.apiKey) > 0 { request.Header.Set(APIKeyHeader, c.apiKey) } diff --git a/fastly/waf.go b/fastly/waf.go index acbefa310..22db0eeda 100644 --- a/fastly/waf.go +++ b/fastly/waf.go @@ -713,8 +713,10 @@ func (c *Client) GetWAFRuleStatuses(i *GetWAFRuleStatusesInput) (GetWAFRuleStatu return statusResponse, err } -// fetchWAFRuleStatusesPage recursively calls the fastly rules status endpoint until there -// are no more results to request. +// interpretWAFRuleStatusesPage accepts a Fastly response for a set of WAF rule statuses +// and unmarshals the results. If there are more pages of results, it fetches the next +// page, adds that response to the array of results, and repeats until all results have +// been fetched. func (c *Client) interpretWAFRuleStatusesPage(answer *GetWAFRuleStatusesResponse, received *http.Response) error { // before we pull the status info out of the response body, fetch // pagination info from it: @@ -737,7 +739,7 @@ func (c *Client) interpretWAFRuleStatusesPage(answer *GetWAFRuleStatusesResponse } if pages.Next != "" { // NOTE: pages.Next URL includes filters already - resp, err := c.StraightGet(pages.Next) + resp, err := c.SimpleGet(pages.Next) if err != nil { return err } @@ -783,7 +785,7 @@ func getPages(body io.Reader) (paginationInfo, io.Reader, error) { return pages.Links, bytes.NewReader(buf.Bytes()), nil } -// GetWAFRuleStatusInput specifies the parameters for the GetWAFRuleStatus call +// GetWAFRuleStatusInput specifies the parameters for the GetWAFRuleStatus call. type GetWAFRuleStatusInput struct { ID int Service string @@ -814,7 +816,7 @@ func (c *Client) GetWAFRuleStatus(i *GetWAFRuleStatusInput) (WAFRuleStatus, erro return status.simplify(), err } -// UpdateWAFRuleStatusInput specifies the parameters for the UpdateWAFRuleStatus call +// UpdateWAFRuleStatusInput specifies the parameters for the UpdateWAFRuleStatus call. type UpdateWAFRuleStatusInput struct { ID int Service string @@ -854,8 +856,7 @@ func (c *Client) UpdateWAFRuleStatus(i *UpdateWAFRuleStatusInput) (WAFRuleStatus } path := fmt.Sprintf("/service/%s/wafs/%s/rules/%d/rule_status", i.Service, i.WAF, i.ID) - toSend := new(updateWAFRuleStatusBody) - toSend = &updateWAFRuleStatusBody{ + toSend := &updateWAFRuleStatusBody{ ID: fmt.Sprintf("%s-%d", i.WAF, i.ID), Status: i.Status, } @@ -882,3 +883,90 @@ func (c *Client) UpdateWAFRuleStatus(i *UpdateWAFRuleStatusInput) (WAFRuleStatus err = jsonapi.UnmarshalPayload(resp.Body, &status) return status.simplify(), err } + +// UpdateWAFRuleTagStatusInput specifies the parameters for the UpdateWAFRuleStatus call. +type UpdateWAFRuleTagStatusInput struct { + Service string + WAF string + Status string `json:"status"` // `jsonapi:"attr,status"` + Tag string `json:"name"` // `jsonapi:"attr,name"` + Force bool `json:"force"` // `jsonapi:"attr,force"` + // HACK: This won't work with the jsonapi struct tags, because the POST body expected by + // Fastly doesn't conform to the jsonapi spec -- there's no ID field at the top level, + // and there's no way for us to indicate the "type" of the entity without a primary key. + // ID field is required: http://jsonapi.org/format/#document-resource-objects +} + +// updateWAFRuleTagStatusBody is the top-level object sent to Fastly based on +// UpdateWAFRuleTagStatusInput from the user. +type updateWAFRuleTagStatusBody struct { + Data updateWAFRuleTagStatusData `json:"data"` +} + +type updateWAFRuleTagStatusData struct { + Type string `json:"type"` // hard-coded because we can't use jsonapi + Attributes *UpdateWAFRuleTagStatusInput `json:"attributes"` // supplied by user as input +} + +// validate makes sure the UpdateWAFRuleStatusInput instance has all +// fields we need to request a change. +func (i UpdateWAFRuleTagStatusInput) validate() error { + if i.Tag == "" { + return ErrMissingTag + } + if i.Service == "" { + return ErrMissingService + } + if i.WAF == "" { + return ErrMissingWAFID + } + if i.Status == "" { + return ErrMissingStatus + } + return nil +} + +// ReceivedWAFRuleTagStatus is the response from Fastly when a tag's +// status is updated for a WAF. +type ReceivedWAFRuleTagStatus struct { + ID string `jsonapi:"primary,rule_status"` + Status string `jsonapi:"attr,status"` + Name string `jsonapi:"attr,name"` +} + +// UpdateWAFRuleTagStatus changes the status of a single rule associated with a WAF. +func (c *Client) UpdateWAFRuleTagStatus(input *UpdateWAFRuleTagStatusInput) (ReceivedWAFRuleTagStatus, error) { + if err := input.validate(); err != nil { + return ReceivedWAFRuleTagStatus{}, err + } + + path := fmt.Sprintf("/service/%s/wafs/%s/rule_statuses", input.Service, input.WAF) + + body := updateWAFRuleTagStatusBody{ + Data: updateWAFRuleTagStatusData{ + Type: "rule_status", + Attributes: input, + }, + } + encoded, err := json.Marshal(body) + if err != nil { + return ReceivedWAFRuleTagStatus{}, err + } + + options := &RequestOptions{ + Body: bytes.NewReader(encoded), + Headers: map[string]string{ + "Content-Type": jsonapi.MediaType, + "Accept": jsonapi.MediaType, + }, + } + + resp, err := c.Post(path, options) + if err != nil { + return ReceivedWAFRuleTagStatus{}, err + } + + var status ReceivedWAFRuleTagStatus + err = jsonapi.UnmarshalPayload(resp.Body, &status) + return status, err +}