Skip to content

Commit

Permalink
Deprecate virtualdns.go in favor of dns_firewall.go
Browse files Browse the repository at this point in the history
This adds new types and functions for the DNS Firewall and DNS Firewall
Analytics API to `dns_firewall.go`, deprecating `virtual_dns.go`

For compatibility, the functions in `virtual_dns.go` are now wrappers
around the functions in `dns_firewall.go`. This means that we now always
use the new routes, even when one of the deprecated functions is called.

In addition to replacing "Virtual DNS" with "DNS Firewall", a few minor
changes were also made to names and function signatures.

Finally, the new DNS Firewall equivalents of the following functions are
no longer exported:

- VirtualDNSResponse
- VirtualDNSListResponse
- VirtualDNSAnalyticsResponse
  • Loading branch information
janik-cloudflare committed Dec 21, 2021
1 parent ae366e9 commit a7a3235
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 117 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The current feature list includes:
* [x] [Railgun](https://www.cloudflare.com/railgun/) administration
* [x] Rate Limiting
* [x] User Administration (partial)
* [x] Virtual DNS Management
* [x] DNS Firewall
* [x] Web Application Firewall (WAF)
* [x] Zone Lockdown and User-Agent Block rules
* [x] Zones
Expand Down
197 changes: 197 additions & 0 deletions dns_firewall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package cloudflare

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"

"github.com/pkg/errors"
)

// DNSFirewallCluster represents a DNS Firewall configuration.
type DNSFirewallCluster struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
OriginIPs []string `json:"origin_ips"`
DNSFirewallIPs []string `json:"dns_firewall_ips,omitempty"`
MinimumCacheTTL uint `json:"minimum_cache_ttl,omitempty"`
MaximumCacheTTL uint `json:"maximum_cache_ttl,omitempty"`
DeprecateAnyRequests bool `json:"deprecate_any_requests"`
ModifiedOn string `json:"modified_on,omitempty"`
}

// DNSFirewallAnalyticsMetrics represents a group of aggregated DNS Firewall metrics.
type DNSFirewallAnalyticsMetrics struct {
QueryCount *int64 `json:"queryCount"`
UncachedCount *int64 `json:"uncachedCount"`
StaleCount *int64 `json:"staleCount"`
ResponseTimeAvg *float64 `json:"responseTimeAvg"`
ResponseTimeMedian *float64 `json:"responseTimeMedian"`
ResponseTime90th *float64 `json:"responseTime90th"`
ResponseTime99th *float64 `json:"responseTime99th"`
}

// DNSFirewallAnalytics represents a set of aggregated DNS Firewall metrics.
// TODO: Add the queried data and not only the aggregated values.
type DNSFirewallAnalytics struct {
Totals DNSFirewallAnalyticsMetrics `json:"totals"`
Min DNSFirewallAnalyticsMetrics `json:"min"`
Max DNSFirewallAnalyticsMetrics `json:"max"`
}

// DNSFirewallUserAnalyticsOptions represents range and dimension selection on analytics endpoint
type DNSFirewallUserAnalyticsOptions struct {
Metrics []string
Since *time.Time
Until *time.Time
}

// dnsFirewallResponse represents a DNS Firewall response.
type dnsFirewallResponse struct {
Response
Result *DNSFirewallCluster `json:"result"`
}

// dnsFirewallListResponse represents an array of DNS Firewall responses.
type dnsFirewallListResponse struct {
Response
Result []*DNSFirewallCluster `json:"result"`
}

// dnsFirewallAnalyticsResponse represents a DNS Firewall analytics response.
type dnsFirewallAnalyticsResponse struct {
Response
Result DNSFirewallAnalytics `json:"result"`
}

// CreateDNSFirewallCluster creates a new DNS Firewall cluster.
//
// API reference: https://api.cloudflare.com/#dns-firewall-create-dns-firewall-cluster
func (api *API) CreateDNSFirewallCluster(ctx context.Context, v DNSFirewallCluster) (*DNSFirewallCluster, error) {
uri := fmt.Sprintf("%s/dns_firewall", api.userBaseURL("/user"))
res, err := api.makeRequestContext(ctx, http.MethodPost, uri, v)
if err != nil {
return nil, err
}

response := &dnsFirewallResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}

return response.Result, nil
}

// DNSFirewallCluster fetches a single DNS Firewall cluster.
//
// API reference: https://api.cloudflare.com/#dns-firewall-dns-firewall-cluster-details
func (api *API) DNSFirewallCluster(ctx context.Context, clusterID string) (*DNSFirewallCluster, error) {
uri := fmt.Sprintf("%s/dns_firewall/%s", api.userBaseURL("/user"), clusterID)
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, err
}

response := &dnsFirewallResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}

return response.Result, nil
}

// ListDNSFirewallClusters lists the DNS Firewall clusters associated with an account.
//
// API reference: https://api.cloudflare.com/#dns-firewall-list-dns-firewall-clusters
func (api *API) ListDNSFirewallClusters(ctx context.Context) ([]*DNSFirewallCluster, error) {
uri := fmt.Sprintf("%s/dns_firewall", api.userBaseURL("/user"))
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, err
}

response := &dnsFirewallListResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return nil, errors.Wrap(err, errUnmarshalError)
}

return response.Result, nil
}

// UpdateDNSFirewallCluster updates a DNS Firewall cluster.
//
// API reference: https://api.cloudflare.com/#dns-firewall-update-dns-firewall-cluster
func (api *API) UpdateDNSFirewallCluster(ctx context.Context, clusterID string, vv DNSFirewallCluster) error {
uri := fmt.Sprintf("%s/dns_firewall/%s", api.userBaseURL("/user"), clusterID)
res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, vv)
if err != nil {
return err
}

response := &dnsFirewallResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return errors.Wrap(err, errUnmarshalError)
}

return nil
}

// DeleteDNSFirewallCluster deletes a DNS Firewall cluster. Note that this cannot be
// undone, and will stop all traffic to that cluster.
//
// API reference: https://api.cloudflare.com/#dns-firewall-delete-dns-firewall-cluster
func (api *API) DeleteDNSFirewallCluster(ctx context.Context, clusterID string) error {
uri := fmt.Sprintf("%s/dns_firewall/%s", api.userBaseURL("/user"), clusterID)
res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
if err != nil {
return err
}

response := &dnsFirewallResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return errors.Wrap(err, errUnmarshalError)
}

return nil
}

// encode encodes non-nil fields into URL encoded form.
func (o DNSFirewallUserAnalyticsOptions) encode() string {
v := url.Values{}
if o.Since != nil {
v.Set("since", (*o.Since).UTC().Format(time.RFC3339))
}
if o.Until != nil {
v.Set("until", (*o.Until).UTC().Format(time.RFC3339))
}
if o.Metrics != nil {
v.Set("metrics", strings.Join(o.Metrics, ","))
}
return v.Encode()
}

// DNSFirewallUserAnalytics retrieves analytics report for a specified dimension and time range
func (api *API) DNSFirewallUserAnalytics(ctx context.Context, clusterID string, o DNSFirewallUserAnalyticsOptions) (DNSFirewallAnalytics, error) {
uri := fmt.Sprintf("%s/dns_firewall/%s/dns_analytics/report?%s", api.userBaseURL("/user"), clusterID, o.encode())
res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return DNSFirewallAnalytics{}, err
}

response := dnsFirewallAnalyticsResponse{}
err = json.Unmarshal(res, &response)
if err != nil {
return DNSFirewallAnalytics{}, errors.Wrap(err, errUnmarshalError)
}

return response.Result, nil
}
86 changes: 86 additions & 0 deletions dns_firewall_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package cloudflare

import (
"context"
"fmt"
"net/http"
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func float64Ptr(v float64) *float64 {
return &v
}

func int64Ptr(v int64) *int64 {
return &v
}

func TestDNSFirewallUserAnalytics(t *testing.T) {
setup()
defer teardown()

now := time.Now().UTC()
since := now.Add(-1 * time.Hour)
until := now

handler := func(w http.ResponseWriter, r *http.Request) {
expectedMetrics := "queryCount,uncachedCount,staleCount,responseTimeAvg,responseTimeMedia,responseTime90th,responseTime99th"

assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET'")
assert.Equal(t, expectedMetrics, r.URL.Query().Get("metrics"), "Expected many metrics in URL parameter")
assert.Equal(t, since.Format(time.RFC3339), r.URL.Query().Get("since"), "Expected since parameter in URL")
assert.Equal(t, until.Format(time.RFC3339), r.URL.Query().Get("until"), "Expected until parameter in URL")

w.Header().Set("content-type", "application/json")
fmt.Fprint(w, `{
"result": {
"totals":{
"queryCount": 5,
"uncachedCount":6,
"staleCount":7,
"responseTimeAvg":1.0,
"responseTimeMedian":2.0,
"responseTime90th":3.0,
"responseTime99th":4.0
}
},
"success": true,
"errors": null,
"messages": null
}`)
}

mux.HandleFunc("/user/dns_firewall/12345/dns_analytics/report", handler)
want := DNSFirewallAnalytics{
Totals: DNSFirewallAnalyticsMetrics{
QueryCount: int64Ptr(5),
UncachedCount: int64Ptr(6),
StaleCount: int64Ptr(7),
ResponseTimeAvg: float64Ptr(1.0),
ResponseTimeMedian: float64Ptr(2.0),
ResponseTime90th: float64Ptr(3.0),
ResponseTime99th: float64Ptr(4.0),
},
}

params := DNSFirewallUserAnalyticsOptions{
Metrics: []string{
"queryCount",
"uncachedCount",
"staleCount",
"responseTimeAvg",
"responseTimeMedia",
"responseTime90th",
"responseTime99th",
},
Since: &since,
Until: &until,
}
actual, err := client.DNSFirewallUserAnalytics(context.Background(), "12345", params)
if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}
Loading

0 comments on commit a7a3235

Please sign in to comment.