diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb551c7b961b..b6db39beb4210 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ * [4736](https://github.com/grafana/loki/pull/4736) **sandeepsukhani**: allow applying retention at different interval than compaction * [4744](https://github.com/grafana/loki/pull/4744) **cyriltovena**: Promtail: Adds GELF UDP support. * [4741](https://github.com/grafana/loki/pull/4741) **sandeepsukhani**: index cleanup fixes while applying retention +* [4813](https://github.com/grafana/loki/pull/4813) **cyriltovena**: Promtail: Adds the ability to pull logs from Cloudflare. * [4853](https://github.com/grafana/loki/pull/4853) **sandeepsukhani**: recreate compacted boltdb files from compactor to reduce storage space usage # 2.4.1 (2021/11/07) diff --git a/clients/cmd/promtail/promtail-cloudflare.yaml b/clients/cmd/promtail/promtail-cloudflare.yaml new file mode 100644 index 0000000000000..8f23dedefbf3a --- /dev/null +++ b/clients/cmd/promtail/promtail-cloudflare.yaml @@ -0,0 +1,17 @@ +server: + http_listen_port: 9080 + grpc_listen_port: 0 + +positions: + filename: /tmp/positions.yaml + +clients: + - url: http://localhost:3100/loki/api/v1/push + +scrape_configs: +- job_name: cloudflare + cloudflare: + api_token: REDACTED + zone_id: REDACTED + labels: + job: cloudflare diff --git a/clients/pkg/promtail/positions/positions.go b/clients/pkg/promtail/positions/positions.go index 7e0439f0c39ed..2232206e4be09 100644 --- a/clients/pkg/promtail/positions/positions.go +++ b/clients/pkg/promtail/positions/positions.go @@ -16,7 +16,11 @@ import ( yaml "gopkg.in/yaml.v2" ) -const positionFileMode = 0600 +const ( + positionFileMode = 0600 + cursorKeyPrefix = "cursor-" + journalKeyPrefix = "journal-" +) // Config describes where to get position information from. type Config struct { @@ -176,14 +180,20 @@ func (p *positions) save() { } } +// CursorKey returns a key that can be saved as a cursor that is never deleted. +func CursorKey(key string) string { + return fmt.Sprintf("%s%s", cursorKeyPrefix, key) +} + func (p *positions) cleanup() { p.mtx.Lock() defer p.mtx.Unlock() toRemove := []string{} for k := range p.positions { - // If the position file is prefixed with journal, it's a - // JournalTarget cursor and not a file on disk. - if strings.HasPrefix(k, "journal-") { + // If the position file is prefixed with cursor, it's a + // cursor and not a file on disk. + // We still have to support journal files, so we keep the previous check to avoid breaking change. + if strings.HasPrefix(k, cursorKeyPrefix) || strings.HasPrefix(k, journalKeyPrefix) { continue } @@ -204,7 +214,6 @@ func (p *positions) cleanup() { } func readPositionsFile(cfg Config, logger log.Logger) (map[string]string, error) { - cleanfn := filepath.Clean(cfg.PositionsFile) buf, err := ioutil.ReadFile(cleanfn) if err != nil { diff --git a/clients/pkg/promtail/scrapeconfig/scrapeconfig.go b/clients/pkg/promtail/scrapeconfig/scrapeconfig.go index f0a1d4f8e129d..44b24d436b98d 100644 --- a/clients/pkg/promtail/scrapeconfig/scrapeconfig.go +++ b/clients/pkg/promtail/scrapeconfig/scrapeconfig.go @@ -42,6 +42,7 @@ type Config struct { WindowsConfig *WindowsEventsTargetConfig `yaml:"windows_events,omitempty"` KafkaConfig *KafkaTargetConfig `yaml:"kafka,omitempty"` GelfConfig *GelfTargetConfig `yaml:"gelf,omitempty"` + CloudflareConfig *CloudflareConfig `yaml:"cloudflare,omitempty"` RelabelConfigs []*relabel.Config `yaml:"relabel_configs,omitempty"` ServiceDiscoveryConfig ServiceDiscoveryConfig `yaml:",inline"` } @@ -309,6 +310,27 @@ type GelfTargetConfig struct { UseIncomingTimestamp bool `yaml:"use_incoming_timestamp"` } +type CloudflareConfig struct { + // APIToken is the API key for the Cloudflare account. + APIToken string `yaml:"api_token"` + // ZoneID is the ID of the zone to use. + ZoneID string `yaml:"zone_id"` + // Labels optionally holds labels to associate with each record read from cloudflare logs. + Labels model.LabelSet `yaml:"labels"` + // The amount of workers to use for parsing cloudflare logs. Default to 3. + Workers int `yaml:"workers"` + // The timerange to fetch for each pull request that will be spread across workers. Default 1m. + PullRange model.Duration `yaml:"pull_range"` + // Fields to fetch from cloudflare logs. + // Default to default fields. + // Available fields type: + // - default + // - minimal + // - extended + // - all + FieldsType string `yaml:"fields_type"` +} + // GcplogTargetConfig describes a scrape config to pull logs from any pubsub topic. type GcplogTargetConfig struct { // ProjectID is the Cloud project id diff --git a/clients/pkg/promtail/targets/cloudflare/client.go b/clients/pkg/promtail/targets/cloudflare/client.go new file mode 100644 index 0000000000000..c0097711cad40 --- /dev/null +++ b/clients/pkg/promtail/targets/cloudflare/client.go @@ -0,0 +1,37 @@ +package cloudflare + +import ( + "context" + "time" + + "github.com/cloudflare/cloudflare-go" +) + +// Client is a wrapper around the Cloudflare API that allow for testing and being zone/fields aware. +type Client interface { + LogpullReceived(ctx context.Context, start, end time.Time) (cloudflare.LogpullReceivedIterator, error) +} + +type wrappedClient struct { + client *cloudflare.API + zoneID string + fields []string +} + +func (w *wrappedClient) LogpullReceived(ctx context.Context, start, end time.Time) (cloudflare.LogpullReceivedIterator, error) { + return w.client.LogpullReceived(ctx, w.zoneID, start, end, cloudflare.LogpullReceivedOption{ + Fields: w.fields, + }) +} + +var getClient = func(apiKey, zoneID string, fields []string) (Client, error) { + c, err := cloudflare.NewWithAPIToken(apiKey) + if err != nil { + return nil, err + } + return &wrappedClient{ + client: c, + zoneID: zoneID, + fields: fields, + }, nil +} diff --git a/clients/pkg/promtail/targets/cloudflare/fields.go b/clients/pkg/promtail/targets/cloudflare/fields.go new file mode 100644 index 0000000000000..38948e875d4c4 --- /dev/null +++ b/clients/pkg/promtail/targets/cloudflare/fields.go @@ -0,0 +1,50 @@ +package cloudflare + +import ( + "fmt" +) + +type FieldsType string + +const ( + FieldsTypeDefault FieldsType = "default" + FieldsTypeMinimal FieldsType = "minimal" + FieldsTypeExtended FieldsType = "extended" + FieldsTypeAll FieldsType = "all" +) + +var ( + defaultFields = []string{ + "ClientIP", "ClientRequestHost", "ClientRequestMethod", "ClientRequestURI", "EdgeEndTimestamp", "EdgeResponseBytes", + "EdgeRequestHost", "EdgeResponseStatus", "EdgeStartTimestamp", "RayID", + } + minimalFields = append(defaultFields, []string{ + "ZoneID", "ClientSSLProtocol", "ClientRequestProtocol", "ClientRequestPath", "ClientRequestUserAgent", "ClientRequestReferer", + "EdgeColoCode", "ClientCountry", "CacheCacheStatus", "CacheResponseStatus", "EdgeResponseContentType", "SecurityLevel", + "WAFAction", "WAFProfile", "WAFRuleID", "WAFRuleMessage", "EdgeRateLimitID", "EdgeRateLimitAction", + }...) + extendedFields = append(minimalFields, []string{ + "ClientSSLCipher", "ClientASN", "ClientIPClass", "CacheResponseBytes", "EdgePathingOp", "EdgePathingSrc", "EdgePathingStatus", "ParentRayID", + "WorkerCPUTime", "WorkerStatus", "WorkerSubrequest", "WorkerSubrequestCount", "OriginIP", "OriginResponseStatus", "OriginSSLProtocol", + "OriginResponseHTTPExpires", "OriginResponseHTTPLastModified", + }...) + allFields = append(extendedFields, []string{ + "ClientRequestBytes", "ClientSrcPort", "ClientXRequestedWith", "CacheTieredFill", "EdgeResponseCompressionRatio", "EdgeServerIP", "FirewallMatchesSources", + "FirewallMatchesActions", "FirewallMatchesRuleIDs", "OriginResponseBytes", "OriginResponseTime", "ClientDeviceType", "WAFFlags", "WAFMatchedVar", "EdgeColoID", + }...) +) + +func Fields(t FieldsType) ([]string, error) { + switch t { + case FieldsTypeDefault: + return defaultFields, nil + case FieldsTypeMinimal: + return minimalFields, nil + case FieldsTypeExtended: + return extendedFields, nil + case FieldsTypeAll: + return allFields, nil + default: + return nil, fmt.Errorf("unknown fields type: %s", t) + } +} diff --git a/clients/pkg/promtail/targets/cloudflare/metrics.go b/clients/pkg/promtail/targets/cloudflare/metrics.go new file mode 100644 index 0000000000000..b6a37661348c4 --- /dev/null +++ b/clients/pkg/promtail/targets/cloudflare/metrics.go @@ -0,0 +1,38 @@ +package cloudflare + +import "github.com/prometheus/client_golang/prometheus" + +// Metrics holds a set of cloudflare metrics. +type Metrics struct { + reg prometheus.Registerer + + Entries prometheus.Counter + LastEnd prometheus.Gauge +} + +// NewMetrics creates a new set of cloudflare metrics. If reg is non-nil, the +// metrics will be registered. +func NewMetrics(reg prometheus.Registerer) *Metrics { + var m Metrics + m.reg = reg + + m.Entries = prometheus.NewCounter(prometheus.CounterOpts{ + Namespace: "promtail", + Name: "cloudflare_target_entries_total", + Help: "Total number of successful entries sent via the cloudflare target", + }) + m.LastEnd = prometheus.NewGauge(prometheus.GaugeOpts{ + Namespace: "promtail", + Name: "cloudflare_target_last_requested_end_timestamp", + Help: "The last cloudflare request end timestamp fetched. This allows to calculate how far the target is behind.", + }) + + if reg != nil { + reg.MustRegister( + m.Entries, + m.LastEnd, + ) + } + + return &m +} diff --git a/clients/pkg/promtail/targets/cloudflare/target.go b/clients/pkg/promtail/targets/cloudflare/target.go new file mode 100644 index 0000000000000..baba1343d4f0c --- /dev/null +++ b/clients/pkg/promtail/targets/cloudflare/target.go @@ -0,0 +1,255 @@ +package cloudflare + +import ( + "context" + "errors" + "strings" + "sync" + "time" + + "github.com/buger/jsonparser" + "github.com/cloudflare/cloudflare-go" + "github.com/go-kit/log" + "github.com/go-kit/log/level" + "github.com/grafana/dskit/backoff" + "github.com/grafana/dskit/concurrency" + "github.com/grafana/dskit/multierror" + "github.com/prometheus/common/model" + "go.uber.org/atomic" + + "github.com/grafana/loki/clients/pkg/promtail/api" + "github.com/grafana/loki/clients/pkg/promtail/positions" + "github.com/grafana/loki/clients/pkg/promtail/scrapeconfig" + "github.com/grafana/loki/clients/pkg/promtail/targets/target" + + "github.com/grafana/loki/pkg/logproto" +) + +// The minimun window size is 1 minute. +const minDelay = time.Minute + +var defaultBackoff = backoff.Config{ + MinBackoff: 1 * time.Second, + MaxBackoff: 10 * time.Second, + MaxRetries: 5, +} + +type Target struct { + logger log.Logger + handler api.EntryHandler + positions positions.Positions + config *scrapeconfig.CloudflareConfig + metrics *Metrics + + client Client + ctx context.Context + cancel context.CancelFunc + wg sync.WaitGroup + to time.Time // the end of the next pull interval + running *atomic.Bool + err error +} + +func NewTarget( + metrics *Metrics, + logger log.Logger, + handler api.EntryHandler, + position positions.Positions, + config *scrapeconfig.CloudflareConfig, +) (*Target, error) { + if err := validateConfig(config); err != nil { + return nil, err + } + fields, err := Fields(FieldsType(config.FieldsType)) + if err != nil { + return nil, err + } + client, err := getClient(config.APIToken, config.ZoneID, fields) + if err != nil { + return nil, err + } + pos, err := position.Get(positions.CursorKey(config.ZoneID)) + if err != nil { + return nil, err + } + to := time.Now() + if pos != 0 { + to = time.Unix(0, pos) + } + ctx, cancel := context.WithCancel(context.Background()) + t := &Target{ + logger: logger, + handler: handler, + positions: position, + config: config, + metrics: metrics, + + ctx: ctx, + cancel: cancel, + client: client, + to: to, + running: atomic.NewBool(false), + } + t.start() + return t, nil +} + +func (t *Target) start() { + t.wg.Add(1) + t.running.Store(true) + go func() { + defer func() { + t.wg.Done() + t.running.Store(false) + }() + for t.ctx.Err() == nil { + end := t.to + maxEnd := time.Now().Add(-minDelay) + if end.After(maxEnd) { + end = maxEnd + } + start := end.Add(-time.Duration(t.config.PullRange)) + + // Use background context for workers as we don't want to cancel half way through. + // In case of errors we stop the target, each worker has it's own retry logic. + if err := concurrency.ForEach(context.Background(), splitRequests(start, end, t.config.Workers), t.config.Workers, func(ctx context.Context, job interface{}) error { + request := job.(pullRequest) + return t.pull(ctx, request.start, request.end) + }); err != nil { + level.Error(t.logger).Log("msg", "failed to pull logs", "err", err, "start", start, "end", end) + t.err = err + return + } + + // Sets current timestamp metrics, move to the next interval and saves the position. + t.metrics.LastEnd.Set(float64(end.UnixNano()) / 1e9) + t.to = end.Add(time.Duration(t.config.PullRange)) + t.positions.Put(positions.CursorKey(t.config.ZoneID), t.to.UnixNano()) + + // If the next window can be fetched do it, if not sleep for a while. + // This is because Cloudflare logs should never be pulled between now-1m and now. + diff := t.to.Sub(time.Now().Add(-minDelay)) + if diff > 0 { + select { + case <-time.After(diff): + case <-t.ctx.Done(): + } + } + } + }() +} + +// pull pulls logs from cloudflare for a given time range. +// It will retry on errors. +func (t *Target) pull(ctx context.Context, start, end time.Time) error { + var ( + backoff = backoff.New(ctx, defaultBackoff) + errs = multierror.New() + it cloudflare.LogpullReceivedIterator + err error + ) + + for backoff.Ongoing() { + it, err = t.client.LogpullReceived(ctx, start, end) + if err != nil { + errs.Add(err) + backoff.Wait() + continue + } + defer it.Close() + for it.Next() { + if it.Err() != nil { + return it.Err() + } + line := it.Line() + ts, err := jsonparser.GetInt(line, "EdgeStartTimestamp") + if err != nil { + ts = time.Now().UnixNano() + } + t.handler.Chan() <- api.Entry{ + Labels: t.config.Labels.Clone(), + Entry: logproto.Entry{ + Timestamp: time.Unix(0, ts), + Line: string(line), + }, + } + t.metrics.Entries.Inc() + } + return nil + } + return errs.Err() +} + +func (t *Target) Stop() { + t.cancel() + t.wg.Wait() + t.handler.Stop() +} + +func (t *Target) Type() target.TargetType { + return target.CloudflareTargetType +} + +func (t *Target) DiscoveredLabels() model.LabelSet { + return nil +} + +func (t *Target) Labels() model.LabelSet { + return t.config.Labels +} + +func (t *Target) Ready() bool { + return t.running.Load() +} + +func (t *Target) Details() interface{} { + fields, _ := Fields(FieldsType(t.config.FieldsType)) + return map[string]string{ + "zone_id": t.config.ZoneID, + "error": t.err.Error(), + "position": t.positions.GetString(positions.CursorKey(t.config.ZoneID)), + "last_timestamp": t.to.String(), + "fields": strings.Join(fields, ","), + } +} + +type pullRequest struct { + start time.Time + end time.Time +} + +func splitRequests(start, end time.Time, workers int) []interface{} { + perWorker := end.Sub(start) / time.Duration(workers) + var requests []interface{} + for i := 0; i < workers; i++ { + r := pullRequest{ + start: start.Add(time.Duration(i) * perWorker), + end: start.Add(time.Duration(i+1) * perWorker), + } + // If the last worker is smaller than the others, we need to make sure it gets the last chunk. + if i == workers-1 && r.end != end { + r.end = end + } + requests = append(requests, r) + } + return requests +} + +func validateConfig(cfg *scrapeconfig.CloudflareConfig) error { + if cfg.FieldsType == "" { + cfg.FieldsType = string(FieldsTypeDefault) + } + if cfg.APIToken == "" { + return errors.New("cloudflare api token is required") + } + if cfg.ZoneID == "" { + return errors.New("cloudflare zone id is required") + } + if cfg.PullRange == 0 { + cfg.PullRange = model.Duration(time.Minute) + } + if cfg.Workers == 0 { + cfg.Workers = 3 + } + return nil +} diff --git a/clients/pkg/promtail/targets/cloudflare/target_test.go b/clients/pkg/promtail/targets/cloudflare/target_test.go new file mode 100644 index 0000000000000..f2c870e237303 --- /dev/null +++ b/clients/pkg/promtail/targets/cloudflare/target_test.go @@ -0,0 +1,244 @@ +package cloudflare + +import ( + "errors" + "os" + "sort" + "testing" + "time" + + "github.com/go-kit/log" + "github.com/grafana/loki/clients/pkg/promtail/client/fake" + "github.com/grafana/loki/clients/pkg/promtail/positions" + "github.com/grafana/loki/clients/pkg/promtail/scrapeconfig" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/model" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" +) + +func Test_CloudflareTarget(t *testing.T) { + var ( + w = log.NewSyncWriter(os.Stderr) + logger = log.NewLogfmtLogger(w) + cfg = &scrapeconfig.CloudflareConfig{ + APIToken: "foo", + ZoneID: "bar", + Labels: model.LabelSet{"job": "cloudflare"}, + PullRange: model.Duration(time.Minute), + } + end = time.Unix(0, time.Hour.Nanoseconds()) + start = time.Unix(0, time.Hour.Nanoseconds()-int64(cfg.PullRange)) + client = fake.New(func() {}) + cfClient = newFakeCloudflareClient() + ) + ps, err := positions.New(logger, positions.Config{ + SyncPeriod: 10 * time.Second, + PositionsFile: t.TempDir() + "/positions.yml", + }) + // set our end time to be the last time we have a position + ps.Put(positions.CursorKey(cfg.ZoneID), end.UnixNano()) + require.NoError(t, err) + + // setup response for the first pull batch of 1 minutes. + cfClient.On("LogpullReceived", mock.Anything, start, start.Add(time.Duration(cfg.PullRange/3))).Return(&fakeLogIterator{ + logs: []string{ + `{"EdgeStartTimestamp":1, "EdgeRequestHost":"foo.com"}`, + }, + }, nil) + cfClient.On("LogpullReceived", mock.Anything, start.Add(time.Duration(cfg.PullRange/3)), start.Add(time.Duration(2*cfg.PullRange/3))).Return(&fakeLogIterator{ + logs: []string{ + `{"EdgeStartTimestamp":2, "EdgeRequestHost":"bar.com"}`, + }, + }, nil) + cfClient.On("LogpullReceived", mock.Anything, start.Add(time.Duration(2*cfg.PullRange/3)), end).Return(&fakeLogIterator{ + logs: []string{ + `{"EdgeStartTimestamp":3, "EdgeRequestHost":"buzz.com"}`, + `{"EdgeRequestHost":"fuzz.com"}`, + }, + }, nil) + // setup empty response for the rest. + cfClient.On("LogpullReceived", mock.Anything, mock.Anything, mock.Anything).Return(&fakeLogIterator{ + logs: []string{}, + }, nil) + // replace the client. + getClient = func(apiKey, zoneID string, fields []string) (Client, error) { + return cfClient, nil + } + + ta, err := NewTarget(NewMetrics(prometheus.NewRegistry()), logger, client, ps, cfg) + require.NoError(t, err) + require.True(t, ta.Ready()) + + require.Eventually(t, func() bool { + return len(client.Received()) == 4 + }, 5*time.Second, 100*time.Millisecond) + + received := client.Received() + sort.Slice(received, func(i, j int) bool { + return received[i].Timestamp.After(received[j].Timestamp) + }) + for _, e := range received { + require.Equal(t, model.LabelValue("cloudflare"), e.Labels["job"]) + } + require.WithinDuration(t, time.Now(), received[0].Timestamp, time.Minute) // no timestamp default to now. + require.Equal(t, `{"EdgeRequestHost":"fuzz.com"}`, received[0].Line) + + require.Equal(t, `{"EdgeStartTimestamp":3, "EdgeRequestHost":"buzz.com"}`, received[1].Line) + require.Equal(t, time.Unix(0, 3), received[1].Timestamp) + require.Equal(t, `{"EdgeStartTimestamp":2, "EdgeRequestHost":"bar.com"}`, received[2].Line) + require.Equal(t, time.Unix(0, 2), received[2].Timestamp) + require.Equal(t, `{"EdgeStartTimestamp":1, "EdgeRequestHost":"foo.com"}`, received[3].Line) + require.Equal(t, time.Unix(0, 1), received[3].Timestamp) + cfClient.AssertExpectations(t) + ta.Stop() + ps.Stop() + // Make sure we save the last position. + newPos, _ := ps.Get(positions.CursorKey(cfg.ZoneID)) + require.Greater(t, newPos, end.UnixNano()) +} + +func Test_CloudflareTargetError(t *testing.T) { + var ( + w = log.NewSyncWriter(os.Stderr) + logger = log.NewLogfmtLogger(w) + cfg = &scrapeconfig.CloudflareConfig{ + APIToken: "foo", + ZoneID: "bar", + Labels: model.LabelSet{"job": "cloudflare"}, + PullRange: model.Duration(time.Minute), + } + end = time.Unix(0, time.Hour.Nanoseconds()) + client = fake.New(func() {}) + cfClient = newFakeCloudflareClient() + ) + ps, err := positions.New(logger, positions.Config{ + SyncPeriod: 10 * time.Second, + PositionsFile: t.TempDir() + "/positions.yml", + }) + // retries as fast as possible. + defaultBackoff.MinBackoff = 0 + defaultBackoff.MaxBackoff = 0 + + // set our end time to be the last time we have a position + ps.Put(positions.CursorKey(cfg.ZoneID), end.UnixNano()) + require.NoError(t, err) + + // setup errors for all retries + cfClient.On("LogpullReceived", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("no logs")) + // replace the client. + getClient = func(apiKey, zoneID string, fields []string) (Client, error) { + return cfClient, nil + } + + ta, err := NewTarget(NewMetrics(prometheus.NewRegistry()), logger, client, ps, cfg) + require.NoError(t, err) + require.True(t, ta.Ready()) + + // wait for the target to be stopped. + require.Eventually(t, func() bool { + return !ta.Ready() + }, 5*time.Second, 100*time.Millisecond) + + require.Len(t, client.Received(), 0) + require.GreaterOrEqual(t, cfClient.CallCount(), 5) + require.NotEmpty(t, ta.Details().(map[string]string)["error"]) + ta.Stop() + ps.Stop() + + // Make sure we save the last position. + newEnd, _ := ps.Get(positions.CursorKey(cfg.ZoneID)) + require.Equal(t, newEnd, end.UnixNano()) +} + +func Test_validateConfig(t *testing.T) { + tests := []struct { + in *scrapeconfig.CloudflareConfig + out *scrapeconfig.CloudflareConfig + wantErr bool + }{ + { + &scrapeconfig.CloudflareConfig{ + APIToken: "foo", + ZoneID: "bar", + }, + &scrapeconfig.CloudflareConfig{ + APIToken: "foo", + ZoneID: "bar", + Workers: 3, + PullRange: model.Duration(time.Minute), + FieldsType: string(FieldsTypeDefault), + }, + false, + }, + { + &scrapeconfig.CloudflareConfig{ + APIToken: "foo", + }, + nil, + true, + }, + { + &scrapeconfig.CloudflareConfig{ + ZoneID: "foo", + }, + nil, + true, + }, + } + for _, tt := range tests { + t.Run("", func(t *testing.T) { + err := validateConfig(tt.in) + if tt.wantErr { + require.Error(t, err) + return + } + require.Equal(t, tt.out, tt.in) + }) + } +} + +func Test_splitRequests(t *testing.T) { + tests := []struct { + start time.Time + end time.Time + want []interface{} + }{ + // perfectly divisible + { + time.Unix(0, 0), + time.Unix(0, int64(time.Minute)), + []interface{}{ + pullRequest{start: time.Unix(0, 0), end: time.Unix(0, int64(time.Minute/3))}, + pullRequest{start: time.Unix(0, int64(time.Minute/3)), end: time.Unix(0, int64(time.Minute*2/3))}, + pullRequest{start: time.Unix(0, int64(time.Minute*2/3)), end: time.Unix(0, int64(time.Minute))}, + }, + }, + // not divisible + { + time.Unix(0, 0), + time.Unix(0, int64(time.Minute+1)), + []interface{}{ + pullRequest{start: time.Unix(0, 0), end: time.Unix(0, int64(time.Minute/3))}, + pullRequest{start: time.Unix(0, int64(time.Minute/3)), end: time.Unix(0, int64(time.Minute*2/3))}, + pullRequest{start: time.Unix(0, int64(time.Minute*2/3)), end: time.Unix(0, int64(time.Minute+1))}, + }, + }, + } + for _, tt := range tests { + t.Run("", func(t *testing.T) { + got := splitRequests(tt.start, tt.end, 3) + if !assert.Equal(t, tt.want, got) { + for i := range got { + if !assert.Equal(t, tt.want[i].(pullRequest).start, got[i].(pullRequest).start) { + t.Logf("expected i:%d start: %d , got: %d", i, tt.want[i].(pullRequest).start.UnixNano(), got[i].(pullRequest).start.UnixNano()) + } + if !assert.Equal(t, tt.want[i].(pullRequest).end, got[i].(pullRequest).end) { + t.Logf("expected i:%d end: %d , got: %d", i, tt.want[i].(pullRequest).end.UnixNano(), got[i].(pullRequest).end.UnixNano()) + } + } + } + }) + } +} diff --git a/clients/pkg/promtail/targets/cloudflare/targetmanager.go b/clients/pkg/promtail/targets/cloudflare/targetmanager.go new file mode 100644 index 0000000000000..c60fd6577a5f3 --- /dev/null +++ b/clients/pkg/promtail/targets/cloudflare/targetmanager.go @@ -0,0 +1,81 @@ +package cloudflare + +import ( + "github.com/go-kit/log" + + "github.com/grafana/loki/clients/pkg/logentry/stages" + "github.com/grafana/loki/clients/pkg/promtail/api" + "github.com/grafana/loki/clients/pkg/promtail/positions" + "github.com/grafana/loki/clients/pkg/promtail/scrapeconfig" + "github.com/grafana/loki/clients/pkg/promtail/targets/target" +) + +// TargetManager manages a series of cloudflare targets. +type TargetManager struct { + logger log.Logger + targets map[string]*Target +} + +// NewTargetManager creates a new cloudflare target managers. +func NewTargetManager( + metrics *Metrics, + logger log.Logger, + positions positions.Positions, + pushClient api.EntryHandler, + scrapeConfigs []scrapeconfig.Config, +) (*TargetManager, error) { + tm := &TargetManager{ + logger: logger, + targets: make(map[string]*Target), + } + for _, cfg := range scrapeConfigs { + if cfg.CloudflareConfig == nil { + continue + } + pipeline, err := stages.NewPipeline(log.With(logger, "component", "cloudflare_pipeline"), cfg.PipelineStages, &cfg.JobName, metrics.reg) + if err != nil { + return nil, err + } + t, err := NewTarget(metrics, log.With(logger, "target", "cloudflare"), pipeline.Wrap(pushClient), positions, cfg.CloudflareConfig) + if err != nil { + return nil, err + } + tm.targets[cfg.JobName] = t + } + + return tm, nil +} + +// Ready returns true if at least one cloudflare target is active. +func (tm *TargetManager) Ready() bool { + for _, t := range tm.targets { + if t.Ready() { + return true + } + } + return false +} + +func (tm *TargetManager) Stop() { + for _, t := range tm.targets { + t.Stop() + } +} + +func (tm *TargetManager) ActiveTargets() map[string][]target.Target { + result := make(map[string][]target.Target, len(tm.targets)) + for k, v := range tm.targets { + if v.Ready() { + result[k] = []target.Target{v} + } + } + return result +} + +func (tm *TargetManager) AllTargets() map[string][]target.Target { + result := make(map[string][]target.Target, len(tm.targets)) + for k, v := range tm.targets { + result[k] = []target.Target{v} + } + return result +} diff --git a/clients/pkg/promtail/targets/cloudflare/util_test.go b/clients/pkg/promtail/targets/cloudflare/util_test.go new file mode 100644 index 0000000000000..4d9077e12fa77 --- /dev/null +++ b/clients/pkg/promtail/targets/cloudflare/util_test.go @@ -0,0 +1,53 @@ +package cloudflare + +import ( + "context" + "time" + + "github.com/cloudflare/cloudflare-go" + "github.com/stretchr/testify/mock" +) + +type fakeCloudflareClient struct { + mock.Mock +} + +func (f *fakeCloudflareClient) CallCount() int { + var actualCalls int + for _, call := range f.Calls { + if call.Method == "LogpullReceived" { + actualCalls++ + } + } + return actualCalls +} + +type fakeLogIterator struct { + logs []string + current string +} + +func (f *fakeLogIterator) Next() bool { + if len(f.logs) == 0 { + return false + } + f.current = f.logs[0] + f.logs = f.logs[1:] + return true +} +func (f *fakeLogIterator) Err() error { return nil } +func (f *fakeLogIterator) Line() []byte { return []byte(f.current) } +func (f *fakeLogIterator) Fields() (map[string]string, error) { return nil, nil } +func (f *fakeLogIterator) Close() error { return nil } + +func newFakeCloudflareClient() *fakeCloudflareClient { + return &fakeCloudflareClient{} +} + +func (f *fakeCloudflareClient) LogpullReceived(ctx context.Context, start, end time.Time) (cloudflare.LogpullReceivedIterator, error) { + r := f.Called(ctx, start, end) + if r.Get(0) != nil { + return r.Get(0).(cloudflare.LogpullReceivedIterator), nil + } + return nil, r.Error(1) +} diff --git a/clients/pkg/promtail/targets/journal/journaltarget.go b/clients/pkg/promtail/targets/journal/journaltarget.go index 11ad3556b783c..3913d8911480f 100644 --- a/clients/pkg/promtail/targets/journal/journaltarget.go +++ b/clients/pkg/promtail/targets/journal/journaltarget.go @@ -127,7 +127,7 @@ func NewJournalTarget( func journalTargetWithReader( logger log.Logger, handler api.EntryHandler, - positions positions.Positions, + pos positions.Positions, jobName string, relabelConfig []*relabel.Config, targetConfig *scrapeconfig.JournalTargetConfig, @@ -135,8 +135,8 @@ func journalTargetWithReader( entryFunc journalEntryFunc, ) (*JournalTarget, error) { - positionPath := fmt.Sprintf("journal-%s", jobName) - position := positions.GetString(positionPath) + positionPath := positions.CursorKey(jobName) + position := pos.GetString(positionPath) if readerFunc == nil { readerFunc = defaultJournalReaderFunc @@ -149,7 +149,7 @@ func journalTargetWithReader( t := &JournalTarget{ logger: logger, handler: handler, - positions: positions, + positions: pos, positionPath: positionPath, relabelConfig: relabelConfig, labels: targetConfig.Labels, diff --git a/clients/pkg/promtail/targets/journal/journaltarget_test.go b/clients/pkg/promtail/targets/journal/journaltarget_test.go index 9524fc5610314..8ebf210111441 100644 --- a/clients/pkg/promtail/targets/journal/journaltarget_test.go +++ b/clients/pkg/promtail/targets/journal/journaltarget_test.go @@ -170,7 +170,6 @@ func TestJournalTarget_JSON(t *testing.T) { for i := 0; i < 10; i++ { require.Equal(t, expectMsg, client.Received()[i].Line) } - } func TestJournalTarget_Since(t *testing.T) { @@ -265,7 +264,7 @@ func TestJournalTarget_Cursor_NotTooOld(t *testing.T) { if err != nil { t.Fatal(err) } - ps.PutString("journal-test", "foobar") + ps.PutString(positions.CursorKey("test"), "foobar") client := fake.New(func() {}) diff --git a/clients/pkg/promtail/targets/manager.go b/clients/pkg/promtail/targets/manager.go index e5afec51faaf2..9944174b87c1a 100644 --- a/clients/pkg/promtail/targets/manager.go +++ b/clients/pkg/promtail/targets/manager.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/loki/clients/pkg/promtail/client" "github.com/grafana/loki/clients/pkg/promtail/positions" "github.com/grafana/loki/clients/pkg/promtail/scrapeconfig" + "github.com/grafana/loki/clients/pkg/promtail/targets/cloudflare" "github.com/grafana/loki/clients/pkg/promtail/targets/file" "github.com/grafana/loki/clients/pkg/promtail/targets/gcplog" "github.com/grafana/loki/clients/pkg/promtail/targets/gelf" @@ -33,6 +34,7 @@ const ( WindowsEventsConfigs = "windowsEventsConfigs" KafkaConfigs = "kafkaConfigs" GelfConfigs = "gelfConfigs" + CloudflareConfigs = "cloudflareConfigs" ) type targetManager interface { @@ -90,6 +92,8 @@ func NewTargetManagers( targetScrapeConfigs[KafkaConfigs] = append(targetScrapeConfigs[KafkaConfigs], cfg) case cfg.GelfConfig != nil: targetScrapeConfigs[GelfConfigs] = append(targetScrapeConfigs[GelfConfigs], cfg) + case cfg.CloudflareConfig != nil: + targetScrapeConfigs[CloudflareConfigs] = append(targetScrapeConfigs[CloudflareConfigs], cfg) default: return nil, fmt.Errorf("no valid target scrape config defined for %q", cfg.JobName) } @@ -110,10 +114,11 @@ func NewTargetManagers( } var ( - fileMetrics *file.Metrics - syslogMetrics *syslog.Metrics - gcplogMetrics *gcplog.Metrics - gelfMetrics *gelf.Metrics + fileMetrics *file.Metrics + syslogMetrics *syslog.Metrics + gcplogMetrics *gcplog.Metrics + gelfMetrics *gelf.Metrics + cloudflareMetrics *cloudflare.Metrics ) if len(targetScrapeConfigs[FileScrapeConfigs]) > 0 { fileMetrics = file.NewMetrics(reg) @@ -127,6 +132,9 @@ func NewTargetManagers( if len(targetScrapeConfigs[GelfConfigs]) > 0 { gelfMetrics = gelf.NewMetrics(reg) } + if len(targetScrapeConfigs[CloudflareConfigs]) > 0 { + cloudflareMetrics = cloudflare.NewMetrics(reg) + } for target, scrapeConfigs := range targetScrapeConfigs { switch target { @@ -214,7 +222,16 @@ func NewTargetManagers( return nil, errors.Wrap(err, "failed to make gelf target manager") } targetManagers = append(targetManagers, gelfTargetManager) - + case CloudflareConfigs: + pos, err := getPositionFile() + if err != nil { + return nil, err + } + cfTargetManager, err := cloudflare.NewTargetManager(cloudflareMetrics, logger, pos, client, scrapeConfigs) + if err != nil { + return nil, errors.Wrap(err, "failed to make cloudflare target manager") + } + targetManagers = append(targetManagers, cfTargetManager) default: return nil, errors.New("unknown scrape config") } diff --git a/clients/pkg/promtail/targets/target/target.go b/clients/pkg/promtail/targets/target/target.go index 29511732801c0..8acf6aad618db 100644 --- a/clients/pkg/promtail/targets/target/target.go +++ b/clients/pkg/promtail/targets/target/target.go @@ -35,6 +35,9 @@ const ( // GelfTargetType is a gelf target GelfTargetType = TargetType("gelf") + + // CloudflareTargetType is a Cloudflare target + CloudflareTargetType = TargetType("Cloudflare") ) // Target is a promtail scrape target diff --git a/docs/sources/clients/promtail/configuration.md b/docs/sources/clients/promtail/configuration.md index f6ad81a822b1a..24a87187da7c2 100644 --- a/docs/sources/clients/promtail/configuration.md +++ b/docs/sources/clients/promtail/configuration.md @@ -328,6 +328,9 @@ job_name: # Describes how to receive logs from gelf client. [gelf: ] +# Configuration describing how to pull logs from Cloudflare. +[cloudflare: ] + # Describes how to relabel targets to determine if they should # be processed. relabel_configs: @@ -1028,6 +1031,133 @@ use_incoming_timestamp: To keep discovered labels to your logs use the [relabel_configs](#relabel_configs) section. +### Cloudflare + +The `cloudflare` block configures Promtail to pull logs from the Cloudflare +[Logpull API](https://developers.cloudflare.com/logs/logpull). + +These logs contain data related to the connecting client, the request path through the Cloudflare network, and the response from the origin web server. This data is useful for enriching existing logs on an origin server. + +```yaml +# The Cloudflare API token to use. (Required) +# You can create a new token by visiting your [Cloudflare profile](https://dash.cloudflare.com/profile/api-tokens). +api_token: + +# The Cloudflare zone id to pull logs for. (Required) +zone_id: + +# The time range to pull logs for. +[pull_range: | default = 1m] + +# The quantity of workers that will pull logs. +[workers: | default = 3] + +# The type list of fields to fetch for logs. +# Supported values: default, minimal, extended, all. +[fields_type: | default = default] + +# Label map to add to every log message. +labels: + [ : ... ] + +``` + +By default Promtail fetches logs with the default set of fields. +Here are the different set of fields type available and the fields they include : + +- `default` includes `"ClientIP", "ClientRequestHost", "ClientRequestMethod", "ClientRequestURI", "EdgeEndTimestamp", "EdgeResponseBytes", +"EdgeRequestHost", "EdgeResponseStatus", "EdgeStartTimestamp", "RayID"` + +- `minimal` includes all `default` fields and adds `"ZoneID", "ClientSSLProtocol", "ClientRequestProtocol", "ClientRequestPath", "ClientRequestUserAgent", "ClientRequestReferer", +"EdgeColoCode", "ClientCountry", "CacheCacheStatus", "CacheResponseStatus", "EdgeResponseContentType` + +- `extended` includes all `minimal`fields and adds `"ClientSSLCipher", "ClientASN", "ClientIPClass", "CacheResponseBytes", "EdgePathingOp", "EdgePathingSrc", "EdgePathingStatus", "ParentRayID", +"WorkerCPUTime", "WorkerStatus", "WorkerSubrequest", "WorkerSubrequestCount", "OriginIP", "OriginResponseStatus", "OriginSSLProtocol", +"OriginResponseHTTPExpires", "OriginResponseHTTPLastModified"` + +- `all` includes all `extended` fields and adds `"ClientRequestBytes", "ClientSrcPort", "ClientXRequestedWith", "CacheTieredFill", "EdgeResponseCompressionRatio", "EdgeServerIP", "FirewallMatchesSources", +"FirewallMatchesActions", "FirewallMatchesRuleIDs", "OriginResponseBytes", "OriginResponseTime", "ClientDeviceType", "WAFFlags", "WAFMatchedVar", "EdgeColoID"` + +To learn more about each field and its value, refer to the [Cloudflare documentation](https://developers.cloudflare.com/logs/reference/log-fields/zone/http_requests). + +Promtail saves the last successfully-fetched timestamp in the position file. +If a position is found in the file for a given zone ID, Promtail will restart pulling logs +from that position. When no position is found, Promtail will start pulling logs from the current time. + +Promtail fetches logs using multiple workers (configurable via `workers`) which request the last available pull range +(configured via `pull_range`) repeatedly. Verify the last timestamp fetched by Promtail using the `cloudflare_target_last_requested_end_timestamp` metric. +It is possible for Promtail to fall behind due to having too many log lines to process for each pull. +Adding more workers, decreasing the pull range, or decreasing the quantity of fields fetched can mitigate this performance issue. + +All Cloudflare logs are in JSON. Here is an example: + +```json +{ + "CacheCacheStatus": "miss", + "CacheResponseBytes": 8377, + "CacheResponseStatus": 200, + "CacheTieredFill": false, + "ClientASN": 786, + "ClientCountry": "gb", + "ClientDeviceType": "desktop", + "ClientIP": "100.100.5.5", + "ClientIPClass": "noRecord", + "ClientRequestBytes": 2691, + "ClientRequestHost": "www.foo.com", + "ClientRequestMethod": "GET", + "ClientRequestPath": "/comments/foo/", + "ClientRequestProtocol": "HTTP/1.0", + "ClientRequestReferer": "https://www.foo.com/foo/168855/?offset=8625", + "ClientRequestURI": "/foo/15248108/", + "ClientRequestUserAgent": "some bot", + "ClientSSLCipher": "ECDHE-ECDSA-AES128-GCM-SHA256", + "ClientSSLProtocol": "TLSv1.2", + "ClientSrcPort": 39816, + "ClientXRequestedWith": "", + "EdgeColoCode": "MAN", + "EdgeColoID": 341, + "EdgeEndTimestamp": 1637336610671000000, + "EdgePathingOp": "wl", + "EdgePathingSrc": "macro", + "EdgePathingStatus": "nr", + "EdgeRateLimitAction": "", + "EdgeRateLimitID": 0, + "EdgeRequestHost": "www.foo.com", + "EdgeResponseBytes": 14878, + "EdgeResponseCompressionRatio": 1, + "EdgeResponseContentType": "text/html", + "EdgeResponseStatus": 200, + "EdgeServerIP": "8.8.8.8", + "EdgeStartTimestamp": 1637336610517000000, + "FirewallMatchesActions": [], + "FirewallMatchesRuleIDs": [], + "FirewallMatchesSources": [], + "OriginIP": "8.8.8.8", + "OriginResponseBytes": 0, + "OriginResponseHTTPExpires": "", + "OriginResponseHTTPLastModified": "", + "OriginResponseStatus": 200, + "OriginResponseTime": 123000000, + "OriginSSLProtocol": "TLSv1.2", + "ParentRayID": "00", + "RayID": "6b0a...", + "SecurityLevel": "med", + "WAFAction": "unknown", + "WAFFlags": "0", + "WAFMatchedVar": "", + "WAFProfile": "unknown", + "WAFRuleID": "", + "WAFRuleMessage": "", + "WorkerCPUTime": 0, + "WorkerStatus": "unknown", + "WorkerSubrequest": false, + "WorkerSubrequestCount": 0, + "ZoneID": 1234 +} +``` + +You can leverage [pipeline stages](pipeline_stages) if, for example, you want to parse the JSON log line and extract more labels or change the log line format. + ### relabel_configs Relabeling is a powerful tool to dynamically rewrite the label set of a target diff --git a/docs/sources/clients/promtail/scraping.md b/docs/sources/clients/promtail/scraping.md index a872e57434fe6..1283ed1f9f1c1 100644 --- a/docs/sources/clients/promtail/scraping.md +++ b/docs/sources/clients/promtail/scraping.md @@ -341,6 +341,25 @@ scrape_configs: target_label: facility ``` +## Cloudflare + +Promtail supports pulling HTTP log messages from Cloudflare using the [Logpull API](https://developers.cloudflare.com/logs/logpull). +The Cloudflare targets can be configured with a `cloudflare` block: + +```yaml +scrape_configs: +- job_name: cloudflare + cloudflare: + api_token: REDACTED + zone_id: REDACTED + fields_type: all + labels: + job: cloudflare-foo.com +``` + +Only `api_token` and `zone_id` are required. +Refer to the [Cloudfare](../../configuration/#cloudflare) configuration section for details. + ## Relabeling Each `scrape_configs` entry can contain a `relabel_configs` stanza. diff --git a/go.mod b/go.mod index 8efc265b594b8..7b1c2104a8124 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,6 @@ require ( github.com/felixge/fgprof v0.9.1 github.com/fluent/fluent-bit-go v0.0.0-20190925192703-ea13c021720c github.com/fsouza/fake-gcs-server v1.7.0 - github.com/go-kit/kit v0.12.0 // indirect github.com/go-kit/log v0.2.0 github.com/go-logfmt/logfmt v0.5.1 github.com/go-redis/redis/v8 v8.11.4 @@ -64,7 +63,6 @@ require ( github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f github.com/ncw/swift v1.0.52 github.com/oklog/run v1.1.0 - github.com/oklog/ulid v1.3.1 // indirect github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e github.com/opentracing-contrib/go-stdlib v1.0.0 github.com/opentracing/opentracing-go v1.2.0 @@ -161,6 +159,7 @@ require ( github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect github.com/felixge/httpsnoop v1.0.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/go-kit/kit v0.12.0 // indirect github.com/go-logr/logr v1.0.0 // indirect github.com/go-openapi/analysis v0.20.0 // indirect github.com/go-openapi/errors v0.20.0 // indirect @@ -174,7 +173,6 @@ require ( github.com/go-openapi/validate v0.20.2 // indirect github.com/go-stack/stack v1.8.0 // indirect github.com/go-zookeeper/zk v1.0.2 // indirect - github.com/gofrs/flock v0.7.1 // indirect github.com/gogo/googleapis v1.4.0 // indirect github.com/gogo/status v1.1.0 // indirect github.com/golang-jwt/jwt/v4 v4.0.0 // indirect @@ -224,6 +222,7 @@ require ( github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect github.com/pierrec/lz4 v2.6.1+incompatible // indirect @@ -278,6 +277,11 @@ require ( sigs.k8s.io/yaml v1.2.0 // indirect ) +require ( + github.com/cloudflare/cloudflare-go v0.27.0 + github.com/gofrs/flock v0.7.1 // indirect +) + // Upgrade to run with gRPC 1.3.0 and above. replace github.com/sercand/kuberesolver => github.com/sercand/kuberesolver v2.4.0+incompatible @@ -308,3 +312,5 @@ replace github.com/thanos-io/thanos v0.22.0 => github.com/thanos-io/thanos v0.19 // We use a fork of Graylog to avoid leaking goroutine when closing the Promtail target. replace gopkg.in/Graylog2/go-gelf.v2 => github.com/grafana/go-gelf v0.0.0-20211112153804-126646b86de8 + +replace github.com/cloudflare/cloudflare-go => github.com/cyriltovena/cloudflare-go v0.27.1-0.20211118103540-ff77400bcb93 diff --git a/go.sum b/go.sum index d78675ad6c28c..e9ec33ccf75ad 100644 --- a/go.sum +++ b/go.sum @@ -487,6 +487,8 @@ github.com/cristalhq/hedgedhttp v0.6.1/go.mod h1:XkqWU6qVMutbhW68NnzjWrGtH8NUx1U github.com/cucumber/godog v0.8.1/go.mod h1:vSh3r/lM+psC1BPXvdkSEuNjmXfpVqrMGYAElF6hxnA= github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/cyriltovena/cloudflare-go v0.27.1-0.20211118103540-ff77400bcb93 h1:PEBeRA25eDfHWkXNJs0HOnMhjIuKMcxKg/Z3VeuoRbU= +github.com/cyriltovena/cloudflare-go v0.27.1-0.20211118103540-ff77400bcb93/go.mod h1:sPWL/lIC6biLEdyGZwBQ1rGQKF1FhM7N60fuNiFdYTI= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= @@ -1329,6 +1331,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -1456,6 +1459,7 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -2103,6 +2107,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= diff --git a/pkg/chunkenc/pool_test.go b/pkg/chunkenc/pool_test.go index 40b1fde17f9ec..6b15b5496b863 100644 --- a/pkg/chunkenc/pool_test.go +++ b/pkg/chunkenc/pool_test.go @@ -54,6 +54,6 @@ func TestPool(t *testing.T) { runtime.GC() return runtime.NumGoroutine() <= 50 }, 5*time.Second, 10*time.Millisecond) { - pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) + _ = pprof.Lookup("goroutine").WriteTo(os.Stdout, 1) } } diff --git a/pkg/querier/queryrange/stats.go b/pkg/querier/queryrange/stats.go index e5b84476cc039..9306d2cad94a9 100644 --- a/pkg/querier/queryrange/stats.go +++ b/pkg/querier/queryrange/stats.go @@ -29,7 +29,7 @@ var ( logql.RecordMetrics(data.ctx, data.params, data.status, *data.statistics, data.result) }) // StatsHTTPMiddleware is an http middleware to record stats for query_range filter. - StatsHTTPMiddleware middleware.Interface = statsHTTPMiddleware(defaultMetricRecorder) + StatsHTTPMiddleware = statsHTTPMiddleware(defaultMetricRecorder) ) type metricRecorder interface { diff --git a/pkg/ruler/storage/wal/wal.go b/pkg/ruler/storage/wal/wal.go index c50244c646096..38fd98c4e1f10 100644 --- a/pkg/ruler/storage/wal/wal.go +++ b/pkg/ruler/storage/wal/wal.go @@ -176,9 +176,7 @@ func (w *Storage) replayWAL() error { } func (w *Storage) loadWAL(r *wal.Reader) (err error) { - var ( - dec record.Decoder - ) + var dec record.Decoder var ( decoded = make(chan interface{}, 10) @@ -237,7 +235,7 @@ func (w *Storage) loadWAL(r *wal.Reader) (err error) { } }() - var biggestRef = chunks.HeadSeriesRef(w.ref.Load()) + biggestRef := chunks.HeadSeriesRef(w.ref.Load()) for d := range decoded { switch v := d.(type) { @@ -517,13 +515,11 @@ func (w *Storage) recordSize() { func dirSize(path string) (int64, error) { var size int64 err := filepath.Walk(path, func(_ string, info os.FileInfo, err error) error { - if err != nil { return err } if !info.IsDir() { - size += info.Size() } diff --git a/vendor/github.com/cloudflare/cloudflare-go/.gitignore b/vendor/github.com/cloudflare/cloudflare-go/.gitignore new file mode 100644 index 0000000000000..d817ce7719f72 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/.gitignore @@ -0,0 +1,4 @@ +.idea +.vscode/ +cmd/flarectl/dist/ +cmd/flarectl/flarectl diff --git a/vendor/github.com/cloudflare/cloudflare-go/.golintci.yaml b/vendor/github.com/cloudflare/cloudflare-go/.golintci.yaml new file mode 100644 index 0000000000000..7f311855e8c04 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/.golintci.yaml @@ -0,0 +1,19 @@ +run: + + # timeout for analysis, e.g. 30s, 5m, default is 1m + timeout: 1m + + # exit code when at least one issue was found, default is 1 + issues-exit-code: 1 + + # include test files or not, default is true + tests: true + + # default is true. Enables skipping of directories: + # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ + skip-dirs-use-default: true + + modules-download-mode: readonly + +output: + format: colored-line-number diff --git a/vendor/github.com/cloudflare/cloudflare-go/CODE_OF_CONDUCT.md b/vendor/github.com/cloudflare/cloudflare-go/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000000..bfbc69d229ddf --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/CODE_OF_CONDUCT.md @@ -0,0 +1,77 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at ggalow@cloudflare.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq + diff --git a/vendor/github.com/cloudflare/cloudflare-go/LICENSE b/vendor/github.com/cloudflare/cloudflare-go/LICENSE new file mode 100644 index 0000000000000..20451115dd8e1 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/LICENSE @@ -0,0 +1,26 @@ +Copyright (c) 2015-2021, Cloudflare. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/cloudflare/cloudflare-go/README.md b/vendor/github.com/cloudflare/cloudflare-go/README.md new file mode 100644 index 0000000000000..f2fff48d85e35 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/README.md @@ -0,0 +1,114 @@ +# cloudflare-go + +[![Go Reference](https://pkg.go.dev/badge/github.com/cloudflare/cloudflare-go.svg)](https://pkg.go.dev/github.com/cloudflare/cloudflare-go) +![Test](https://github.com/cloudflare/cloudflare-go/workflows/Test/badge.svg) +[![Go Report Card](https://goreportcard.com/badge/github.com/cloudflare/cloudflare-go?style=flat-square)](https://goreportcard.com/report/github.com/cloudflare/cloudflare-go) + +> **Note**: This library is under active development as we expand it to cover +> our (expanding!) API. Consider the public API of this package a little +> unstable as we work towards a v1.0. + +A Go library for interacting with +[Cloudflare's API v4](https://api.cloudflare.com/). This library allows you to: + +* Manage and automate changes to your DNS records within Cloudflare +* Manage and automate changes to your zones (domains) on Cloudflare, including + adding new zones to your account +* List and modify the status of WAF (Web Application Firewall) rules for your + zones +* Fetch Cloudflare's IP ranges for automating your firewall whitelisting + +A command-line client, [flarectl](cmd/flarectl), is also available as part of +this project. + +## Features + +The current feature list includes: + +* [x] Cache purging +* [x] Cloudflare IPs +* [x] Custom hostnames +* [x] DNS Records +* [x] Firewall (partial) +* [x] [Keyless SSL](https://blog.cloudflare.com/keyless-ssl-the-nitty-gritty-technical-details/) +* [x] [Load Balancing](https://blog.cloudflare.com/introducing-load-balancing-intelligent-failover-with-cloudflare/) +* [x] [Logpush Jobs](https://developers.cloudflare.com/logs/logpush/) +* [ ] Organization Administration +* [x] [Origin CA](https://blog.cloudflare.com/universal-ssl-encryption-all-the-way-to-the-origin-for-free/) +* [x] [Railgun](https://www.cloudflare.com/railgun/) administration +* [x] Rate Limiting +* [x] User Administration (partial) +* [x] Virtual DNS Management +* [x] Web Application Firewall (WAF) +* [x] Zone Lockdown and User-Agent Block rules +* [x] Zones +* [x] Workers KV +* [x] Notifications +* [x] Gateway Locations + +Pull Requests are welcome, but please open an issue (or comment in an existing +issue) to discuss any non-trivial changes before submitting code. + +## Installation + +You need a working Go environment. + +``` +go get github.com/cloudflare/cloudflare-go +``` + +## Getting Started + +```go +package main + +import ( + "context" + "fmt" + "log" + "os" + + "github.com/cloudflare/cloudflare-go" +) + +func main() { + // Construct a new API object + api, err := cloudflare.New(os.Getenv("CLOUDFLARE_API_KEY"), os.Getenv("CLOUDFLARE_API_EMAIL")) + if err != nil { + log.Fatal(err) + } + + // Most API calls require a Context + ctx := context.Background() + + // Fetch user details on the account + u, err := api.UserDetails(ctx) + if err != nil { + log.Fatal(err) + } + // Print user details + fmt.Println(u) + + // Fetch the zone ID + id, err := api.ZoneIDByName("example.com") // Assuming example.com exists in your Cloudflare account already + if err != nil { + log.Fatal(err) + } + + // Fetch zone details + zone, err := api.ZoneDetails(ctx, id) + if err != nil { + log.Fatal(err) + } + // Print zone details + fmt.Println(zone) +} +``` + +Also refer to the +[API documentation](https://pkg.go.dev/github.com/cloudflare/cloudflare-go) for +how to use this package in-depth. + +# License + +BSD licensed. See the [LICENSE](LICENSE) file for details. diff --git a/vendor/github.com/cloudflare/cloudflare-go/access_application.go b/vendor/github.com/cloudflare/cloudflare-go/access_application.go new file mode 100644 index 0000000000000..bc83fda7adc02 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/access_application.go @@ -0,0 +1,289 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/pkg/errors" +) + +// AccessApplicationType represents the application type. +type AccessApplicationType string + +// These constants represent all valid application types. +const ( + SelfHosted AccessApplicationType = "self_hosted" + SSH AccessApplicationType = "ssh" + VNC AccessApplicationType = "vnc" + File AccessApplicationType = "file" +) + +// AccessApplication represents an Access application. +type AccessApplication struct { + ID string `json:"id,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + AUD string `json:"aud,omitempty"` + Name string `json:"name"` + Domain string `json:"domain"` + Type AccessApplicationType `json:"type,omitempty"` + SessionDuration string `json:"session_duration,omitempty"` + AutoRedirectToIdentity bool `json:"auto_redirect_to_identity,omitempty"` + EnableBindingCookie bool `json:"enable_binding_cookie,omitempty"` + AllowedIdps []string `json:"allowed_idps,omitempty"` + CorsHeaders *AccessApplicationCorsHeaders `json:"cors_headers,omitempty"` + CustomDenyMessage string `json:"custom_deny_message,omitempty"` + CustomDenyURL string `json:"custom_deny_url,omitempty"` + HttpOnlyCookieAttribute bool `json:"http_only_cookie_attribute,omitempty"` + SameSiteCookieAttribute string `json:"same_site_cookie_attribute,omitempty"` + LogoURL string `json:"logo_url,omitempty"` + SkipInterstitial bool `json:"skip_interstitial,omitempty"` +} + +// AccessApplicationCorsHeaders represents the CORS HTTP headers for an Access +// Application. +type AccessApplicationCorsHeaders struct { + AllowedMethods []string `json:"allowed_methods,omitempty"` + AllowedOrigins []string `json:"allowed_origins,omitempty"` + AllowedHeaders []string `json:"allowed_headers,omitempty"` + AllowAllMethods bool `json:"allow_all_methods,omitempty"` + AllowAllHeaders bool `json:"allow_all_headers,omitempty"` + AllowAllOrigins bool `json:"allow_all_origins,omitempty"` + AllowCredentials bool `json:"allow_credentials,omitempty"` + MaxAge int `json:"max_age,omitempty"` +} + +// AccessApplicationListResponse represents the response from the list +// access applications endpoint. +type AccessApplicationListResponse struct { + Result []AccessApplication `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccessApplicationDetailResponse is the API response, containing a single +// access application. +type AccessApplicationDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccessApplication `json:"result"` +} + +// AccessApplications returns all applications within an account. +// +// API reference: https://api.cloudflare.com/#access-applications-list-access-applications +func (api *API) AccessApplications(ctx context.Context, accountID string, pageOpts PaginationOptions) ([]AccessApplication, ResultInfo, error) { + return api.accessApplications(ctx, accountID, pageOpts, AccountRouteRoot) +} + +// ZoneLevelAccessApplications returns all applications within a zone. +// +// API reference: https://api.cloudflare.com/#zone-level-access-applications-list-access-applications +func (api *API) ZoneLevelAccessApplications(ctx context.Context, zoneID string, pageOpts PaginationOptions) ([]AccessApplication, ResultInfo, error) { + return api.accessApplications(ctx, zoneID, pageOpts, ZoneRouteRoot) +} + +func (api *API) accessApplications(ctx context.Context, id string, pageOpts PaginationOptions, routeRoot RouteRoot) ([]AccessApplication, ResultInfo, error) { + v := url.Values{} + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + uri := fmt.Sprintf("/%s/%s/access/apps", routeRoot, id) + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []AccessApplication{}, ResultInfo{}, err + } + + var accessApplicationListResponse AccessApplicationListResponse + err = json.Unmarshal(res, &accessApplicationListResponse) + if err != nil { + return []AccessApplication{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return accessApplicationListResponse.Result, accessApplicationListResponse.ResultInfo, nil +} + +// AccessApplication returns a single application based on the +// application ID. +// +// API reference: https://api.cloudflare.com/#access-applications-access-applications-details +func (api *API) AccessApplication(ctx context.Context, accountID, applicationID string) (AccessApplication, error) { + return api.accessApplication(ctx, accountID, applicationID, AccountRouteRoot) +} + +// ZoneLevelAccessApplication returns a single zone level application based on the +// application ID. +// +// API reference: https://api.cloudflare.com/#zone-level-access-applications-access-applications-details +func (api *API) ZoneLevelAccessApplication(ctx context.Context, zoneID, applicationID string) (AccessApplication, error) { + return api.accessApplication(ctx, zoneID, applicationID, ZoneRouteRoot) +} + +func (api *API) accessApplication(ctx context.Context, id, applicationID string, routeRoot RouteRoot) (AccessApplication, error) { + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s", + routeRoot, + id, + applicationID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AccessApplication{}, err + } + + var accessApplicationDetailResponse AccessApplicationDetailResponse + err = json.Unmarshal(res, &accessApplicationDetailResponse) + if err != nil { + return AccessApplication{}, errors.Wrap(err, errUnmarshalError) + } + + return accessApplicationDetailResponse.Result, nil +} + +// CreateAccessApplication creates a new access application. +// +// API reference: https://api.cloudflare.com/#access-applications-create-access-application +func (api *API) CreateAccessApplication(ctx context.Context, accountID string, accessApplication AccessApplication) (AccessApplication, error) { + return api.createAccessApplication(ctx, accountID, accessApplication, AccountRouteRoot) +} + +// CreateZoneLevelAccessApplication creates a new zone level access application. +// +// API reference: https://api.cloudflare.com/#zone-level-access-applications-create-access-application +func (api *API) CreateZoneLevelAccessApplication(ctx context.Context, zoneID string, accessApplication AccessApplication) (AccessApplication, error) { + return api.createAccessApplication(ctx, zoneID, accessApplication, ZoneRouteRoot) +} + +func (api *API) createAccessApplication(ctx context.Context, id string, accessApplication AccessApplication, routeRoot RouteRoot) (AccessApplication, error) { + uri := fmt.Sprintf("/%s/%s/access/apps", routeRoot, id) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, accessApplication) + if err != nil { + return AccessApplication{}, err + } + + var accessApplicationDetailResponse AccessApplicationDetailResponse + err = json.Unmarshal(res, &accessApplicationDetailResponse) + if err != nil { + return AccessApplication{}, errors.Wrap(err, errUnmarshalError) + } + + return accessApplicationDetailResponse.Result, nil +} + +// UpdateAccessApplication updates an existing access application. +// +// API reference: https://api.cloudflare.com/#access-applications-update-access-application +func (api *API) UpdateAccessApplication(ctx context.Context, accountID string, accessApplication AccessApplication) (AccessApplication, error) { + return api.updateAccessApplication(ctx, accountID, accessApplication, AccountRouteRoot) +} + +// UpdateZoneLevelAccessApplication updates an existing zone level access application. +// +// API reference: https://api.cloudflare.com/#zone-level-access-applications-update-access-application +func (api *API) UpdateZoneLevelAccessApplication(ctx context.Context, zoneID string, accessApplication AccessApplication) (AccessApplication, error) { + return api.updateAccessApplication(ctx, zoneID, accessApplication, ZoneRouteRoot) +} + +func (api *API) updateAccessApplication(ctx context.Context, id string, accessApplication AccessApplication, routeRoot RouteRoot) (AccessApplication, error) { + if accessApplication.ID == "" { + return AccessApplication{}, errors.Errorf("access application ID cannot be empty") + } + + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s", + routeRoot, + id, + accessApplication.ID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, accessApplication) + if err != nil { + return AccessApplication{}, err + } + + var accessApplicationDetailResponse AccessApplicationDetailResponse + err = json.Unmarshal(res, &accessApplicationDetailResponse) + if err != nil { + return AccessApplication{}, errors.Wrap(err, errUnmarshalError) + } + + return accessApplicationDetailResponse.Result, nil +} + +// DeleteAccessApplication deletes an access application. +// +// API reference: https://api.cloudflare.com/#access-applications-delete-access-application +func (api *API) DeleteAccessApplication(ctx context.Context, accountID, applicationID string) error { + return api.deleteAccessApplication(ctx, accountID, applicationID, AccountRouteRoot) +} + +// DeleteZoneLevelAccessApplication deletes a zone level access application. +// +// API reference: https://api.cloudflare.com/#zone-level-access-applications-delete-access-application +func (api *API) DeleteZoneLevelAccessApplication(ctx context.Context, zoneID, applicationID string) error { + return api.deleteAccessApplication(ctx, zoneID, applicationID, ZoneRouteRoot) +} + +func (api *API) deleteAccessApplication(ctx context.Context, id, applicationID string, routeRoot RouteRoot) error { + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s", + routeRoot, + id, + applicationID, + ) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} + +// RevokeAccessApplicationTokens revokes tokens associated with an +// access application. +// +// API reference: https://api.cloudflare.com/#access-applications-revoke-access-tokens +func (api *API) RevokeAccessApplicationTokens(ctx context.Context, accountID, applicationID string) error { + return api.revokeAccessApplicationTokens(ctx, accountID, applicationID, AccountRouteRoot) +} + +// RevokeZoneLevelAccessApplicationTokens revokes tokens associated with a zone level +// access application. +// +// API reference: https://api.cloudflare.com/#zone-level-access-applications-revoke-access-tokens +func (api *API) RevokeZoneLevelAccessApplicationTokens(ctx context.Context, zoneID, applicationID string) error { + return api.revokeAccessApplicationTokens(ctx, zoneID, applicationID, ZoneRouteRoot) +} + +func (api *API) revokeAccessApplicationTokens(ctx context.Context, id string, applicationID string, routeRoot RouteRoot) error { + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s/revoke-tokens", + routeRoot, + id, + applicationID, + ) + + _, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/access_audit_log.go b/vendor/github.com/cloudflare/cloudflare-go/access_audit_log.go new file mode 100644 index 0000000000000..af67b72cc4de6 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/access_audit_log.go @@ -0,0 +1,87 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/pkg/errors" +) + +// AccessAuditLogRecord is the structure of a single Access Audit Log entry. +type AccessAuditLogRecord struct { + UserEmail string `json:"user_email"` + IPAddress string `json:"ip_address"` + AppUID string `json:"app_uid"` + AppDomain string `json:"app_domain"` + Action string `json:"action"` + Connection string `json:"connection"` + Allowed bool `json:"allowed"` + CreatedAt *time.Time `json:"created_at"` + RayID string `json:"ray_id"` +} + +// AccessAuditLogListResponse represents the response from the list +// access applications endpoint. +type AccessAuditLogListResponse struct { + Result []AccessAuditLogRecord `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccessAuditLogFilterOptions provides the structure of available audit log +// filters. +type AccessAuditLogFilterOptions struct { + Direction string + Since *time.Time + Until *time.Time + Limit int +} + +// AccessAuditLogs retrieves all audit logs for the Access service. +// +// API reference: https://api.cloudflare.com/#access-requests-access-requests-audit +func (api *API) AccessAuditLogs(ctx context.Context, accountID string, opts AccessAuditLogFilterOptions) ([]AccessAuditLogRecord, error) { + uri := fmt.Sprintf("/accounts/%s/access/logs/access-requests?%s", accountID, opts.Encode()) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []AccessAuditLogRecord{}, err + } + + var accessAuditLogListResponse AccessAuditLogListResponse + err = json.Unmarshal(res, &accessAuditLogListResponse) + if err != nil { + return []AccessAuditLogRecord{}, errors.Wrap(err, errUnmarshalError) + } + + return accessAuditLogListResponse.Result, nil +} + +// Encode is a custom method for encoding the filter options into a usable HTTP +// query parameter string. +func (a AccessAuditLogFilterOptions) Encode() string { + v := url.Values{} + + if a.Direction != "" { + v.Set("direction", a.Direction) + } + + if a.Limit > 0 { + v.Set("limit", strconv.Itoa(a.Limit)) + } + + if a.Since != nil { + v.Set("since", (*a.Since).Format(time.RFC3339)) + } + + if a.Until != nil { + v.Set("until", (*a.Until).Format(time.RFC3339)) + } + + return v.Encode() +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/access_ca_certificate.go b/vendor/github.com/cloudflare/cloudflare-go/access_ca_certificate.go new file mode 100644 index 0000000000000..d19765bc2ae4d --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/access_ca_certificate.go @@ -0,0 +1,166 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +// AccessCACertificate is the structure of the CA certificate used for +// short lived certificates. +type AccessCACertificate struct { + ID string `json:"id"` + Aud string `json:"aud"` + PublicKey string `json:"public_key"` +} + +// AccessCACertificateListResponse represents the response of all CA +// certificates within Access. +type AccessCACertificateListResponse struct { + Response + Result []AccessCACertificate `json:"result"` +} + +// AccessCACertificateResponse represents the response of a single CA +// certificate. +type AccessCACertificateResponse struct { + Response + Result AccessCACertificate `json:"result"` +} + +// AccessCACertificates returns all CA certificates within Access. +// +// API reference: https://api.cloudflare.com/#access-short-lived-certificates-list-short-lived-certificates +func (api *API) AccessCACertificates(ctx context.Context, accountID string) ([]AccessCACertificate, error) { + return api.accessCACertificates(ctx, accountID, AccountRouteRoot) +} + +// ZoneLevelAccessCACertificates returns all zone level CA certificates within Access. +// +// API reference: https://api.cloudflare.com/#zone-level-access-short-lived-certificates-list-short-lived-certificates +func (api *API) ZoneLevelAccessCACertificates(ctx context.Context, zoneID string) ([]AccessCACertificate, error) { + return api.accessCACertificates(ctx, zoneID, ZoneRouteRoot) +} + +func (api *API) accessCACertificates(ctx context.Context, id string, routeRoot RouteRoot) ([]AccessCACertificate, error) { + uri := fmt.Sprintf("/%s/%s/access/apps/ca", routeRoot, id) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []AccessCACertificate{}, err + } + + var accessCAListResponse AccessCACertificateListResponse + err = json.Unmarshal(res, &accessCAListResponse) + if err != nil { + return []AccessCACertificate{}, errors.Wrap(err, errUnmarshalError) + } + + return accessCAListResponse.Result, nil +} + +// AccessCACertificate returns a single CA certificate associated with an Access +// Application. +// +// API reference: https://api.cloudflare.com/#access-short-lived-certificates-short-lived-certificate-details +func (api *API) AccessCACertificate(ctx context.Context, accountID, applicationID string) (AccessCACertificate, error) { + return api.accessCACertificate(ctx, accountID, applicationID, AccountRouteRoot) +} + +// ZoneLevelAccessCACertificate returns a single zone level CA certificate associated with an Access +// Application. +// +// API reference: https://api.cloudflare.com/#zone-level-access-short-lived-certificates-short-lived-certificate-details +func (api *API) ZoneLevelAccessCACertificate(ctx context.Context, zoneID, applicationID string) (AccessCACertificate, error) { + return api.accessCACertificate(ctx, zoneID, applicationID, ZoneRouteRoot) +} + +func (api *API) accessCACertificate(ctx context.Context, id, applicationID string, routeRoot RouteRoot) (AccessCACertificate, error) { + uri := fmt.Sprintf("/%s/%s/access/apps/%s/ca", routeRoot, id, applicationID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AccessCACertificate{}, err + } + + var accessCAResponse AccessCACertificateResponse + err = json.Unmarshal(res, &accessCAResponse) + if err != nil { + return AccessCACertificate{}, errors.Wrap(err, errUnmarshalError) + } + + return accessCAResponse.Result, nil +} + +// CreateAccessCACertificate creates a new CA certificate for an Access +// Application. +// +// API reference: https://api.cloudflare.com/#access-short-lived-certificates-create-short-lived-certificate +func (api *API) CreateAccessCACertificate(ctx context.Context, accountID, applicationID string) (AccessCACertificate, error) { + return api.createAccessCACertificate(ctx, accountID, applicationID, AccountRouteRoot) +} + +// CreateZoneLevelAccessCACertificate creates a new zone level CA certificate for an Access +// Application. +// +// API reference: https://api.cloudflare.com/#zone-level-access-short-lived-certificates-create-short-lived-certificate +func (api *API) CreateZoneLevelAccessCACertificate(ctx context.Context, zoneID string, applicationID string) (AccessCACertificate, error) { + return api.createAccessCACertificate(ctx, zoneID, applicationID, ZoneRouteRoot) +} + +func (api *API) createAccessCACertificate(ctx context.Context, id string, applicationID string, routeRoot RouteRoot) (AccessCACertificate, error) { + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s/ca", + routeRoot, + id, + applicationID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return AccessCACertificate{}, err + } + + var accessCACertificate AccessCACertificateResponse + err = json.Unmarshal(res, &accessCACertificate) + if err != nil { + return AccessCACertificate{}, errors.Wrap(err, errUnmarshalError) + } + + return accessCACertificate.Result, nil +} + +// DeleteAccessCACertificate deletes an Access CA certificate on a defined +// Access Application. +// +// API reference: https://api.cloudflare.com/#access-short-lived-certificates-delete-access-certificate +func (api *API) DeleteAccessCACertificate(ctx context.Context, accountID, applicationID string) error { + return api.deleteAccessCACertificate(ctx, accountID, applicationID, AccountRouteRoot) +} + +// DeleteZoneLevelAccessCACertificate deletes a zone level Access CA certificate on a defined +// Access Application. +// +// API reference: https://api.cloudflare.com/#zone-level-access-short-lived-certificates-delete-access-certificate +func (api *API) DeleteZoneLevelAccessCACertificate(ctx context.Context, zoneID, applicationID string) error { + return api.deleteAccessCACertificate(ctx, zoneID, applicationID, ZoneRouteRoot) +} + +func (api *API) deleteAccessCACertificate(ctx context.Context, id string, applicationID string, routeRoot RouteRoot) error { + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s/ca", + routeRoot, + id, + applicationID, + ) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/access_group.go b/vendor/github.com/cloudflare/cloudflare-go/access_group.go new file mode 100644 index 0000000000000..26faffcec8b70 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/access_group.go @@ -0,0 +1,380 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/pkg/errors" +) + +// AccessGroup defines a group for allowing or disallowing access to +// one or more Access applications. +type AccessGroup struct { + ID string `json:"id,omitempty"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + Name string `json:"name"` + + // The include group works like an OR logical operator. The user must + // satisfy one of the rules. + Include []interface{} `json:"include"` + + // The exclude group works like a NOT logical operator. The user must + // not satisfy all of the rules in exclude. + Exclude []interface{} `json:"exclude"` + + // The require group works like a AND logical operator. The user must + // satisfy all of the rules in require. + Require []interface{} `json:"require"` +} + +// AccessGroupEmail is used for managing access based on the email. +// For example, restrict access to users with the email addresses +// `test@example.com` or `someone@example.com`. +type AccessGroupEmail struct { + Email struct { + Email string `json:"email"` + } `json:"email"` +} + +// AccessGroupEmailDomain is used for managing access based on an email +// domain domain such as `example.com` instead of individual addresses. +type AccessGroupEmailDomain struct { + EmailDomain struct { + Domain string `json:"domain"` + } `json:"email_domain"` +} + +// AccessGroupIP is used for managing access based in the IP. It +// accepts individual IPs or CIDRs. +type AccessGroupIP struct { + IP struct { + IP string `json:"ip"` + } `json:"ip"` +} + +// AccessGroupGeo is used for managing access based on the country code. +type AccessGroupGeo struct { + Geo struct { + CountryCode string `json:"country_code"` + } `json:"geo"` +} + +// AccessGroupEveryone is used for managing access to everyone. +type AccessGroupEveryone struct { + Everyone struct{} `json:"everyone"` +} + +// AccessGroupServiceToken is used for managing access based on a specific +// service token. +type AccessGroupServiceToken struct { + ServiceToken struct { + ID string `json:"token_id"` + } `json:"service_token"` +} + +// AccessGroupAnyValidServiceToken is used for managing access for all valid +// service tokens (not restricted). +type AccessGroupAnyValidServiceToken struct { + AnyValidServiceToken struct{} `json:"any_valid_service_token"` +} + +// AccessGroupAccessGroup is used for managing access based on an +// access group. +type AccessGroupAccessGroup struct { + Group struct { + ID string `json:"id"` + } `json:"group"` +} + +// AccessGroupCertificate is used for managing access to based on a valid +// mTLS certificate being presented. +type AccessGroupCertificate struct { + Certificate struct{} `json:"certificate"` +} + +// AccessGroupCertificateCommonName is used for managing access based on a +// common name within a certificate. +type AccessGroupCertificateCommonName struct { + CommonName struct { + CommonName string `json:"common_name"` + } `json:"common_name"` +} + +// AccessGroupGSuite is used to configure access based on GSuite group. +type AccessGroupGSuite struct { + Gsuite struct { + Email string `json:"email"` + IdentityProviderID string `json:"identity_provider_id"` + } `json:"gsuite"` +} + +// AccessGroupGitHub is used to configure access based on a GitHub organisation. +type AccessGroupGitHub struct { + GitHubOrganization struct { + Name string `json:"name"` + Team string `json:"team,omitempty"` + IdentityProviderID string `json:"identity_provider_id"` + } `json:"github-organization"` +} + +// AccessGroupAzure is used to configure access based on a Azure group. +type AccessGroupAzure struct { + AzureAD struct { + ID string `json:"id"` + IdentityProviderID string `json:"identity_provider_id"` + } `json:"azureAD"` +} + +// AccessGroupOkta is used to configure access based on a Okta group. +type AccessGroupOkta struct { + Okta struct { + Name string `json:"name"` + IdentityProviderID string `json:"identity_provider_id"` + } `json:"okta"` +} + +// AccessGroupSAML is used to allow SAML users with a specific attribute +// configuration. +type AccessGroupSAML struct { + Saml struct { + AttributeName string `json:"attribute_name"` + AttributeValue string `json:"attribute_value"` + IdentityProviderID string `json:"identity_provider_id"` + } `json:"saml"` +} + +// AccessGroupAuthMethod is used for managing access by the "amr" +// (Authentication Methods References) identifier. For example, an +// application may want to require that users authenticate using a hardware +// key by setting the "auth_method" to "swk". A list of values are listed +// here: https://tools.ietf.org/html/rfc8176#section-2. Custom values are +// supported as well. +type AccessGroupAuthMethod struct { + AuthMethod struct { + AuthMethod string `json:"auth_method"` + } `json:"auth_method"` +} + +// AccessGroupLoginMethod restricts the application to specific IdP instances. +type AccessGroupLoginMethod struct { + LoginMethod struct { + ID string `json:"id"` + } `json:"login_method"` +} + +// AccessGroupDevicePosture restricts the application to specific devices +type AccessGroupDevicePosture struct { + DevicePosture struct { + ID string `json:"integration_uid"` + } `json:"device_posture"` +} + +// AccessGroupListResponse represents the response from the list +// access group endpoint. +type AccessGroupListResponse struct { + Result []AccessGroup `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccessGroupDetailResponse is the API response, containing a single +// access group. +type AccessGroupDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccessGroup `json:"result"` +} + +// AccessGroups returns all access groups for an access application. +// +// API reference: https://api.cloudflare.com/#access-groups-list-access-groups +func (api *API) AccessGroups(ctx context.Context, accountID string, pageOpts PaginationOptions) ([]AccessGroup, ResultInfo, error) { + return api.accessGroups(ctx, accountID, pageOpts, AccountRouteRoot) +} + +// ZoneLevelAccessGroups returns all zone level access groups for an access application. +// +// API reference: https://api.cloudflare.com/#zone-level-access-groups-list-access-groups +func (api *API) ZoneLevelAccessGroups(ctx context.Context, zoneID string, pageOpts PaginationOptions) ([]AccessGroup, ResultInfo, error) { + return api.accessGroups(ctx, zoneID, pageOpts, ZoneRouteRoot) +} + +func (api *API) accessGroups(ctx context.Context, id string, pageOpts PaginationOptions, routeRoot RouteRoot) ([]AccessGroup, ResultInfo, error) { + v := url.Values{} + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + uri := fmt.Sprintf( + "/%s/%s/access/groups", + routeRoot, + id, + ) + + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []AccessGroup{}, ResultInfo{}, err + } + + var accessGroupListResponse AccessGroupListResponse + err = json.Unmarshal(res, &accessGroupListResponse) + if err != nil { + return []AccessGroup{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return accessGroupListResponse.Result, accessGroupListResponse.ResultInfo, nil +} + +// AccessGroup returns a single group based on the group ID. +// +// API reference: https://api.cloudflare.com/#access-groups-access-group-details +func (api *API) AccessGroup(ctx context.Context, accountID, groupID string) (AccessGroup, error) { + return api.accessGroup(ctx, accountID, groupID, AccountRouteRoot) +} + +// ZoneLevelAccessGroup returns a single zone level group based on the group ID. +// +// API reference: https://api.cloudflare.com/#zone-level-access-groups-access-group-details +func (api *API) ZoneLevelAccessGroup(ctx context.Context, zoneID, groupID string) (AccessGroup, error) { + return api.accessGroup(ctx, zoneID, groupID, ZoneRouteRoot) +} + +func (api *API) accessGroup(ctx context.Context, id, groupID string, routeRoot RouteRoot) (AccessGroup, error) { + uri := fmt.Sprintf( + "/%s/%s/access/groups/%s", + routeRoot, + id, + groupID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AccessGroup{}, err + } + + var accessGroupDetailResponse AccessGroupDetailResponse + err = json.Unmarshal(res, &accessGroupDetailResponse) + if err != nil { + return AccessGroup{}, errors.Wrap(err, errUnmarshalError) + } + + return accessGroupDetailResponse.Result, nil +} + +// CreateAccessGroup creates a new access group. +// +// API reference: https://api.cloudflare.com/#access-groups-create-access-group +func (api *API) CreateAccessGroup(ctx context.Context, accountID string, accessGroup AccessGroup) (AccessGroup, error) { + return api.createAccessGroup(ctx, accountID, accessGroup, AccountRouteRoot) +} + +// CreateZoneLevelAccessGroup creates a new zone level access group. +// +// API reference: https://api.cloudflare.com/#zone-level-access-groups-create-access-group +func (api *API) CreateZoneLevelAccessGroup(ctx context.Context, zoneID string, accessGroup AccessGroup) (AccessGroup, error) { + return api.createAccessGroup(ctx, zoneID, accessGroup, ZoneRouteRoot) +} + +func (api *API) createAccessGroup(ctx context.Context, id string, accessGroup AccessGroup, routeRoot RouteRoot) (AccessGroup, error) { + uri := fmt.Sprintf( + "/%s/%s/access/groups", + routeRoot, + id, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, accessGroup) + if err != nil { + return AccessGroup{}, err + } + + var accessGroupDetailResponse AccessGroupDetailResponse + err = json.Unmarshal(res, &accessGroupDetailResponse) + if err != nil { + return AccessGroup{}, errors.Wrap(err, errUnmarshalError) + } + + return accessGroupDetailResponse.Result, nil +} + +// UpdateAccessGroup updates an existing access group. +// +// API reference: https://api.cloudflare.com/#access-groups-update-access-group +func (api *API) UpdateAccessGroup(ctx context.Context, accountID string, accessGroup AccessGroup) (AccessGroup, error) { + return api.updateAccessGroup(ctx, accountID, accessGroup, AccountRouteRoot) +} + +// UpdateZoneLevelAccessGroup updates an existing zone level access group. +// +// API reference: https://api.cloudflare.com/#zone-level-access-groups-update-access-group +func (api *API) UpdateZoneLevelAccessGroup(ctx context.Context, zoneID string, accessGroup AccessGroup) (AccessGroup, error) { + return api.updateAccessGroup(ctx, zoneID, accessGroup, ZoneRouteRoot) +} + +func (api *API) updateAccessGroup(ctx context.Context, id string, accessGroup AccessGroup, routeRoot RouteRoot) (AccessGroup, error) { + if accessGroup.ID == "" { + return AccessGroup{}, errors.Errorf("access group ID cannot be empty") + } + uri := fmt.Sprintf( + "/%s/%s/access/groups/%s", + routeRoot, + id, + accessGroup.ID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, accessGroup) + if err != nil { + return AccessGroup{}, err + } + + var accessGroupDetailResponse AccessGroupDetailResponse + err = json.Unmarshal(res, &accessGroupDetailResponse) + if err != nil { + return AccessGroup{}, errors.Wrap(err, errUnmarshalError) + } + + return accessGroupDetailResponse.Result, nil +} + +// DeleteAccessGroup deletes an access group. +// +// API reference: https://api.cloudflare.com/#access-groups-delete-access-group +func (api *API) DeleteAccessGroup(ctx context.Context, accountID, groupID string) error { + return api.deleteAccessGroup(ctx, accountID, groupID, AccountRouteRoot) +} + +// DeleteZoneLevelAccessGroup deletes a zone level access group. +// +// API reference: https://api.cloudflare.com/#zone-level-access-groups-delete-access-group +func (api *API) DeleteZoneLevelAccessGroup(ctx context.Context, zoneID, groupID string) error { + return api.deleteAccessGroup(ctx, zoneID, groupID, ZoneRouteRoot) +} + +func (api *API) deleteAccessGroup(ctx context.Context, id string, groupID string, routeRoot RouteRoot) error { + uri := fmt.Sprintf( + "/%s/%s/access/groups/%s", + routeRoot, + id, + groupID, + ) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/access_identity_provider.go b/vendor/github.com/cloudflare/cloudflare-go/access_identity_provider.go new file mode 100644 index 0000000000000..0fbc423939cfa --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/access_identity_provider.go @@ -0,0 +1,236 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +// AccessIdentityProvider is the structure of the provider object. +type AccessIdentityProvider struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Config AccessIdentityProviderConfiguration `json:"config"` +} + +// AccessIdentityProviderConfiguration is the combined structure of *all* +// identity provider configuration fields. This is done to simplify the use of +// Access products and their relationship to each other. +// +// API reference: https://developers.cloudflare.com/access/configuring-identity-providers/ +type AccessIdentityProviderConfiguration struct { + APIToken string `json:"api_token,omitempty"` + AppsDomain string `json:"apps_domain,omitempty"` + Attributes []string `json:"attributes,omitempty"` + AuthURL string `json:"auth_url,omitempty"` + CentrifyAccount string `json:"centrify_account,omitempty"` + CentrifyAppID string `json:"centrify_app_id,omitempty"` + CertsURL string `json:"certs_url,omitempty"` + ClientID string `json:"client_id,omitempty"` + ClientSecret string `json:"client_secret,omitempty"` + DirectoryID string `json:"directory_id,omitempty"` + EmailAttributeName string `json:"email_attribute_name,omitempty"` + IdpPublicCert string `json:"idp_public_cert,omitempty"` + IssuerURL string `json:"issuer_url,omitempty"` + OktaAccount string `json:"okta_account,omitempty"` + OneloginAccount string `json:"onelogin_account,omitempty"` + RedirectURL string `json:"redirect_url,omitempty"` + SignRequest bool `json:"sign_request,omitempty"` + SsoTargetURL string `json:"sso_target_url,omitempty"` + SupportGroups bool `json:"support_groups,omitempty"` + TokenURL string `json:"token_url,omitempty"` +} + +// AccessIdentityProvidersListResponse is the API response for multiple +// Access Identity Providers. +type AccessIdentityProvidersListResponse struct { + Response + Result []AccessIdentityProvider `json:"result"` +} + +// AccessIdentityProviderListResponse is the API response for a single +// Access Identity Provider. +type AccessIdentityProviderListResponse struct { + Response + Result AccessIdentityProvider `json:"result"` +} + +// AccessIdentityProviders returns all Access Identity Providers for an +// account. +// +// API reference: https://api.cloudflare.com/#access-identity-providers-list-access-identity-providers +func (api *API) AccessIdentityProviders(ctx context.Context, accountID string) ([]AccessIdentityProvider, error) { + return api.accessIdentityProviders(ctx, accountID, AccountRouteRoot) +} + +// ZoneLevelAccessIdentityProviders returns all Access Identity Providers for an +// account. +// +// API reference: https://api.cloudflare.com/#zone-level-access-identity-providers-list-access-identity-providers +func (api *API) ZoneLevelAccessIdentityProviders(ctx context.Context, zoneID string) ([]AccessIdentityProvider, error) { + return api.accessIdentityProviders(ctx, zoneID, ZoneRouteRoot) +} + +func (api *API) accessIdentityProviders(ctx context.Context, id string, routeRoot RouteRoot) ([]AccessIdentityProvider, error) { + uri := fmt.Sprintf("/%s/%s/access/identity_providers", routeRoot, id) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []AccessIdentityProvider{}, err + } + + var accessIdentityProviderResponse AccessIdentityProvidersListResponse + err = json.Unmarshal(res, &accessIdentityProviderResponse) + if err != nil { + return []AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError) + } + + return accessIdentityProviderResponse.Result, nil +} + +// AccessIdentityProviderDetails returns a single Access Identity +// Provider for an account. +// +// API reference: https://api.cloudflare.com/#access-identity-providers-access-identity-providers-details +func (api *API) AccessIdentityProviderDetails(ctx context.Context, accountID, identityProviderID string) (AccessIdentityProvider, error) { + return api.accessIdentityProviderDetails(ctx, accountID, identityProviderID, AccountRouteRoot) +} + +// ZoneLevelAccessIdentityProviderDetails returns a single zone level Access Identity +// Provider for an account. +// +// API reference: https://api.cloudflare.com/#zone-level-access-identity-providers-access-identity-providers-details +func (api *API) ZoneLevelAccessIdentityProviderDetails(ctx context.Context, zoneID, identityProviderID string) (AccessIdentityProvider, error) { + return api.accessIdentityProviderDetails(ctx, zoneID, identityProviderID, ZoneRouteRoot) +} + +func (api *API) accessIdentityProviderDetails(ctx context.Context, id string, identityProviderID string, routeRoot RouteRoot) (AccessIdentityProvider, error) { + uri := fmt.Sprintf( + "/%s/%s/access/identity_providers/%s", + routeRoot, + id, + identityProviderID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AccessIdentityProvider{}, err + } + + var accessIdentityProviderResponse AccessIdentityProviderListResponse + err = json.Unmarshal(res, &accessIdentityProviderResponse) + if err != nil { + return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError) + } + + return accessIdentityProviderResponse.Result, nil +} + +// CreateAccessIdentityProvider creates a new Access Identity Provider. +// +// API reference: https://api.cloudflare.com/#access-identity-providers-create-access-identity-provider +func (api *API) CreateAccessIdentityProvider(ctx context.Context, accountID string, identityProviderConfiguration AccessIdentityProvider) (AccessIdentityProvider, error) { + return api.createAccessIdentityProvider(ctx, accountID, identityProviderConfiguration, AccountRouteRoot) +} + +// CreateZoneLevelAccessIdentityProvider creates a new zone level Access Identity Provider. +// +// API reference: https://api.cloudflare.com/#zone-level-access-identity-providers-create-access-identity-provider +func (api *API) CreateZoneLevelAccessIdentityProvider(ctx context.Context, zoneID string, identityProviderConfiguration AccessIdentityProvider) (AccessIdentityProvider, error) { + return api.createAccessIdentityProvider(ctx, zoneID, identityProviderConfiguration, ZoneRouteRoot) +} + +func (api *API) createAccessIdentityProvider(ctx context.Context, id string, identityProviderConfiguration AccessIdentityProvider, routeRoot RouteRoot) (AccessIdentityProvider, error) { + uri := fmt.Sprintf("/%s/%s/access/identity_providers", routeRoot, id) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, identityProviderConfiguration) + if err != nil { + return AccessIdentityProvider{}, err + } + + var accessIdentityProviderResponse AccessIdentityProviderListResponse + err = json.Unmarshal(res, &accessIdentityProviderResponse) + if err != nil { + return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError) + } + + return accessIdentityProviderResponse.Result, nil +} + +// UpdateAccessIdentityProvider updates an existing Access Identity +// Provider. +// +// API reference: https://api.cloudflare.com/#access-identity-providers-create-access-identity-provider +func (api *API) UpdateAccessIdentityProvider(ctx context.Context, accountID, identityProviderUUID string, identityProviderConfiguration AccessIdentityProvider) (AccessIdentityProvider, error) { + return api.updateAccessIdentityProvider(ctx, accountID, identityProviderUUID, identityProviderConfiguration, AccountRouteRoot) +} + +// UpdateZoneLevelAccessIdentityProvider updates an existing zone level Access Identity +// Provider. +// +// API reference: https://api.cloudflare.com/#zone-level-access-identity-providers-update-access-identity-provider +func (api *API) UpdateZoneLevelAccessIdentityProvider(ctx context.Context, zoneID, identityProviderUUID string, identityProviderConfiguration AccessIdentityProvider) (AccessIdentityProvider, error) { + return api.updateAccessIdentityProvider(ctx, zoneID, identityProviderUUID, identityProviderConfiguration, ZoneRouteRoot) +} + +func (api *API) updateAccessIdentityProvider(ctx context.Context, id string, identityProviderUUID string, identityProviderConfiguration AccessIdentityProvider, routeRoot RouteRoot) (AccessIdentityProvider, error) { + uri := fmt.Sprintf( + "/%s/%s/access/identity_providers/%s", + routeRoot, + id, + identityProviderUUID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, identityProviderConfiguration) + if err != nil { + return AccessIdentityProvider{}, err + } + + var accessIdentityProviderResponse AccessIdentityProviderListResponse + err = json.Unmarshal(res, &accessIdentityProviderResponse) + if err != nil { + return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError) + } + + return accessIdentityProviderResponse.Result, nil +} + +// DeleteAccessIdentityProvider deletes an Access Identity Provider. +// +// API reference: https://api.cloudflare.com/#access-identity-providers-create-access-identity-provider +func (api *API) DeleteAccessIdentityProvider(ctx context.Context, accountID, identityProviderUUID string) (AccessIdentityProvider, error) { + return api.deleteAccessIdentityProvider(ctx, accountID, identityProviderUUID, AccountRouteRoot) +} + +// DeleteZoneLevelAccessIdentityProvider deletes a zone level Access Identity Provider. +// +// API reference: https://api.cloudflare.com/#zone-level-access-identity-providers-delete-access-identity-provider +func (api *API) DeleteZoneLevelAccessIdentityProvider(ctx context.Context, zoneID, identityProviderUUID string) (AccessIdentityProvider, error) { + return api.deleteAccessIdentityProvider(ctx, zoneID, identityProviderUUID, ZoneRouteRoot) +} + +func (api *API) deleteAccessIdentityProvider(ctx context.Context, id string, identityProviderUUID string, routeRoot RouteRoot) (AccessIdentityProvider, error) { + uri := fmt.Sprintf( + "/%s/%s/access/identity_providers/%s", + routeRoot, + id, + identityProviderUUID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return AccessIdentityProvider{}, err + } + + var accessIdentityProviderResponse AccessIdentityProviderListResponse + err = json.Unmarshal(res, &accessIdentityProviderResponse) + if err != nil { + return AccessIdentityProvider{}, errors.Wrap(err, errUnmarshalError) + } + + return accessIdentityProviderResponse.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/access_keys.go b/vendor/github.com/cloudflare/cloudflare-go/access_keys.go new file mode 100644 index 0000000000000..1816dc6c5afe1 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/access_keys.go @@ -0,0 +1,64 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "github.com/pkg/errors" + "net/http" + "time" +) + +type AccessKeysConfig struct { + KeyRotationIntervalDays int `json:"key_rotation_interval_days"` + LastKeyRotationAt time.Time `json:"last_key_rotation_at"` + DaysUntilNextRotation int `json:"days_until_next_rotation"` +} + +type AccessKeysConfigUpdateRequest struct { + KeyRotationIntervalDays int `json:"key_rotation_interval_days"` +} + +type accessKeysConfigResponse struct { + Response + Result AccessKeysConfig `json:"result"` +} + +// AccessKeysConfig returns the Access Keys Configuration for an account. +// +// API reference: https://api.cloudflare.com/#access-keys-configuration-get-access-keys-configuration +func (api *API) AccessKeysConfig(ctx context.Context, accountID string) (AccessKeysConfig, error) { + uri := fmt.Sprintf("/%s/%s/access/keys", AccountRouteRoot, accountID) + + return api.accessKeysRequest(ctx, http.MethodGet, uri, nil) +} + +// UpdateAccessKeysConfig updates the Access Keys Configuration for an account. +// +// API reference: https://api.cloudflare.com/#access-keys-configuration-update-access-keys-configuration +func (api *API) UpdateAccessKeysConfig(ctx context.Context, accountID string, request AccessKeysConfigUpdateRequest) (AccessKeysConfig, error) { + uri := fmt.Sprintf("/%s/%s/access/keys", AccountRouteRoot, accountID) + + return api.accessKeysRequest(ctx, http.MethodPut, uri, request) +} + +// RotateAccessKeys rotates the Access Keys for an account and returns the updated Access Keys Configuration +// +// API reference: https://api.cloudflare.com/#access-keys-configuration-rotate-access-keys +func (api *API) RotateAccessKeys(ctx context.Context, accountID string) (AccessKeysConfig, error) { + uri := fmt.Sprintf("/%s/%s/access/keys/rotate", AccountRouteRoot, accountID) + return api.accessKeysRequest(ctx, http.MethodPost, uri, nil) +} + +func (api *API) accessKeysRequest(ctx context.Context, method, uri string, params interface{}) (AccessKeysConfig, error) { + res, err := api.makeRequestContext(ctx, method, uri, params) + if err != nil { + return AccessKeysConfig{}, err + } + + var keysConfigResponse accessKeysConfigResponse + if err := json.Unmarshal(res, &keysConfigResponse); err != nil { + return AccessKeysConfig{}, errors.Wrap(err, errUnmarshalError) + } + return keysConfigResponse.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/access_mutual_tls_certificates.go b/vendor/github.com/cloudflare/cloudflare-go/access_mutual_tls_certificates.go new file mode 100644 index 0000000000000..007ddef21e802 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/access_mutual_tls_certificates.go @@ -0,0 +1,226 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// AccessMutualTLSCertificate is the structure of a single Access Mutual TLS +// certificate. +type AccessMutualTLSCertificate struct { + ID string `json:"id,omitempty"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` + ExpiresOn time.Time `json:"expires_on,omitempty"` + Name string `json:"name,omitempty"` + Fingerprint string `json:"fingerprint,omitempty"` + Certificate string `json:"certificate,omitempty"` + AssociatedHostnames []string `json:"associated_hostnames,omitempty"` +} + +// AccessMutualTLSCertificateListResponse is the API response for all Access +// Mutual TLS certificates. +type AccessMutualTLSCertificateListResponse struct { + Response + Result []AccessMutualTLSCertificate `json:"result"` +} + +// AccessMutualTLSCertificateDetailResponse is the API response for a single +// Access Mutual TLS certificate. +type AccessMutualTLSCertificateDetailResponse struct { + Response + Result AccessMutualTLSCertificate `json:"result"` +} + +// AccessMutualTLSCertificates returns all Access TLS certificates for the account +// level. +// +// API reference: https://api.cloudflare.com/#access-mutual-tls-authentication-properties +func (api *API) AccessMutualTLSCertificates(ctx context.Context, accountID string) ([]AccessMutualTLSCertificate, error) { + return api.accessMutualTLSCertificates(ctx, accountID, AccountRouteRoot) +} + +// ZoneAccessMutualTLSCertificates returns all Access TLS certificates for the +// zone level. +// +// API reference: https://api.cloudflare.com/#zone-level-access-mutual-tls-authentication-properties +func (api *API) ZoneAccessMutualTLSCertificates(ctx context.Context, zoneID string) ([]AccessMutualTLSCertificate, error) { + return api.accessMutualTLSCertificates(ctx, zoneID, ZoneRouteRoot) +} + +func (api *API) accessMutualTLSCertificates(ctx context.Context, id string, routeRoot RouteRoot) ([]AccessMutualTLSCertificate, error) { + uri := fmt.Sprintf( + "/%s/%s/access/certificates", + routeRoot, + id, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []AccessMutualTLSCertificate{}, err + } + + var accessMutualTLSCertificateListResponse AccessMutualTLSCertificateListResponse + err = json.Unmarshal(res, &accessMutualTLSCertificateListResponse) + if err != nil { + return []AccessMutualTLSCertificate{}, errors.Wrap(err, errUnmarshalError) + } + + return accessMutualTLSCertificateListResponse.Result, nil +} + +// AccessMutualTLSCertificate returns a single account level Access Mutual TLS +// certificate. +// +// API reference: https://api.cloudflare.com/#access-mutual-tls-authentication-access-certificate-details +func (api *API) AccessMutualTLSCertificate(ctx context.Context, accountID, certificateID string) (AccessMutualTLSCertificate, error) { + return api.accessMutualTLSCertificate(ctx, accountID, certificateID, AccountRouteRoot) +} + +// ZoneAccessMutualTLSCertificate returns a single zone level Access Mutual TLS +// certificate. +// +// API reference: https://api.cloudflare.com/#zone-level-access-mutual-tls-authentication-access-certificate-details +func (api *API) ZoneAccessMutualTLSCertificate(ctx context.Context, zoneID, certificateID string) (AccessMutualTLSCertificate, error) { + return api.accessMutualTLSCertificate(ctx, zoneID, certificateID, ZoneRouteRoot) +} + +func (api *API) accessMutualTLSCertificate(ctx context.Context, id, certificateID string, routeRoot RouteRoot) (AccessMutualTLSCertificate, error) { + uri := fmt.Sprintf( + "/%s/%s/access/certificates/%s", + routeRoot, + id, + certificateID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AccessMutualTLSCertificate{}, err + } + + var accessMutualTLSCertificateDetailResponse AccessMutualTLSCertificateDetailResponse + err = json.Unmarshal(res, &accessMutualTLSCertificateDetailResponse) + if err != nil { + return AccessMutualTLSCertificate{}, errors.Wrap(err, errUnmarshalError) + } + + return accessMutualTLSCertificateDetailResponse.Result, nil +} + +// CreateAccessMutualTLSCertificate creates an account level Access TLS Mutual +// certificate. +// +// API reference: https://api.cloudflare.com/#access-mutual-tls-authentication-create-access-certificate +func (api *API) CreateAccessMutualTLSCertificate(ctx context.Context, accountID string, certificate AccessMutualTLSCertificate) (AccessMutualTLSCertificate, error) { + return api.createAccessMutualTLSCertificate(ctx, accountID, certificate, AccountRouteRoot) +} + +// CreateZoneAccessMutualTLSCertificate creates a zone level Access TLS Mutual +// certificate. +// +// API reference: https://api.cloudflare.com/#zone-level-access-mutual-tls-authentication-create-access-certificate +func (api *API) CreateZoneAccessMutualTLSCertificate(ctx context.Context, zoneID string, certificate AccessMutualTLSCertificate) (AccessMutualTLSCertificate, error) { + return api.createAccessMutualTLSCertificate(ctx, zoneID, certificate, ZoneRouteRoot) +} + +func (api *API) createAccessMutualTLSCertificate(ctx context.Context, id string, certificate AccessMutualTLSCertificate, routeRoot RouteRoot) (AccessMutualTLSCertificate, error) { + uri := fmt.Sprintf( + "/%s/%s/access/certificates", + routeRoot, + id, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, certificate) + if err != nil { + return AccessMutualTLSCertificate{}, err + } + + var accessMutualTLSCertificateDetailResponse AccessMutualTLSCertificateDetailResponse + err = json.Unmarshal(res, &accessMutualTLSCertificateDetailResponse) + if err != nil { + return AccessMutualTLSCertificate{}, errors.Wrap(err, errUnmarshalError) + } + + return accessMutualTLSCertificateDetailResponse.Result, nil +} + +// UpdateAccessMutualTLSCertificate updates an account level Access TLS Mutual +// certificate. +// +// API reference: https://api.cloudflare.com/#access-mutual-tls-authentication-update-access-certificate +func (api *API) UpdateAccessMutualTLSCertificate(ctx context.Context, accountID, certificateID string, certificate AccessMutualTLSCertificate) (AccessMutualTLSCertificate, error) { + return api.updateAccessMutualTLSCertificate(ctx, accountID, certificateID, certificate, AccountRouteRoot) +} + +// UpdateZoneAccessMutualTLSCertificate updates a zone level Access TLS Mutual +// certificate. +// +// API reference: https://api.cloudflare.com/#zone-level-access-mutual-tls-authentication-update-access-certificate +func (api *API) UpdateZoneAccessMutualTLSCertificate(ctx context.Context, zoneID, certificateID string, certificate AccessMutualTLSCertificate) (AccessMutualTLSCertificate, error) { + return api.updateAccessMutualTLSCertificate(ctx, zoneID, certificateID, certificate, ZoneRouteRoot) +} + +func (api *API) updateAccessMutualTLSCertificate(ctx context.Context, id string, certificateID string, certificate AccessMutualTLSCertificate, routeRoot RouteRoot) (AccessMutualTLSCertificate, error) { + uri := fmt.Sprintf( + "/%s/%s/access/certificates/%s", + routeRoot, + id, + certificateID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, certificate) + if err != nil { + return AccessMutualTLSCertificate{}, err + } + + var accessMutualTLSCertificateDetailResponse AccessMutualTLSCertificateDetailResponse + err = json.Unmarshal(res, &accessMutualTLSCertificateDetailResponse) + if err != nil { + return AccessMutualTLSCertificate{}, errors.Wrap(err, errUnmarshalError) + } + + return accessMutualTLSCertificateDetailResponse.Result, nil +} + +// DeleteAccessMutualTLSCertificate destroys an account level Access Mutual +// TLS certificate. +// +// API reference: https://api.cloudflare.com/#access-mutual-tls-authentication-update-access-certificate +func (api *API) DeleteAccessMutualTLSCertificate(ctx context.Context, accountID, certificateID string) error { + return api.deleteAccessMutualTLSCertificate(ctx, accountID, certificateID, AccountRouteRoot) +} + +// DeleteZoneAccessMutualTLSCertificate destroys a zone level Access Mutual TLS +// certificate. +// +// API reference: https://api.cloudflare.com/#zone-level-access-mutual-tls-authentication-update-access-certificate +func (api *API) DeleteZoneAccessMutualTLSCertificate(ctx context.Context, zoneID, certificateID string) error { + return api.deleteAccessMutualTLSCertificate(ctx, zoneID, certificateID, ZoneRouteRoot) +} + +func (api *API) deleteAccessMutualTLSCertificate(ctx context.Context, id, certificateID string, routeRoot RouteRoot) error { + uri := fmt.Sprintf( + "/%s/%s/access/certificates/%s", + routeRoot, + id, + certificateID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + var accessMutualTLSCertificateDetailResponse AccessMutualTLSCertificateDetailResponse + err = json.Unmarshal(res, &accessMutualTLSCertificateDetailResponse) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/access_organization.go b/vendor/github.com/cloudflare/cloudflare-go/access_organization.go new file mode 100644 index 0000000000000..774c3febaff46 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/access_organization.go @@ -0,0 +1,137 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// AccessOrganization represents an Access organization. +type AccessOrganization struct { + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + Name string `json:"name"` + AuthDomain string `json:"auth_domain"` + LoginDesign AccessOrganizationLoginDesign `json:"login_design"` +} + +// AccessOrganizationLoginDesign represents the login design options. +type AccessOrganizationLoginDesign struct { + BackgroundColor string `json:"background_color"` + TextColor string `json:"text_color"` + LogoPath string `json:"logo_path"` +} + +// AccessOrganizationListResponse represents the response from the list +// access organization endpoint. +type AccessOrganizationListResponse struct { + Result AccessOrganization `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccessOrganizationDetailResponse is the API response, containing a +// single access organization. +type AccessOrganizationDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccessOrganization `json:"result"` +} + +// AccessOrganization returns the Access organisation details. +// +// API reference: https://api.cloudflare.com/#access-organizations-access-organization-details +func (api *API) AccessOrganization(ctx context.Context, accountID string) (AccessOrganization, ResultInfo, error) { + return api.accessOrganization(ctx, accountID, AccountRouteRoot) +} + +// ZoneLevelAccessOrganization returns the zone level Access organisation details. +// +// API reference: https://api.cloudflare.com/#zone-level-access-organizations-access-organization-details +func (api *API) ZoneLevelAccessOrganization(ctx context.Context, zoneID string) (AccessOrganization, ResultInfo, error) { + return api.accessOrganization(ctx, zoneID, ZoneRouteRoot) +} + +func (api *API) accessOrganization(ctx context.Context, id string, routeRoot RouteRoot) (AccessOrganization, ResultInfo, error) { + uri := fmt.Sprintf("/%s/%s/access/organizations", routeRoot, id) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AccessOrganization{}, ResultInfo{}, err + } + + var accessOrganizationListResponse AccessOrganizationListResponse + err = json.Unmarshal(res, &accessOrganizationListResponse) + if err != nil { + return AccessOrganization{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return accessOrganizationListResponse.Result, accessOrganizationListResponse.ResultInfo, nil +} + +// CreateAccessOrganization creates the Access organisation details. +// +// API reference: https://api.cloudflare.com/#access-organizations-create-access-organization +func (api *API) CreateAccessOrganization(ctx context.Context, accountID string, accessOrganization AccessOrganization) (AccessOrganization, error) { + return api.createAccessOrganization(ctx, accountID, accessOrganization, AccountRouteRoot) +} + +// CreateZoneLevelAccessOrganization creates the zone level Access organisation details. +// +// API reference: https://api.cloudflare.com/#zone-level-access-organizations-create-access-organization +func (api *API) CreateZoneLevelAccessOrganization(ctx context.Context, zoneID string, accessOrganization AccessOrganization) (AccessOrganization, error) { + return api.createAccessOrganization(ctx, zoneID, accessOrganization, ZoneRouteRoot) +} + +func (api *API) createAccessOrganization(ctx context.Context, id string, accessOrganization AccessOrganization, routeRoot RouteRoot) (AccessOrganization, error) { + uri := fmt.Sprintf("/%s/%s/access/organizations", routeRoot, id) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, accessOrganization) + if err != nil { + return AccessOrganization{}, err + } + + var accessOrganizationDetailResponse AccessOrganizationDetailResponse + err = json.Unmarshal(res, &accessOrganizationDetailResponse) + if err != nil { + return AccessOrganization{}, errors.Wrap(err, errUnmarshalError) + } + + return accessOrganizationDetailResponse.Result, nil +} + +// UpdateAccessOrganization updates the Access organisation details. +// +// API reference: https://api.cloudflare.com/#access-organizations-update-access-organization +func (api *API) UpdateAccessOrganization(ctx context.Context, accountID string, accessOrganization AccessOrganization) (AccessOrganization, error) { + return api.updateAccessOrganization(ctx, accountID, accessOrganization, AccountRouteRoot) +} + +// UpdateZoneLevelAccessOrganization updates the zone level Access organisation details. +// +// API reference: https://api.cloudflare.com/#zone-level-access-organizations-update-access-organization +func (api *API) UpdateZoneLevelAccessOrganization(ctx context.Context, zoneID string, accessOrganization AccessOrganization) (AccessOrganization, error) { + return api.updateAccessOrganization(ctx, zoneID, accessOrganization, ZoneRouteRoot) +} + +func (api *API) updateAccessOrganization(ctx context.Context, id string, accessOrganization AccessOrganization, routeRoot RouteRoot) (AccessOrganization, error) { + uri := fmt.Sprintf("/%s/%s/access/organizations", routeRoot, id) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, accessOrganization) + if err != nil { + return AccessOrganization{}, err + } + + var accessOrganizationDetailResponse AccessOrganizationDetailResponse + err = json.Unmarshal(res, &accessOrganizationDetailResponse) + if err != nil { + return AccessOrganization{}, errors.Wrap(err, errUnmarshalError) + } + + return accessOrganizationDetailResponse.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/access_policy.go b/vendor/github.com/cloudflare/cloudflare-go/access_policy.go new file mode 100644 index 0000000000000..022ab4495f8c6 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/access_policy.go @@ -0,0 +1,256 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/pkg/errors" +) + +type AccessApprovalGroup struct { + EmailListUuid string `json:"email_list_uuid,omitempty"` + EmailAddresses []string `json:"email_addresses,omitempty"` + ApprovalsNeeded int `json:"approvals_needed,omitempty"` +} + +// AccessPolicy defines a policy for allowing or disallowing access to +// one or more Access applications. +type AccessPolicy struct { + ID string `json:"id,omitempty"` + Precedence int `json:"precedence"` + Decision string `json:"decision"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + Name string `json:"name"` + + PurposeJustificationRequired *bool `json:"purpose_justification_required,omitempty"` + PurposeJustificationPrompt *string `json:"purpose_justification_prompt,omitempty"` + ApprovalRequired *bool `json:"approval_required,omitempty"` + ApprovalGroups []AccessApprovalGroup `json:"approval_groups"` + + // The include policy works like an OR logical operator. The user must + // satisfy one of the rules. + Include []interface{} `json:"include"` + + // The exclude policy works like a NOT logical operator. The user must + // not satisfy all of the rules in exclude. + Exclude []interface{} `json:"exclude"` + + // The require policy works like a AND logical operator. The user must + // satisfy all of the rules in require. + Require []interface{} `json:"require"` +} + +// AccessPolicyListResponse represents the response from the list +// access policies endpoint. +type AccessPolicyListResponse struct { + Result []AccessPolicy `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccessPolicyDetailResponse is the API response, containing a single +// access policy. +type AccessPolicyDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccessPolicy `json:"result"` +} + +// AccessPolicies returns all access policies for an access application. +// +// API reference: https://api.cloudflare.com/#access-policy-list-access-policies +func (api *API) AccessPolicies(ctx context.Context, accountID, applicationID string, pageOpts PaginationOptions) ([]AccessPolicy, ResultInfo, error) { + return api.accessPolicies(ctx, accountID, applicationID, pageOpts, AccountRouteRoot) +} + +// ZoneLevelAccessPolicies returns all zone level access policies for an access application. +// +// API reference: https://api.cloudflare.com/#zone-level-access-policy-list-access-policies +func (api *API) ZoneLevelAccessPolicies(ctx context.Context, zoneID, applicationID string, pageOpts PaginationOptions) ([]AccessPolicy, ResultInfo, error) { + return api.accessPolicies(ctx, zoneID, applicationID, pageOpts, ZoneRouteRoot) +} + +func (api *API) accessPolicies(ctx context.Context, id string, applicationID string, pageOpts PaginationOptions, routeRoot RouteRoot) ([]AccessPolicy, ResultInfo, error) { + v := url.Values{} + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s/policies", + routeRoot, + id, + applicationID, + ) + + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []AccessPolicy{}, ResultInfo{}, err + } + + var accessPolicyListResponse AccessPolicyListResponse + err = json.Unmarshal(res, &accessPolicyListResponse) + if err != nil { + return []AccessPolicy{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return accessPolicyListResponse.Result, accessPolicyListResponse.ResultInfo, nil +} + +// AccessPolicy returns a single policy based on the policy ID. +// +// API reference: https://api.cloudflare.com/#access-policy-access-policy-details +func (api *API) AccessPolicy(ctx context.Context, accountID, applicationID, policyID string) (AccessPolicy, error) { + return api.accessPolicy(ctx, accountID, applicationID, policyID, AccountRouteRoot) +} + +// ZoneLevelAccessPolicy returns a single zone level policy based on the policy ID. +// +// API reference: https://api.cloudflare.com/#zone-level-access-policy-access-policy-details +func (api *API) ZoneLevelAccessPolicy(ctx context.Context, zoneID, applicationID, policyID string) (AccessPolicy, error) { + return api.accessPolicy(ctx, zoneID, applicationID, policyID, ZoneRouteRoot) +} + +func (api *API) accessPolicy(ctx context.Context, id string, applicationID string, policyID string, routeRoot RouteRoot) (AccessPolicy, error) { + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s/policies/%s", + routeRoot, + id, + applicationID, + policyID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AccessPolicy{}, err + } + + var accessPolicyDetailResponse AccessPolicyDetailResponse + err = json.Unmarshal(res, &accessPolicyDetailResponse) + if err != nil { + return AccessPolicy{}, errors.Wrap(err, errUnmarshalError) + } + + return accessPolicyDetailResponse.Result, nil +} + +// CreateAccessPolicy creates a new access policy. +// +// API reference: https://api.cloudflare.com/#access-policy-create-access-policy +func (api *API) CreateAccessPolicy(ctx context.Context, accountID, applicationID string, accessPolicy AccessPolicy) (AccessPolicy, error) { + return api.createAccessPolicy(ctx, accountID, applicationID, accessPolicy, AccountRouteRoot) +} + +// CreateZoneLevelAccessPolicy creates a new zone level access policy. +// +// API reference: https://api.cloudflare.com/#zone-level-access-policy-create-access-policy +func (api *API) CreateZoneLevelAccessPolicy(ctx context.Context, zoneID, applicationID string, accessPolicy AccessPolicy) (AccessPolicy, error) { + return api.createAccessPolicy(ctx, zoneID, applicationID, accessPolicy, ZoneRouteRoot) +} + +func (api *API) createAccessPolicy(ctx context.Context, id, applicationID string, accessPolicy AccessPolicy, routeRoot RouteRoot) (AccessPolicy, error) { + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s/policies", + routeRoot, + id, + applicationID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, accessPolicy) + if err != nil { + return AccessPolicy{}, err + } + + var accessPolicyDetailResponse AccessPolicyDetailResponse + err = json.Unmarshal(res, &accessPolicyDetailResponse) + if err != nil { + return AccessPolicy{}, errors.Wrap(err, errUnmarshalError) + } + + return accessPolicyDetailResponse.Result, nil +} + +// UpdateAccessPolicy updates an existing access policy. +// +// API reference: https://api.cloudflare.com/#access-policy-update-access-policy +func (api *API) UpdateAccessPolicy(ctx context.Context, accountID, applicationID string, accessPolicy AccessPolicy) (AccessPolicy, error) { + return api.updateAccessPolicy(ctx, accountID, applicationID, accessPolicy, AccountRouteRoot) +} + +// UpdateZoneLevelAccessPolicy updates an existing zone level access policy. +// +// API reference: https://api.cloudflare.com/#zone-level-access-policy-update-access-policy +func (api *API) UpdateZoneLevelAccessPolicy(ctx context.Context, zoneID, applicationID string, accessPolicy AccessPolicy) (AccessPolicy, error) { + return api.updateAccessPolicy(ctx, zoneID, applicationID, accessPolicy, ZoneRouteRoot) +} + +func (api *API) updateAccessPolicy(ctx context.Context, id, applicationID string, accessPolicy AccessPolicy, routeRoot RouteRoot) (AccessPolicy, error) { + if accessPolicy.ID == "" { + return AccessPolicy{}, errors.Errorf("access policy ID cannot be empty") + } + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s/policies/%s", + routeRoot, + id, + applicationID, + accessPolicy.ID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, accessPolicy) + if err != nil { + return AccessPolicy{}, err + } + + var accessPolicyDetailResponse AccessPolicyDetailResponse + err = json.Unmarshal(res, &accessPolicyDetailResponse) + if err != nil { + return AccessPolicy{}, errors.Wrap(err, errUnmarshalError) + } + + return accessPolicyDetailResponse.Result, nil +} + +// DeleteAccessPolicy deletes an access policy. +// +// API reference: https://api.cloudflare.com/#access-policy-update-access-policy +func (api *API) DeleteAccessPolicy(ctx context.Context, accountID, applicationID, accessPolicyID string) error { + return api.deleteAccessPolicy(ctx, accountID, applicationID, accessPolicyID, AccountRouteRoot) +} + +// DeleteZoneLevelAccessPolicy deletes a zone level access policy. +// +// API reference: https://api.cloudflare.com/#zone-level-access-policy-delete-access-policy +func (api *API) DeleteZoneLevelAccessPolicy(ctx context.Context, zoneID, applicationID, accessPolicyID string) error { + return api.deleteAccessPolicy(ctx, zoneID, applicationID, accessPolicyID, ZoneRouteRoot) +} + +func (api *API) deleteAccessPolicy(ctx context.Context, id, applicationID, accessPolicyID string, routeRoot RouteRoot) error { + uri := fmt.Sprintf( + "/%s/%s/access/apps/%s/policies/%s", + routeRoot, + id, + applicationID, + accessPolicyID, + ) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/access_service_tokens.go b/vendor/github.com/cloudflare/cloudflare-go/access_service_tokens.go new file mode 100644 index 0000000000000..110c1dbed482b --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/access_service_tokens.go @@ -0,0 +1,217 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// AccessServiceToken represents an Access Service Token. +type AccessServiceToken struct { + ClientID string `json:"client_id"` + CreatedAt *time.Time `json:"created_at"` + ExpiresAt *time.Time `json:"expires_at"` + ID string `json:"id"` + Name string `json:"name"` + UpdatedAt *time.Time `json:"updated_at"` +} + +// AccessServiceTokenUpdateResponse represents the response from the API +// when a new Service Token is updated. This base struct is also used in the +// Create as they are very similar responses. +type AccessServiceTokenUpdateResponse struct { + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + ExpiresAt *time.Time `json:"expires_at"` + ID string `json:"id"` + Name string `json:"name"` + ClientID string `json:"client_id"` +} + +// AccessServiceTokenCreateResponse is the same API response as the Update +// operation with the exception that the `ClientSecret` is present in a +// Create operation. +type AccessServiceTokenCreateResponse struct { + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + ExpiresAt *time.Time `json:"expires_at"` + ID string `json:"id"` + Name string `json:"name"` + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` +} + +// AccessServiceTokensListResponse represents the response from the list +// Access Service Tokens endpoint. +type AccessServiceTokensListResponse struct { + Result []AccessServiceToken `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccessServiceTokensDetailResponse is the API response, containing a single +// Access Service Token. +type AccessServiceTokensDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccessServiceToken `json:"result"` +} + +// AccessServiceTokensCreationDetailResponse is the API response, containing a +// single Access Service Token. +type AccessServiceTokensCreationDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccessServiceTokenCreateResponse `json:"result"` +} + +// AccessServiceTokensUpdateDetailResponse is the API response, containing a +// single Access Service Token. +type AccessServiceTokensUpdateDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccessServiceTokenUpdateResponse `json:"result"` +} + +// AccessServiceTokens returns all Access Service Tokens for an account. +// +// API reference: https://api.cloudflare.com/#access-service-tokens-list-access-service-tokens +func (api *API) AccessServiceTokens(ctx context.Context, accountID string) ([]AccessServiceToken, ResultInfo, error) { + return api.accessServiceTokens(ctx, accountID, AccountRouteRoot) +} + +// ZoneLevelAccessServiceTokens returns all Access Service Tokens for a zone. +// +// API reference: https://api.cloudflare.com/#zone-level-access-service-tokens-list-access-service-tokens +func (api *API) ZoneLevelAccessServiceTokens(ctx context.Context, zoneID string) ([]AccessServiceToken, ResultInfo, error) { + return api.accessServiceTokens(ctx, zoneID, ZoneRouteRoot) +} + +func (api *API) accessServiceTokens(ctx context.Context, id string, routeRoot RouteRoot) ([]AccessServiceToken, ResultInfo, error) { + uri := fmt.Sprintf("/%s/%s/access/service_tokens", routeRoot, id) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []AccessServiceToken{}, ResultInfo{}, err + } + + var accessServiceTokensListResponse AccessServiceTokensListResponse + err = json.Unmarshal(res, &accessServiceTokensListResponse) + if err != nil { + return []AccessServiceToken{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return accessServiceTokensListResponse.Result, accessServiceTokensListResponse.ResultInfo, nil +} + +// CreateAccessServiceToken creates a new Access Service Token for an account. +// +// API reference: https://api.cloudflare.com/#access-service-tokens-create-access-service-token +func (api *API) CreateAccessServiceToken(ctx context.Context, accountID, name string) (AccessServiceTokenCreateResponse, error) { + return api.createAccessServiceToken(ctx, accountID, name, AccountRouteRoot) +} + +// CreateZoneLevelAccessServiceToken creates a new Access Service Token for a zone. +// +// API reference: https://api.cloudflare.com/#zone-level-access-service-tokens-create-access-service-token +func (api *API) CreateZoneLevelAccessServiceToken(ctx context.Context, zoneID, name string) (AccessServiceTokenCreateResponse, error) { + return api.createAccessServiceToken(ctx, zoneID, name, ZoneRouteRoot) +} + +func (api *API) createAccessServiceToken(ctx context.Context, id, name string, routeRoot RouteRoot) (AccessServiceTokenCreateResponse, error) { + uri := fmt.Sprintf("/%s/%s/access/service_tokens", routeRoot, id) + marshalledName, _ := json.Marshal(struct { + Name string `json:"name"` + }{name}) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, marshalledName) + + if err != nil { + return AccessServiceTokenCreateResponse{}, err + } + + var accessServiceTokenCreation AccessServiceTokensCreationDetailResponse + err = json.Unmarshal(res, &accessServiceTokenCreation) + if err != nil { + return AccessServiceTokenCreateResponse{}, errors.Wrap(err, errUnmarshalError) + } + + return accessServiceTokenCreation.Result, nil +} + +// UpdateAccessServiceToken updates an existing Access Service Token for an +// account. +// +// API reference: https://api.cloudflare.com/#access-service-tokens-update-access-service-token +func (api *API) UpdateAccessServiceToken(ctx context.Context, accountID, uuid, name string) (AccessServiceTokenUpdateResponse, error) { + return api.updateAccessServiceToken(ctx, accountID, uuid, name, AccountRouteRoot) +} + +// UpdateZoneLevelAccessServiceToken updates an existing Access Service Token for a +// zone. +// +// API reference: https://api.cloudflare.com/#zone-level-access-service-tokens-update-access-service-token +func (api *API) UpdateZoneLevelAccessServiceToken(ctx context.Context, zoneID, uuid, name string) (AccessServiceTokenUpdateResponse, error) { + return api.updateAccessServiceToken(ctx, zoneID, uuid, name, ZoneRouteRoot) +} + +func (api *API) updateAccessServiceToken(ctx context.Context, id, uuid, name string, routeRoot RouteRoot) (AccessServiceTokenUpdateResponse, error) { + uri := fmt.Sprintf("/%s/%s/access/service_tokens/%s", routeRoot, id, uuid) + + marshalledName, _ := json.Marshal(struct { + Name string `json:"name"` + }{name}) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, marshalledName) + if err != nil { + return AccessServiceTokenUpdateResponse{}, err + } + + var accessServiceTokenUpdate AccessServiceTokensUpdateDetailResponse + err = json.Unmarshal(res, &accessServiceTokenUpdate) + if err != nil { + return AccessServiceTokenUpdateResponse{}, errors.Wrap(err, errUnmarshalError) + } + + return accessServiceTokenUpdate.Result, nil +} + +// DeleteAccessServiceToken removes an existing Access Service Token for an +// account. +// +// API reference: https://api.cloudflare.com/#access-service-tokens-delete-access-service-token +func (api *API) DeleteAccessServiceToken(ctx context.Context, accountID, uuid string) (AccessServiceTokenUpdateResponse, error) { + return api.deleteAccessServiceToken(ctx, accountID, uuid, AccountRouteRoot) +} + +// DeleteZoneLevelAccessServiceToken removes an existing Access Service Token for a +// zone. +// +// API reference: https://api.cloudflare.com/#zone-level-access-service-tokens-delete-access-service-token +func (api *API) DeleteZoneLevelAccessServiceToken(ctx context.Context, zoneID, uuid string) (AccessServiceTokenUpdateResponse, error) { + return api.deleteAccessServiceToken(ctx, zoneID, uuid, ZoneRouteRoot) +} + +func (api *API) deleteAccessServiceToken(ctx context.Context, id, uuid string, routeRoot RouteRoot) (AccessServiceTokenUpdateResponse, error) { + uri := fmt.Sprintf("/%s/%s/access/service_tokens/%s", routeRoot, id, uuid) + + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return AccessServiceTokenUpdateResponse{}, err + } + + var accessServiceTokenUpdate AccessServiceTokensUpdateDetailResponse + err = json.Unmarshal(res, &accessServiceTokenUpdate) + if err != nil { + return AccessServiceTokenUpdateResponse{}, errors.Wrap(err, errUnmarshalError) + } + + return accessServiceTokenUpdate.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/account_members.go b/vendor/github.com/cloudflare/cloudflare-go/account_members.go new file mode 100644 index 0000000000000..ba2c5544783fa --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/account_members.go @@ -0,0 +1,200 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + + "github.com/pkg/errors" +) + +// AccountMember is the definition of a member of an account. +type AccountMember struct { + ID string `json:"id"` + Code string `json:"code"` + User AccountMemberUserDetails `json:"user"` + Status string `json:"status"` + Roles []AccountRole `json:"roles"` +} + +// AccountMemberUserDetails outlines all the personal information about +// a member. +type AccountMemberUserDetails struct { + ID string `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Email string `json:"email"` + TwoFactorAuthenticationEnabled bool `json:"two_factor_authentication_enabled"` +} + +// AccountMembersListResponse represents the response from the list +// account members endpoint. +type AccountMembersListResponse struct { + Result []AccountMember `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccountMemberDetailResponse is the API response, containing a single +// account member. +type AccountMemberDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccountMember `json:"result"` +} + +// AccountMemberInvitation represents the invitation for a new member to +// the account. +type AccountMemberInvitation struct { + Email string `json:"email"` + Roles []string `json:"roles"` + Status string `json:"status,omitempty"` +} + +// AccountMembers returns all members of an account. +// +// API reference: https://api.cloudflare.com/#accounts-list-accounts +func (api *API) AccountMembers(ctx context.Context, accountID string, pageOpts PaginationOptions) ([]AccountMember, ResultInfo, error) { + if accountID == "" { + return []AccountMember{}, ResultInfo{}, errors.New(errMissingAccountID) + } + + v := url.Values{} + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + uri := fmt.Sprintf("/accounts/%s/members", accountID) + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []AccountMember{}, ResultInfo{}, err + } + + var accountMemberListresponse AccountMembersListResponse + err = json.Unmarshal(res, &accountMemberListresponse) + if err != nil { + return []AccountMember{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return accountMemberListresponse.Result, accountMemberListresponse.ResultInfo, nil +} + +// CreateAccountMemberWithStatus invites a new member to join an account, allowing setting the status. +// +// Refer to the API reference for valid statuses. +// +// API reference: https://api.cloudflare.com/#account-members-add-member +func (api *API) CreateAccountMemberWithStatus(ctx context.Context, accountID string, emailAddress string, roles []string, status string) (AccountMember, error) { + if accountID == "" { + return AccountMember{}, errors.New(errMissingAccountID) + } + + uri := fmt.Sprintf("/accounts/%s/members", accountID) + + var newMember = AccountMemberInvitation{ + Email: emailAddress, + Roles: roles, + Status: status, + } + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, newMember) + if err != nil { + return AccountMember{}, err + } + + var accountMemberListResponse AccountMemberDetailResponse + err = json.Unmarshal(res, &accountMemberListResponse) + if err != nil { + return AccountMember{}, errors.Wrap(err, errUnmarshalError) + } + + return accountMemberListResponse.Result, nil +} + +// CreateAccountMember invites a new member to join an account. +// The member will be placed into "pending" status and receive an email confirmation. +// +// API reference: https://api.cloudflare.com/#account-members-add-member +func (api *API) CreateAccountMember(ctx context.Context, accountID string, emailAddress string, roles []string) (AccountMember, error) { + return api.CreateAccountMemberWithStatus(ctx, accountID, emailAddress, roles, "") +} + +// DeleteAccountMember removes a member from an account. +// +// API reference: https://api.cloudflare.com/#account-members-remove-member +func (api *API) DeleteAccountMember(ctx context.Context, accountID string, userID string) error { + if accountID == "" { + return errors.New(errMissingAccountID) + } + + uri := fmt.Sprintf("/accounts/%s/members/%s", accountID, userID) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} + +// UpdateAccountMember modifies an existing account member. +// +// API reference: https://api.cloudflare.com/#account-members-update-member +func (api *API) UpdateAccountMember(ctx context.Context, accountID string, userID string, member AccountMember) (AccountMember, error) { + if accountID == "" { + return AccountMember{}, errors.New(errMissingAccountID) + } + + uri := fmt.Sprintf("/accounts/%s/members/%s", accountID, userID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, member) + if err != nil { + return AccountMember{}, err + } + + var accountMemberListResponse AccountMemberDetailResponse + err = json.Unmarshal(res, &accountMemberListResponse) + if err != nil { + return AccountMember{}, errors.Wrap(err, errUnmarshalError) + } + + return accountMemberListResponse.Result, nil +} + +// AccountMember returns details of a single account member. +// +// API reference: https://api.cloudflare.com/#account-members-member-details +func (api *API) AccountMember(ctx context.Context, accountID string, memberID string) (AccountMember, error) { + if accountID == "" { + return AccountMember{}, errors.New(errMissingAccountID) + } + + uri := fmt.Sprintf( + "/accounts/%s/members/%s", + accountID, + memberID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AccountMember{}, err + } + + var accountMemberResponse AccountMemberDetailResponse + err = json.Unmarshal(res, &accountMemberResponse) + if err != nil { + return AccountMember{}, errors.Wrap(err, errUnmarshalError) + } + + return accountMemberResponse.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/account_roles.go b/vendor/github.com/cloudflare/cloudflare-go/account_roles.go new file mode 100644 index 0000000000000..0a0662ec4787d --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/account_roles.go @@ -0,0 +1,82 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +// AccountRole defines the roles that a member can have attached. +type AccountRole struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Permissions map[string]AccountRolePermission `json:"permissions"` +} + +// AccountRolePermission is the shared structure for all permissions +// that can be assigned to a member. +type AccountRolePermission struct { + Read bool `json:"read"` + Edit bool `json:"edit"` +} + +// AccountRolesListResponse represents the list response from the +// account roles. +type AccountRolesListResponse struct { + Result []AccountRole `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccountRoleDetailResponse is the API response, containing a single +// account role. +type AccountRoleDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result AccountRole `json:"result"` +} + +// AccountRoles returns all roles of an account. +// +// API reference: https://api.cloudflare.com/#account-roles-list-roles +func (api *API) AccountRoles(ctx context.Context, accountID string) ([]AccountRole, error) { + uri := fmt.Sprintf("/accounts/%s/roles", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []AccountRole{}, err + } + + var accountRolesListResponse AccountRolesListResponse + err = json.Unmarshal(res, &accountRolesListResponse) + if err != nil { + return []AccountRole{}, errors.Wrap(err, errUnmarshalError) + } + + return accountRolesListResponse.Result, nil +} + +// AccountRole returns the details of a single account role. +// +// API reference: https://api.cloudflare.com/#account-roles-role-details +func (api *API) AccountRole(ctx context.Context, accountID string, roleID string) (AccountRole, error) { + uri := fmt.Sprintf("/accounts/%s/roles/%s", accountID, roleID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AccountRole{}, err + } + + var accountRole AccountRoleDetailResponse + err = json.Unmarshal(res, &accountRole) + if err != nil { + return AccountRole{}, errors.Wrap(err, errUnmarshalError) + } + + return accountRole.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/accounts.go b/vendor/github.com/cloudflare/cloudflare-go/accounts.go new file mode 100644 index 0000000000000..b4f9ce8f277c6 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/accounts.go @@ -0,0 +1,158 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + + "github.com/pkg/errors" +) + +// AccountSettings outlines the available options for an account. +type AccountSettings struct { + EnforceTwoFactor bool `json:"enforce_twofactor"` +} + +// Account represents the root object that owns resources. +type Account struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` + Settings *AccountSettings `json:"settings,omitempty"` +} + +// AccountResponse represents the response from the accounts endpoint for a +// single account ID. +type AccountResponse struct { + Result Account `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccountListResponse represents the response from the list accounts endpoint. +type AccountListResponse struct { + Result []Account `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccountDetailResponse is the API response, containing a single Account. +type AccountDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result Account `json:"result"` +} + +// Accounts returns all accounts the logged in user has access to. +// +// API reference: https://api.cloudflare.com/#accounts-list-accounts +func (api *API) Accounts(ctx context.Context, pageOpts PaginationOptions) ([]Account, ResultInfo, error) { + v := url.Values{} + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + uri := "/accounts" + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []Account{}, ResultInfo{}, err + } + + var accListResponse AccountListResponse + err = json.Unmarshal(res, &accListResponse) + if err != nil { + return []Account{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + return accListResponse.Result, accListResponse.ResultInfo, nil +} + +// Account returns a single account based on the ID. +// +// API reference: https://api.cloudflare.com/#accounts-account-details +func (api *API) Account(ctx context.Context, accountID string) (Account, ResultInfo, error) { + uri := fmt.Sprintf("/accounts/%s", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return Account{}, ResultInfo{}, err + } + + var accResponse AccountResponse + err = json.Unmarshal(res, &accResponse) + if err != nil { + return Account{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return accResponse.Result, accResponse.ResultInfo, nil +} + +// UpdateAccount allows management of an account using the account ID. +// +// API reference: https://api.cloudflare.com/#accounts-update-account +func (api *API) UpdateAccount(ctx context.Context, accountID string, account Account) (Account, error) { + uri := fmt.Sprintf("/accounts/%s", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, account) + if err != nil { + return Account{}, err + } + + var a AccountDetailResponse + err = json.Unmarshal(res, &a) + if err != nil { + return Account{}, errors.Wrap(err, errUnmarshalError) + } + + return a.Result, nil +} + +// CreateAccount creates a new account. Note: This requires the Tenant +// entitlement. +// +// API reference: https://developers.cloudflare.com/tenant/tutorial/provisioning-resources#creating-an-account +func (api *API) CreateAccount(ctx context.Context, account Account) (Account, error) { + uri := "/accounts" + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, account) + if err != nil { + return Account{}, err + } + + var a AccountDetailResponse + err = json.Unmarshal(res, &a) + if err != nil { + return Account{}, errors.Wrap(err, errUnmarshalError) + } + + return a.Result, nil +} + +// DeleteAccount removes an account. Note: This requires the Tenant +// entitlement. +// +// API reference: https://developers.cloudflare.com/tenant/tutorial/provisioning-resources#optional-deleting-accounts +func (api *API) DeleteAccount(ctx context.Context, accountID string) error { + if accountID == "" { + return errors.New(errMissingAccountID) + } + + uri := fmt.Sprintf("/accounts/%s", accountID) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/api_token.go b/vendor/github.com/cloudflare/cloudflare-go/api_token.go new file mode 100644 index 0000000000000..0eb9bf7aee262 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/api_token.go @@ -0,0 +1,239 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// APIToken is the full API token. +type APIToken struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` + Status string `json:"status,omitempty"` + IssuedOn *time.Time `json:"issued_on,omitempty"` + ModifiedOn *time.Time `json:"modified_on,omitempty"` + NotBefore *time.Time `json:"not_before,omitempty"` + ExpiresOn *time.Time `json:"expires_on,omitempty"` + Policies []APITokenPolicies `json:"policies"` + Condition *APITokenCondition `json:"condition,omitempty"` + Value string `json:"value,omitempty"` +} + +// APITokenPermissionGroups is the permission groups associated with API tokens. +type APITokenPermissionGroups struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Scopes []string `json:"scopes,omitempty"` +} + +// APITokenPolicies are policies attached to an API token. +type APITokenPolicies struct { + ID string `json:"id,omitempty"` + Effect string `json:"effect"` + Resources map[string]interface{} `json:"resources"` + PermissionGroups []APITokenPermissionGroups `json:"permission_groups"` +} + +// APITokenRequestIPCondition is the struct for adding an IP restriction to an +// API token. +type APITokenRequestIPCondition struct { + In []string `json:"in,omitempty"` + NotIn []string `json:"not_in,omitempty"` +} + +// APITokenCondition is the outer structure for request conditions (currently +// only IPs). +type APITokenCondition struct { + RequestIP *APITokenRequestIPCondition `json:"request.ip,omitempty"` +} + +// APITokenResponse is the API response for a single API token. +type APITokenResponse struct { + Response + Result APIToken `json:"result"` +} + +// APITokenListResponse is the API response for multiple API tokens. +type APITokenListResponse struct { + Response + Result []APIToken `json:"result"` +} + +// APITokenRollResponse is the API response when rolling the token. +type APITokenRollResponse struct { + Response + Result string `json:"result"` +} + +// APITokenVerifyResponse is the API response for verifying a token. +type APITokenVerifyResponse struct { + Response + Result APITokenVerifyBody `json:"result"` +} + +// APITokenPermissionGroupsResponse is the API response for the available +// permission groups. +type APITokenPermissionGroupsResponse struct { + Response + Result []APITokenPermissionGroups `json:"result"` +} + +// APITokenVerifyBody is the API body for verifying a token. +type APITokenVerifyBody struct { + ID string `json:"id"` + Status string `json:"status"` + NotBefore time.Time `json:"not_before"` + ExpiresOn time.Time `json:"expires_on"` +} + +// GetAPIToken returns a single API token based on the ID. +// +// API reference: https://api.cloudflare.com/#user-api-tokens-token-details +func (api *API) GetAPIToken(ctx context.Context, tokenID string) (APIToken, error) { + uri := fmt.Sprintf("/user/tokens/%s", tokenID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return APIToken{}, err + } + + var apiTokenResponse APITokenResponse + err = json.Unmarshal(res, &apiTokenResponse) + if err != nil { + return APIToken{}, errors.Wrap(err, errUnmarshalError) + } + + return apiTokenResponse.Result, nil +} + +// APITokens returns all available API tokens. +// +// API reference: https://api.cloudflare.com/#user-api-tokens-list-tokens +func (api *API) APITokens(ctx context.Context) ([]APIToken, error) { + res, err := api.makeRequestContext(ctx, http.MethodGet, "/user/tokens", nil) + if err != nil { + return []APIToken{}, err + } + + var apiTokenListResponse APITokenListResponse + err = json.Unmarshal(res, &apiTokenListResponse) + if err != nil { + return []APIToken{}, errors.Wrap(err, errUnmarshalError) + } + + return apiTokenListResponse.Result, nil +} + +// CreateAPIToken creates a new token. Returns the API token that has been +// generated. +// +// The token value itself is only shown once (post create) and will present as +// `Value` from this method. If you fail to capture it at this point, you will +// need to roll the token in order to get a new value. +// +// API reference: https://api.cloudflare.com/#user-api-tokens-create-token +func (api *API) CreateAPIToken(ctx context.Context, token APIToken) (APIToken, error) { + res, err := api.makeRequestContext(ctx, http.MethodPost, "/user/tokens", token) + if err != nil { + return APIToken{}, err + } + + var createTokenAPIResponse APITokenResponse + err = json.Unmarshal(res, &createTokenAPIResponse) + if err != nil { + return APIToken{}, errors.Wrap(err, errUnmarshalError) + } + + return createTokenAPIResponse.Result, nil +} + +// UpdateAPIToken updates an existing API token. +// +// API reference: https://api.cloudflare.com/#user-api-tokens-update-token +func (api *API) UpdateAPIToken(ctx context.Context, tokenID string, token APIToken) (APIToken, error) { + res, err := api.makeRequestContext(ctx, http.MethodPut, "/user/tokens/"+tokenID, token) + if err != nil { + return APIToken{}, err + } + + var updatedTokenResponse APITokenResponse + err = json.Unmarshal(res, &updatedTokenResponse) + if err != nil { + return APIToken{}, errors.Wrap(err, errUnmarshalError) + } + + return updatedTokenResponse.Result, nil +} + +// RollAPIToken rolls the credential associated with the token. +// +// API reference: https://api.cloudflare.com/#user-api-tokens-roll-token +func (api *API) RollAPIToken(ctx context.Context, tokenID string) (string, error) { + uri := fmt.Sprintf("/user/tokens/%s/value", tokenID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, nil) + if err != nil { + return "", err + } + + var apiTokenRollResponse APITokenRollResponse + err = json.Unmarshal(res, &apiTokenRollResponse) + if err != nil { + return "", errors.Wrap(err, errUnmarshalError) + } + + return apiTokenRollResponse.Result, nil +} + +// VerifyAPIToken tests the validity of the token. +// +// API reference: https://api.cloudflare.com/#user-api-tokens-verify-token +func (api *API) VerifyAPIToken(ctx context.Context) (APITokenVerifyBody, error) { + res, err := api.makeRequestContext(ctx, http.MethodGet, "/user/tokens/verify", nil) + if err != nil { + return APITokenVerifyBody{}, err + } + + var apiTokenVerifyResponse APITokenVerifyResponse + err = json.Unmarshal(res, &apiTokenVerifyResponse) + if err != nil { + return APITokenVerifyBody{}, errors.Wrap(err, errUnmarshalError) + } + + return apiTokenVerifyResponse.Result, nil +} + +// DeleteAPIToken deletes a single API token. +// +// API reference: https://api.cloudflare.com/#user-api-tokens-delete-token +func (api *API) DeleteAPIToken(ctx context.Context, tokenID string) error { + _, err := api.makeRequestContext(ctx, http.MethodDelete, "/user/tokens/"+tokenID, nil) + if err != nil { + return err + } + + return nil +} + +// ListAPITokensPermissionGroups returns all available API token permission groups. +// +// API reference: https://api.cloudflare.com/#permission-groups-list-permission-groups +func (api *API) ListAPITokensPermissionGroups(ctx context.Context) ([]APITokenPermissionGroups, error) { + var r APITokenPermissionGroupsResponse + res, err := api.makeRequestContext(ctx, http.MethodGet, "/user/tokens/permission_groups", nil) + if err != nil { + return []APITokenPermissionGroups{}, err + } + + err = json.Unmarshal(res, &r) + if err != nil { + return []APITokenPermissionGroups{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/argo.go b/vendor/github.com/cloudflare/cloudflare-go/argo.go new file mode 100644 index 0000000000000..24b57516093e1 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/argo.go @@ -0,0 +1,122 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +var validSettingValues = []string{"on", "off"} + +// ArgoFeatureSetting is the structure of the API object for the +// argo smart routing and tiered caching settings. +type ArgoFeatureSetting struct { + Editable bool `json:"editable,omitempty"` + ID string `json:"id,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty"` + Value string `json:"value"` +} + +// ArgoDetailsResponse is the API response for the argo smart routing +// and tiered caching response. +type ArgoDetailsResponse struct { + Result ArgoFeatureSetting `json:"result"` + Response +} + +// ArgoSmartRouting returns the current settings for smart routing. +// +// API reference: https://api.cloudflare.com/#argo-smart-routing-get-argo-smart-routing-setting +func (api *API) ArgoSmartRouting(ctx context.Context, zoneID string) (ArgoFeatureSetting, error) { + uri := fmt.Sprintf("/zones/%s/argo/smart_routing", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return ArgoFeatureSetting{}, err + } + + var argoDetailsResponse ArgoDetailsResponse + err = json.Unmarshal(res, &argoDetailsResponse) + if err != nil { + return ArgoFeatureSetting{}, errors.Wrap(err, errUnmarshalError) + } + return argoDetailsResponse.Result, nil +} + +// UpdateArgoSmartRouting updates the setting for smart routing. +// +// API reference: https://api.cloudflare.com/#argo-smart-routing-patch-argo-smart-routing-setting +func (api *API) UpdateArgoSmartRouting(ctx context.Context, zoneID, settingValue string) (ArgoFeatureSetting, error) { + if !contains(validSettingValues, settingValue) { + return ArgoFeatureSetting{}, errors.New(fmt.Sprintf("invalid setting value '%s'. must be 'on' or 'off'", settingValue)) + } + + uri := fmt.Sprintf("/zones/%s/argo/smart_routing", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, ArgoFeatureSetting{Value: settingValue}) + if err != nil { + return ArgoFeatureSetting{}, err + } + + var argoDetailsResponse ArgoDetailsResponse + err = json.Unmarshal(res, &argoDetailsResponse) + if err != nil { + return ArgoFeatureSetting{}, errors.Wrap(err, errUnmarshalError) + } + return argoDetailsResponse.Result, nil +} + +// ArgoTieredCaching returns the current settings for tiered caching. +// +// API reference: TBA +func (api *API) ArgoTieredCaching(ctx context.Context, zoneID string) (ArgoFeatureSetting, error) { + uri := fmt.Sprintf("/zones/%s/argo/tiered_caching", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return ArgoFeatureSetting{}, err + } + + var argoDetailsResponse ArgoDetailsResponse + err = json.Unmarshal(res, &argoDetailsResponse) + if err != nil { + return ArgoFeatureSetting{}, errors.Wrap(err, errUnmarshalError) + } + return argoDetailsResponse.Result, nil +} + +// UpdateArgoTieredCaching updates the setting for tiered caching. +// +// API reference: TBA +func (api *API) UpdateArgoTieredCaching(ctx context.Context, zoneID, settingValue string) (ArgoFeatureSetting, error) { + if !contains(validSettingValues, settingValue) { + return ArgoFeatureSetting{}, errors.New(fmt.Sprintf("invalid setting value '%s'. must be 'on' or 'off'", settingValue)) + } + + uri := fmt.Sprintf("/zones/%s/argo/tiered_caching", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, ArgoFeatureSetting{Value: settingValue}) + if err != nil { + return ArgoFeatureSetting{}, err + } + + var argoDetailsResponse ArgoDetailsResponse + err = json.Unmarshal(res, &argoDetailsResponse) + if err != nil { + return ArgoFeatureSetting{}, errors.Wrap(err, errUnmarshalError) + } + return argoDetailsResponse.Result, nil +} + +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/argo_tunnel.go b/vendor/github.com/cloudflare/cloudflare-go/argo_tunnel.go new file mode 100644 index 0000000000000..4ad69ca60acf7 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/argo_tunnel.go @@ -0,0 +1,153 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// ArgoTunnel is the struct definition of a tunnel. +type ArgoTunnel struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Secret string `json:"tunnel_secret,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + DeletedAt *time.Time `json:"deleted_at,omitempty"` + Connections []ArgoTunnelConnection `json:"connections,omitempty"` +} + +// ArgoTunnelConnection represents the connections associated with a tunnel. +type ArgoTunnelConnection struct { + ColoName string `json:"colo_name"` + UUID string `json:"uuid"` + IsPendingReconnect bool `json:"is_pending_reconnect"` +} + +// ArgoTunnelsDetailResponse is used for representing the API response payload for +// multiple tunnels. +type ArgoTunnelsDetailResponse struct { + Result []ArgoTunnel `json:"result"` + Response +} + +// ArgoTunnelDetailResponse is used for representing the API response payload for +// a single tunnel. +type ArgoTunnelDetailResponse struct { + Result ArgoTunnel `json:"result"` + Response +} + +// ArgoTunnels lists all tunnels. +// +// API reference: https://api.cloudflare.com/#argo-tunnel-list-argo-tunnels +func (api *API) ArgoTunnels(ctx context.Context, accountID string) ([]ArgoTunnel, error) { + uri := fmt.Sprintf("/accounts/%s/tunnels", accountID) + + res, err := api.makeRequestContextWithHeaders(ctx, http.MethodGet, uri, nil, argoV1Header()) + if err != nil { + return []ArgoTunnel{}, err + } + + var argoDetailsResponse ArgoTunnelsDetailResponse + err = json.Unmarshal(res, &argoDetailsResponse) + if err != nil { + return []ArgoTunnel{}, errors.Wrap(err, errUnmarshalError) + } + return argoDetailsResponse.Result, nil +} + +// ArgoTunnel returns a single Argo tunnel. +// +// API reference: https://api.cloudflare.com/#argo-tunnel-get-argo-tunnel +func (api *API) ArgoTunnel(ctx context.Context, accountID, tunnelUUID string) (ArgoTunnel, error) { + uri := fmt.Sprintf("/accounts/%s/tunnels/%s", accountID, tunnelUUID) + + res, err := api.makeRequestContextWithHeaders(ctx, http.MethodGet, uri, nil, argoV1Header()) + if err != nil { + return ArgoTunnel{}, err + } + + var argoDetailsResponse ArgoTunnelDetailResponse + err = json.Unmarshal(res, &argoDetailsResponse) + if err != nil { + return ArgoTunnel{}, errors.Wrap(err, errUnmarshalError) + } + return argoDetailsResponse.Result, nil +} + +// CreateArgoTunnel creates a new tunnel for the account. +// +// API reference: https://api.cloudflare.com/#argo-tunnel-create-argo-tunnel +func (api *API) CreateArgoTunnel(ctx context.Context, accountID, name, secret string) (ArgoTunnel, error) { + uri := fmt.Sprintf("/accounts/%s/tunnels", accountID) + + tunnel := ArgoTunnel{Name: name, Secret: secret} + + res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPost, uri, tunnel, argoV1Header()) + if err != nil { + return ArgoTunnel{}, err + } + + var argoDetailsResponse ArgoTunnelDetailResponse + err = json.Unmarshal(res, &argoDetailsResponse) + if err != nil { + return ArgoTunnel{}, errors.Wrap(err, errUnmarshalError) + } + + return argoDetailsResponse.Result, nil +} + +// DeleteArgoTunnel removes a single Argo tunnel. +// +// API reference: https://api.cloudflare.com/#argo-tunnel-delete-argo-tunnel +func (api *API) DeleteArgoTunnel(ctx context.Context, accountID, tunnelUUID string) error { + uri := fmt.Sprintf("/accounts/%s/tunnels/%s", accountID, tunnelUUID) + + res, err := api.makeRequestContextWithHeaders(ctx, http.MethodDelete, uri, nil, argoV1Header()) + if err != nil { + return err + } + + var argoDetailsResponse ArgoTunnelDetailResponse + err = json.Unmarshal(res, &argoDetailsResponse) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + + return nil +} + +// CleanupArgoTunnelConnections deletes any inactive connections on a tunnel. +// +// API reference: https://api.cloudflare.com/#argo-tunnel-clean-up-argo-tunnel-connections +func (api *API) CleanupArgoTunnelConnections(ctx context.Context, accountID, tunnelUUID string) error { + uri := fmt.Sprintf("/accounts/%s/tunnels/%s/connections", accountID, tunnelUUID) + + res, err := api.makeRequestContextWithHeaders(ctx, http.MethodDelete, uri, nil, argoV1Header()) + if err != nil { + return err + } + + var argoDetailsResponse ArgoTunnelDetailResponse + err = json.Unmarshal(res, &argoDetailsResponse) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + + return nil +} + +// The early implementation of Argo Tunnel endpoints didn't conform to the V4 +// API standard response structure. This has been remedied going forward however +// to support older clients this isn't yet the default. An explicit `Accept` +// header is used to get the V4 compatible version. +func argoV1Header() http.Header { + header := make(http.Header) + header.Set("Accept", "application/json;version=1") + + return header +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/auditlogs.go b/vendor/github.com/cloudflare/cloudflare-go/auditlogs.go new file mode 100644 index 0000000000000..114ede5edbf0a --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/auditlogs.go @@ -0,0 +1,153 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "net/http" + "net/url" + "path" + "strconv" + "time" +) + +// AuditLogAction is a member of AuditLog, the action that was taken. +type AuditLogAction struct { + Result bool `json:"result"` + Type string `json:"type"` +} + +// AuditLogActor is a member of AuditLog, who performed the action. +type AuditLogActor struct { + Email string `json:"email"` + ID string `json:"id"` + IP string `json:"ip"` + Type string `json:"type"` +} + +// AuditLogOwner is a member of AuditLog, who owns this audit log. +type AuditLogOwner struct { + ID string `json:"id"` +} + +// AuditLogResource is a member of AuditLog, what was the action performed on. +type AuditLogResource struct { + ID string `json:"id"` + Type string `json:"type"` +} + +// AuditLog is an resource that represents an update in the cloudflare dash +type AuditLog struct { + Action AuditLogAction `json:"action"` + Actor AuditLogActor `json:"actor"` + ID string `json:"id"` + Metadata map[string]interface{} `json:"metadata"` + NewValue string `json:"newValue"` + NewValueJSON map[string]interface{} `json:"newValueJson"` + OldValue string `json:"oldValue"` + OldValueJSON map[string]interface{} `json:"oldValueJson"` + Owner AuditLogOwner `json:"owner"` + Resource AuditLogResource `json:"resource"` + When time.Time `json:"when"` +} + +// AuditLogResponse is the response returned from the cloudflare v4 api +type AuditLogResponse struct { + Response Response + Result []AuditLog `json:"result"` + ResultInfo `json:"result_info"` +} + +// AuditLogFilter is an object for filtering the audit log response from the api. +type AuditLogFilter struct { + ID string + ActorIP string + ActorEmail string + Direction string + ZoneName string + Since string + Before string + PerPage int + Page int +} + +// ToQuery turns an audit log filter in to an HTTP Query Param +// list, suitable for use in a url.URL.RawQuery. It will not include empty +// members of the struct in the query parameters. +func (a AuditLogFilter) ToQuery() url.Values { + v := url.Values{} + + if a.ID != "" { + v.Add("id", a.ID) + } + if a.ActorIP != "" { + v.Add("actor.ip", a.ActorIP) + } + if a.ActorEmail != "" { + v.Add("actor.email", a.ActorEmail) + } + if a.ZoneName != "" { + v.Add("zone.name", a.ZoneName) + } + if a.Direction != "" { + v.Add("direction", a.Direction) + } + if a.Since != "" { + v.Add("since", a.Since) + } + if a.Before != "" { + v.Add("before", a.Before) + } + if a.PerPage > 0 { + v.Add("per_page", strconv.Itoa(a.PerPage)) + } + if a.Page > 0 { + v.Add("page", strconv.Itoa(a.Page)) + } + + return v +} + +// GetOrganizationAuditLogs will return the audit logs of a specific +// organization, based on the ID passed in. The audit logs can be +// filtered based on any argument in the AuditLogFilter +// +// API Reference: https://api.cloudflare.com/#audit-logs-list-organization-audit-logs +func (api *API) GetOrganizationAuditLogs(ctx context.Context, organizationID string, a AuditLogFilter) (AuditLogResponse, error) { + uri := url.URL{ + Path: path.Join("/accounts", organizationID, "audit_logs"), + ForceQuery: true, + RawQuery: a.ToQuery().Encode(), + } + res, err := api.makeRequestContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return AuditLogResponse{}, err + } + return unmarshalReturn(res) +} + +// unmarshalReturn will unmarshal bytes and return an auditlogresponse +func unmarshalReturn(res []byte) (AuditLogResponse, error) { + var auditResponse AuditLogResponse + err := json.Unmarshal(res, &auditResponse) + if err != nil { + return auditResponse, err + } + return auditResponse, nil +} + +// GetUserAuditLogs will return your user's audit logs. The audit logs can be +// filtered based on any argument in the AuditLogFilter +// +// API Reference: https://api.cloudflare.com/#audit-logs-list-user-audit-logs +func (api *API) GetUserAuditLogs(ctx context.Context, a AuditLogFilter) (AuditLogResponse, error) { + uri := url.URL{ + Path: path.Join("/user", "audit_logs"), + ForceQuery: true, + RawQuery: a.ToQuery().Encode(), + } + res, err := api.makeRequestContext(ctx, http.MethodGet, uri.String(), nil) + if err != nil { + return AuditLogResponse{}, err + } + return unmarshalReturn(res) +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/authenticated_origin_pulls.go b/vendor/github.com/cloudflare/cloudflare-go/authenticated_origin_pulls.go new file mode 100644 index 0000000000000..0efa33c94e8fa --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/authenticated_origin_pulls.go @@ -0,0 +1,68 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// AuthenticatedOriginPulls represents global AuthenticatedOriginPulls (tls_client_auth) metadata. +type AuthenticatedOriginPulls struct { + ID string `json:"id"` + Value string `json:"value"` + Editable bool `json:"editable"` + ModifiedOn time.Time `json:"modified_on"` +} + +// AuthenticatedOriginPullsResponse represents the response from the global AuthenticatedOriginPulls (tls_client_auth) details endpoint. +type AuthenticatedOriginPullsResponse struct { + Response + Result AuthenticatedOriginPulls `json:"result"` +} + +// GetAuthenticatedOriginPullsStatus returns the configuration details for global AuthenticatedOriginPulls (tls_client_auth). +// +// API reference: https://api.cloudflare.com/#zone-settings-get-tls-client-auth-setting +func (api *API) GetAuthenticatedOriginPullsStatus(ctx context.Context, zoneID string) (AuthenticatedOriginPulls, error) { + uri := fmt.Sprintf("/zones/%s/settings/tls_client_auth", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AuthenticatedOriginPulls{}, err + } + var r AuthenticatedOriginPullsResponse + if err := json.Unmarshal(res, &r); err != nil { + return AuthenticatedOriginPulls{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// SetAuthenticatedOriginPullsStatus toggles whether global AuthenticatedOriginPulls is enabled for the zone. +// +// API reference: https://api.cloudflare.com/#zone-settings-change-tls-client-auth-setting +func (api *API) SetAuthenticatedOriginPullsStatus(ctx context.Context, zoneID string, enable bool) (AuthenticatedOriginPulls, error) { + uri := fmt.Sprintf("/zones/%s/settings/tls_client_auth", zoneID) + var val string + if enable { + val = "on" + } else { + val = "off" + } + params := struct { + Value string `json:"value"` + }{ + Value: val, + } + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params) + if err != nil { + return AuthenticatedOriginPulls{}, err + } + var r AuthenticatedOriginPullsResponse + if err := json.Unmarshal(res, &r); err != nil { + return AuthenticatedOriginPulls{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/authenticated_origin_pulls_per_hostname.go b/vendor/github.com/cloudflare/cloudflare-go/authenticated_origin_pulls_per_hostname.go new file mode 100644 index 0000000000000..4242e07547e84 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/authenticated_origin_pulls_per_hostname.go @@ -0,0 +1,176 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// PerHostnameAuthenticatedOriginPullsCertificateDetails represents the metadata for a Per Hostname AuthenticatedOriginPulls certificate. +type PerHostnameAuthenticatedOriginPullsCertificateDetails struct { + ID string `json:"id"` + Certificate string `json:"certificate"` + Issuer string `json:"issuer"` + Signature string `json:"signature"` + SerialNumber string `json:"serial_number"` + ExpiresOn time.Time `json:"expires_on"` + Status string `json:"status"` + UploadedOn time.Time `json:"uploaded_on"` +} + +// PerHostnameAuthenticatedOriginPullsCertificateResponse represents the response from endpoints relating to creating and deleting a Per Hostname AuthenticatedOriginPulls certificate. +type PerHostnameAuthenticatedOriginPullsCertificateResponse struct { + Response + Result PerHostnameAuthenticatedOriginPullsCertificateDetails `json:"result"` +} + +// PerHostnameAuthenticatedOriginPullsDetails contains metadata about the Per Hostname AuthenticatedOriginPulls configuration on a hostname. +type PerHostnameAuthenticatedOriginPullsDetails struct { + Hostname string `json:"hostname"` + CertID string `json:"cert_id"` + Enabled bool `json:"enabled"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + CertStatus string `json:"cert_status"` + Issuer string `json:"issuer"` + Signature string `json:"signature"` + SerialNumber string `json:"serial_number"` + Certificate string `json:"certificate"` + CertUploadedOn time.Time `json:"cert_uploaded_on"` + CertUpdatedAt time.Time `json:"cert_updated_at"` + ExpiresOn time.Time `json:"expires_on"` +} + +// PerHostnameAuthenticatedOriginPullsDetailsResponse represents Per Hostname AuthenticatedOriginPulls configuration metadata for a single hostname. +type PerHostnameAuthenticatedOriginPullsDetailsResponse struct { + Response + Result PerHostnameAuthenticatedOriginPullsDetails `json:"result"` +} + +// PerHostnamesAuthenticatedOriginPullsDetailsResponse represents Per Hostname AuthenticatedOriginPulls configuration metadata for multiple hostnames. +type PerHostnamesAuthenticatedOriginPullsDetailsResponse struct { + Response + Result []PerHostnameAuthenticatedOriginPullsDetails `json:"result"` +} + +// PerHostnameAuthenticatedOriginPullsCertificateParams represents the required data related to the client certificate being uploaded to be used in Per Hostname AuthenticatedOriginPulls. +type PerHostnameAuthenticatedOriginPullsCertificateParams struct { + Certificate string `json:"certificate"` + PrivateKey string `json:"private_key"` +} + +// PerHostnameAuthenticatedOriginPullsConfig represents the config state for Per Hostname AuthenticatedOriginPulls applied on a hostname. +type PerHostnameAuthenticatedOriginPullsConfig struct { + Hostname string `json:"hostname"` + CertID string `json:"cert_id"` + Enabled bool `json:"enabled"` +} + +// PerHostnameAuthenticatedOriginPullsConfigParams represents the expected config param format for Per Hostname AuthenticatedOriginPulls applied on a hostname. +type PerHostnameAuthenticatedOriginPullsConfigParams struct { + Config []PerHostnameAuthenticatedOriginPullsConfig `json:"config"` +} + +// ListPerHostnameAuthenticatedOriginPullsCertificates will get all certificate under Per Hostname AuthenticatedOriginPulls zone. +// +// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-list-certificates +func (api *API) ListPerHostnameAuthenticatedOriginPullsCertificates(ctx context.Context, zoneID string) ([]PerHostnameAuthenticatedOriginPullsDetails, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth/hostnames/certificates", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []PerHostnameAuthenticatedOriginPullsDetails{}, err + } + var r PerHostnamesAuthenticatedOriginPullsDetailsResponse + if err := json.Unmarshal(res, &r); err != nil { + return []PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UploadPerHostnameAuthenticatedOriginPullsCertificate will upload the provided certificate and private key to the edge under Per Hostname AuthenticatedOriginPulls. +// +// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-upload-a-hostname-client-certificate +func (api *API) UploadPerHostnameAuthenticatedOriginPullsCertificate(ctx context.Context, zoneID string, params PerHostnameAuthenticatedOriginPullsCertificateParams) (PerHostnameAuthenticatedOriginPullsCertificateDetails, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth/hostnames/certificates", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params) + if err != nil { + return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, err + } + var r PerHostnameAuthenticatedOriginPullsCertificateResponse + if err := json.Unmarshal(res, &r); err != nil { + return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// GetPerHostnameAuthenticatedOriginPullsCertificate retrieves certificate metadata about the requested Per Hostname certificate. +// +// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-get-the-hostname-client-certificate +func (api *API) GetPerHostnameAuthenticatedOriginPullsCertificate(ctx context.Context, zoneID, certificateID string) (PerHostnameAuthenticatedOriginPullsCertificateDetails, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth/hostnames/certificates/%s", zoneID, certificateID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, err + } + var r PerHostnameAuthenticatedOriginPullsCertificateResponse + if err := json.Unmarshal(res, &r); err != nil { + return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeletePerHostnameAuthenticatedOriginPullsCertificate will remove the requested Per Hostname certificate from the edge. +// +// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-delete-hostname-client-certificate +func (api *API) DeletePerHostnameAuthenticatedOriginPullsCertificate(ctx context.Context, zoneID, certificateID string) (PerHostnameAuthenticatedOriginPullsCertificateDetails, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth/hostnames/certificates/%s", zoneID, certificateID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, err + } + var r PerHostnameAuthenticatedOriginPullsCertificateResponse + if err := json.Unmarshal(res, &r); err != nil { + return PerHostnameAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// EditPerHostnameAuthenticatedOriginPullsConfig applies the supplied Per Hostname AuthenticatedOriginPulls config onto a hostname(s) in the edge. +// +// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-enable-or-disable-a-hostname-for-client-authentication +func (api *API) EditPerHostnameAuthenticatedOriginPullsConfig(ctx context.Context, zoneID string, config []PerHostnameAuthenticatedOriginPullsConfig) ([]PerHostnameAuthenticatedOriginPullsDetails, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth/hostnames", zoneID) + conf := PerHostnameAuthenticatedOriginPullsConfigParams{ + Config: config, + } + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, conf) + if err != nil { + return []PerHostnameAuthenticatedOriginPullsDetails{}, err + } + var r PerHostnamesAuthenticatedOriginPullsDetailsResponse + if err := json.Unmarshal(res, &r); err != nil { + return []PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// GetPerHostnameAuthenticatedOriginPullsConfig returns the config state of Per Hostname AuthenticatedOriginPulls of the provided hostname within a zone. +// +// API reference: https://api.cloudflare.com/#per-hostname-authenticated-origin-pull-get-the-hostname-status-for-client-authentication +func (api *API) GetPerHostnameAuthenticatedOriginPullsConfig(ctx context.Context, zoneID, hostname string) (PerHostnameAuthenticatedOriginPullsDetails, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth/hostnames/%s", zoneID, hostname) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return PerHostnameAuthenticatedOriginPullsDetails{}, err + } + var r PerHostnameAuthenticatedOriginPullsDetailsResponse + if err := json.Unmarshal(res, &r); err != nil { + return PerHostnameAuthenticatedOriginPullsDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/authenticated_origin_pulls_per_zone.go b/vendor/github.com/cloudflare/cloudflare-go/authenticated_origin_pulls_per_zone.go new file mode 100644 index 0000000000000..7c32be9f7b52a --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/authenticated_origin_pulls_per_zone.go @@ -0,0 +1,152 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// PerZoneAuthenticatedOriginPullsSettings represents the settings for Per Zone AuthenticatedOriginPulls. +type PerZoneAuthenticatedOriginPullsSettings struct { + Enabled bool `json:"enabled"` +} + +// PerZoneAuthenticatedOriginPullsSettingsResponse represents the response from the Per Zone AuthenticatedOriginPulls settings endpoint. +type PerZoneAuthenticatedOriginPullsSettingsResponse struct { + Response + Result PerZoneAuthenticatedOriginPullsSettings `json:"result"` +} + +// PerZoneAuthenticatedOriginPullsCertificateDetails represents the metadata for a Per Zone AuthenticatedOriginPulls client certificate. +type PerZoneAuthenticatedOriginPullsCertificateDetails struct { + ID string `json:"id"` + Certificate string `json:"certificate"` + Issuer string `json:"issuer"` + Signature string `json:"signature"` + ExpiresOn time.Time `json:"expires_on"` + Status string `json:"status"` + UploadedOn time.Time `json:"uploaded_on"` +} + +// PerZoneAuthenticatedOriginPullsCertificateResponse represents the response from endpoints relating to creating and deleting a per zone AuthenticatedOriginPulls certificate. +type PerZoneAuthenticatedOriginPullsCertificateResponse struct { + Response + Result PerZoneAuthenticatedOriginPullsCertificateDetails `json:"result"` +} + +// PerZoneAuthenticatedOriginPullsCertificatesResponse represents the response from the per zone AuthenticatedOriginPulls certificate list endpoint. +type PerZoneAuthenticatedOriginPullsCertificatesResponse struct { + Response + Result []PerZoneAuthenticatedOriginPullsCertificateDetails `json:"result"` +} + +// PerZoneAuthenticatedOriginPullsCertificateParams represents the required data related to the client certificate being uploaded to be used in Per Zone AuthenticatedOriginPulls. +type PerZoneAuthenticatedOriginPullsCertificateParams struct { + Certificate string `json:"certificate"` + PrivateKey string `json:"private_key"` +} + +// GetPerZoneAuthenticatedOriginPullsStatus returns whether per zone AuthenticatedOriginPulls is enabled or not. It is false by default. +// +// API reference: https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-get-enablement-setting-for-zone +func (api *API) GetPerZoneAuthenticatedOriginPullsStatus(ctx context.Context, zoneID string) (PerZoneAuthenticatedOriginPullsSettings, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth/settings", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return PerZoneAuthenticatedOriginPullsSettings{}, err + } + var r PerZoneAuthenticatedOriginPullsSettingsResponse + if err := json.Unmarshal(res, &r); err != nil { + return PerZoneAuthenticatedOriginPullsSettings{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// SetPerZoneAuthenticatedOriginPullsStatus will update whether Per Zone AuthenticatedOriginPulls is enabled for the zone. +// +// API reference: https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-set-enablement-for-zone +func (api *API) SetPerZoneAuthenticatedOriginPullsStatus(ctx context.Context, zoneID string, enable bool) (PerZoneAuthenticatedOriginPullsSettings, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth/settings", zoneID) + params := struct { + Enabled bool `json:"enabled"` + }{ + Enabled: enable, + } + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params) + if err != nil { + return PerZoneAuthenticatedOriginPullsSettings{}, err + } + var r PerZoneAuthenticatedOriginPullsSettingsResponse + if err := json.Unmarshal(res, &r); err != nil { + return PerZoneAuthenticatedOriginPullsSettings{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UploadPerZoneAuthenticatedOriginPullsCertificate will upload a provided client certificate and enable it to be used in all AuthenticatedOriginPulls requests for the zone. +// +// API reference: https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-upload-certificate +func (api *API) UploadPerZoneAuthenticatedOriginPullsCertificate(ctx context.Context, zoneID string, params PerZoneAuthenticatedOriginPullsCertificateParams) (PerZoneAuthenticatedOriginPullsCertificateDetails, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params) + if err != nil { + return PerZoneAuthenticatedOriginPullsCertificateDetails{}, err + } + var r PerZoneAuthenticatedOriginPullsCertificateResponse + if err := json.Unmarshal(res, &r); err != nil { + return PerZoneAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListPerZoneAuthenticatedOriginPullsCertificates returns a list of all user uploaded client certificates to Per Zone AuthenticatedOriginPulls. +// +// API reference: https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-list-certificates +func (api *API) ListPerZoneAuthenticatedOriginPullsCertificates(ctx context.Context, zoneID string) ([]PerZoneAuthenticatedOriginPullsCertificateDetails, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []PerZoneAuthenticatedOriginPullsCertificateDetails{}, err + } + var r PerZoneAuthenticatedOriginPullsCertificatesResponse + if err := json.Unmarshal(res, &r); err != nil { + return []PerZoneAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// GetPerZoneAuthenticatedOriginPullsCertificateDetails returns the metadata associated with a user uploaded client certificate to Per Zone AuthenticatedOriginPulls. +// +// API reference: https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-get-certificate-details +func (api *API) GetPerZoneAuthenticatedOriginPullsCertificateDetails(ctx context.Context, zoneID, certificateID string) (PerZoneAuthenticatedOriginPullsCertificateDetails, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth/%s", zoneID, certificateID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return PerZoneAuthenticatedOriginPullsCertificateDetails{}, err + } + var r PerZoneAuthenticatedOriginPullsCertificateResponse + if err := json.Unmarshal(res, &r); err != nil { + return PerZoneAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeletePerZoneAuthenticatedOriginPullsCertificate removes the specified client certificate from the edge. +// +// API reference: https://api.cloudflare.com/#zone-level-authenticated-origin-pulls-delete-certificate +func (api *API) DeletePerZoneAuthenticatedOriginPullsCertificate(ctx context.Context, zoneID, certificateID string) (PerZoneAuthenticatedOriginPullsCertificateDetails, error) { + uri := fmt.Sprintf("/zones/%s/origin_tls_client_auth/%s", zoneID, certificateID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return PerZoneAuthenticatedOriginPullsCertificateDetails{}, err + } + var r PerZoneAuthenticatedOriginPullsCertificateResponse + if err := json.Unmarshal(res, &r); err != nil { + return PerZoneAuthenticatedOriginPullsCertificateDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/certificate_packs.go b/vendor/github.com/cloudflare/cloudflare-go/certificate_packs.go new file mode 100644 index 0000000000000..89e430605e1fa --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/certificate_packs.go @@ -0,0 +1,191 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// CertificatePackGeoRestrictions is for the structure of the geographic +// restrictions for a TLS certificate. +type CertificatePackGeoRestrictions struct { + Label string `json:"label"` +} + +// CertificatePackCertificate is the base structure of a TLS certificate that is +// contained within a certificate pack. +type CertificatePackCertificate struct { + ID string `json:"id"` + Hosts []string `json:"hosts"` + Issuer string `json:"issuer"` + Signature string `json:"signature"` + Status string `json:"status"` + BundleMethod string `json:"bundle_method"` + GeoRestrictions CertificatePackGeoRestrictions `json:"geo_restrictions"` + ZoneID string `json:"zone_id"` + UploadedOn time.Time `json:"uploaded_on"` + ModifiedOn time.Time `json:"modified_on"` + ExpiresOn time.Time `json:"expires_on"` + Priority int `json:"priority"` +} + +// CertificatePack is the overarching structure of a certificate pack response. +type CertificatePack struct { + ID string `json:"id"` + Type string `json:"type"` + Hosts []string `json:"hosts"` + Certificates []CertificatePackCertificate `json:"certificates"` + PrimaryCertificate string `json:"primary_certificate"` +} + +// CertificatePackRequest is used for requesting a new certificate. +type CertificatePackRequest struct { + Type string `json:"type"` + Hosts []string `json:"hosts"` +} + +// CertificatePackAdvancedCertificate is the structure of the advanced +// certificate pack certificate. +type CertificatePackAdvancedCertificate struct { + ID string `json:"id"` + Type string `json:"type"` + Hosts []string `json:"hosts"` + ValidationMethod string `json:"validation_method"` + ValidityDays int `json:"validity_days"` + CertificateAuthority string `json:"certificate_authority"` + CloudflareBranding bool `json:"cloudflare_branding"` +} + +// CertificatePacksResponse is for responses where multiple certificates are +// expected. +type CertificatePacksResponse struct { + Response + Result []CertificatePack `json:"result"` +} + +// CertificatePacksDetailResponse contains a single certificate pack in the +// response. +type CertificatePacksDetailResponse struct { + Response + Result CertificatePack `json:"result"` +} + +// CertificatePacksAdvancedDetailResponse contains a single advanced certificate +// pack in the response. +type CertificatePacksAdvancedDetailResponse struct { + Response + Result CertificatePackAdvancedCertificate `json:"result"` +} + +// ListCertificatePacks returns all available TLS certificate packs for a zone. +// +// API Reference: https://api.cloudflare.com/#certificate-packs-list-certificate-packs +func (api *API) ListCertificatePacks(ctx context.Context, zoneID string) ([]CertificatePack, error) { + uri := fmt.Sprintf("/zones/%s/ssl/certificate_packs?status=all", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []CertificatePack{}, err + } + + var certificatePacksResponse CertificatePacksResponse + err = json.Unmarshal(res, &certificatePacksResponse) + if err != nil { + return []CertificatePack{}, errors.Wrap(err, errUnmarshalError) + } + + return certificatePacksResponse.Result, nil +} + +// CertificatePack returns a single TLS certificate pack on a zone. +// +// API Reference: https://api.cloudflare.com/#certificate-packs-get-certificate-pack +func (api *API) CertificatePack(ctx context.Context, zoneID, certificatePackID string) (CertificatePack, error) { + uri := fmt.Sprintf("/zones/%s/ssl/certificate_packs/%s", zoneID, certificatePackID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return CertificatePack{}, err + } + + var certificatePacksDetailResponse CertificatePacksDetailResponse + err = json.Unmarshal(res, &certificatePacksDetailResponse) + if err != nil { + return CertificatePack{}, errors.Wrap(err, errUnmarshalError) + } + + return certificatePacksDetailResponse.Result, nil +} + +// CreateCertificatePack creates a new certificate pack associated with a zone. +// +// API Reference: https://api.cloudflare.com/#certificate-packs-order-certificate-pack +func (api *API) CreateCertificatePack(ctx context.Context, zoneID string, cert CertificatePackRequest) (CertificatePack, error) { + uri := fmt.Sprintf("/zones/%s/ssl/certificate_packs", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, cert) + if err != nil { + return CertificatePack{}, err + } + + var certificatePacksDetailResponse CertificatePacksDetailResponse + err = json.Unmarshal(res, &certificatePacksDetailResponse) + if err != nil { + return CertificatePack{}, errors.Wrap(err, errUnmarshalError) + } + + return certificatePacksDetailResponse.Result, nil +} + +// DeleteCertificatePack removes a certificate pack associated with a zone. +// +// API Reference: https://api.cloudflare.com/#certificate-packs-delete-advanced-certificate-manager-certificate-pack +func (api *API) DeleteCertificatePack(ctx context.Context, zoneID, certificateID string) error { + uri := fmt.Sprintf("/zones/%s/ssl/certificate_packs/%s", zoneID, certificateID) + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} + +// CreateAdvancedCertificatePack creates a new certificate pack associated with a zone. +// +// API Reference: https://api.cloudflare.com/#certificate-packs-order-certificate-pack +func (api *API) CreateAdvancedCertificatePack(ctx context.Context, zoneID string, cert CertificatePackAdvancedCertificate) (CertificatePackAdvancedCertificate, error) { + uri := fmt.Sprintf("/zones/%s/ssl/certificate_packs/order", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, cert) + if err != nil { + return CertificatePackAdvancedCertificate{}, err + } + + var advancedCertificatePacksDetailResponse CertificatePacksAdvancedDetailResponse + err = json.Unmarshal(res, &advancedCertificatePacksDetailResponse) + if err != nil { + return CertificatePackAdvancedCertificate{}, errors.Wrap(err, errUnmarshalError) + } + + return advancedCertificatePacksDetailResponse.Result, nil +} + +// RestartAdvancedCertificateValidation kicks off the validation process for a +// pending certificate pack. +// +// API Reference: https://api.cloudflare.com/#certificate-packs-restart-validation-for-advanced-certificate-manager-certificate-pack +func (api *API) RestartAdvancedCertificateValidation(ctx context.Context, zoneID, certificateID string) (CertificatePackAdvancedCertificate, error) { + uri := fmt.Sprintf("/zones/%s/ssl/certificate_packs/%s", zoneID, certificateID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, nil) + if err != nil { + return CertificatePackAdvancedCertificate{}, err + } + + var advancedCertificatePacksDetailResponse CertificatePacksAdvancedDetailResponse + err = json.Unmarshal(res, &advancedCertificatePacksDetailResponse) + if err != nil { + return CertificatePackAdvancedCertificate{}, errors.Wrap(err, errUnmarshalError) + } + + return advancedCertificatePacksDetailResponse.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/cloudflare.go b/vendor/github.com/cloudflare/cloudflare-go/cloudflare.go new file mode 100644 index 0000000000000..1a7ab31cb3f0c --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/cloudflare.go @@ -0,0 +1,599 @@ +// Package cloudflare implements the Cloudflare v4 API. +package cloudflare + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/json" + "io" + "io/ioutil" + "log" + "math" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "github.com/pkg/errors" + "golang.org/x/time/rate" +) + +const apiURL = "https://api.cloudflare.com/client/v4" + +const ( + originCARootCertEccURL = "https://developers.cloudflare.com/ssl/0d2cd0f374da0fb6dbf53128b60bbbf7/origin_ca_ecc_root.pem" + originCARootCertRsaURL = "https://developers.cloudflare.com/ssl/e2b9968022bf23b071d95229b5678452/origin_ca_rsa_root.pem" +) + +const ( + // AuthKeyEmail specifies that we should authenticate with API key and email address + AuthKeyEmail = 1 << iota + // AuthUserService specifies that we should authenticate with a User-Service key + AuthUserService + // AuthToken specifies that we should authenticate with an API Token + AuthToken +) + +// API holds the configuration for the current API client. A client should not +// be modified concurrently. +type API struct { + APIKey string + APIEmail string + APIUserServiceKey string + APIToken string + BaseURL string + AccountID string + UserAgent string + headers http.Header + httpClient *http.Client + authType int + rateLimiter *rate.Limiter + retryPolicy RetryPolicy + logger Logger +} + +// newClient provides shared logic for New and NewWithUserServiceKey +func newClient(opts ...Option) (*API, error) { + silentLogger := log.New(ioutil.Discard, "", log.LstdFlags) + + api := &API{ + BaseURL: apiURL, + headers: make(http.Header), + rateLimiter: rate.NewLimiter(rate.Limit(4), 1), // 4rps equates to default api limit (1200 req/5 min) + retryPolicy: RetryPolicy{ + MaxRetries: 3, + MinRetryDelay: time.Duration(1) * time.Second, + MaxRetryDelay: time.Duration(30) * time.Second, + }, + logger: silentLogger, + } + + err := api.parseOptions(opts...) + if err != nil { + return nil, errors.Wrap(err, "options parsing failed") + } + + // Fall back to http.DefaultClient if the package user does not provide + // their own. + if api.httpClient == nil { + api.httpClient = http.DefaultClient + } + + return api, nil +} + +// New creates a new Cloudflare v4 API client. +func New(key, email string, opts ...Option) (*API, error) { + if key == "" || email == "" { + return nil, errors.New(errEmptyCredentials) + } + + api, err := newClient(opts...) + if err != nil { + return nil, err + } + + api.APIKey = key + api.APIEmail = email + api.authType = AuthKeyEmail + + return api, nil +} + +// NewWithAPIToken creates a new Cloudflare v4 API client using API Tokens +func NewWithAPIToken(token string, opts ...Option) (*API, error) { + if token == "" { + return nil, errors.New(errEmptyAPIToken) + } + + api, err := newClient(opts...) + if err != nil { + return nil, err + } + + api.APIToken = token + api.authType = AuthToken + + return api, nil +} + +// NewWithUserServiceKey creates a new Cloudflare v4 API client using service key authentication. +func NewWithUserServiceKey(key string, opts ...Option) (*API, error) { + if key == "" { + return nil, errors.New(errEmptyCredentials) + } + + api, err := newClient(opts...) + if err != nil { + return nil, err + } + + api.APIUserServiceKey = key + api.authType = AuthUserService + + return api, nil +} + +// SetAuthType sets the authentication method (AuthKeyEmail, AuthToken, or AuthUserService). +func (api *API) SetAuthType(authType int) { + api.authType = authType +} + +// ZoneIDByName retrieves a zone's ID from the name. +func (api *API) ZoneIDByName(zoneName string) (string, error) { + zoneName = normalizeZoneName(zoneName) + res, err := api.ListZonesContext(context.Background(), WithZoneFilters(zoneName, api.AccountID, "")) + if err != nil { + return "", errors.Wrap(err, "ListZonesContext command failed") + } + + switch len(res.Result) { + case 0: + return "", errors.New("zone could not be found") + case 1: + return res.Result[0].ID, nil + default: + return "", errors.New("ambiguous zone name; an account ID might help") + } +} + +// makeRequest makes a HTTP request and returns the body as a byte slice, +// closing it before returning. params will be serialized to JSON. +func (api *API) makeRequest(method, uri string, params interface{}) ([]byte, error) { + return api.makeRequestWithAuthType(context.Background(), method, uri, params, api.authType) +} + +func (api *API) makeRequestContext(ctx context.Context, method, uri string, params interface{}) ([]byte, error) { + return api.makeRequestWithAuthType(ctx, method, uri, params, api.authType) +} + +func (api *API) makeRequestContextWithHeaders(ctx context.Context, method, uri string, params interface{}, headers http.Header) ([]byte, error) { + return readAllClose(api.makeRequestWithAuthTypeAndHeaders(ctx, method, uri, params, api.authType, headers)) +} + +func (api *API) makeRequestWithHeaders(method, uri string, params interface{}, headers http.Header) ([]byte, error) { + return readAllClose(api.makeRequestWithAuthTypeAndHeaders(context.Background(), method, uri, params, api.authType, headers)) +} + +func (api *API) makeRequestWithAuthType(ctx context.Context, method, uri string, params interface{}, authType int) ([]byte, error) { + return readAllClose(api.makeRequestWithAuthTypeAndHeaders(ctx, method, uri, params, authType, nil)) +} + +func readAllClose(r io.ReadCloser, err error) ([]byte, error) { + if err != nil { + return nil, err + } + defer r.Close() + return io.ReadAll(r) +} + +func (api *API) makeRequestWithAuthTypeAndHeaders(ctx context.Context, method, uri string, params interface{}, authType int, headers http.Header) (io.ReadCloser, error) { + // Replace nil with a JSON object if needed + var jsonBody []byte + var err error + + if params != nil { + if paramBytes, ok := params.([]byte); ok { + jsonBody = paramBytes + } else { + jsonBody, err = json.Marshal(params) + if err != nil { + return nil, errors.Wrap(err, "error marshalling params to JSON") + } + } + } else { + jsonBody = nil + } + + var resp *http.Response + var respErr error + var reqBody io.Reader + var respBody []byte + for i := 0; i <= api.retryPolicy.MaxRetries; i++ { + if jsonBody != nil { + reqBody = bytes.NewReader(jsonBody) + } + if i > 0 { + // expect the backoff introduced here on errored requests to dominate the effect of rate limiting + // don't need a random component here as the rate limiter should do something similar + // nb time duration could truncate an arbitrary float. Since our inputs are all ints, we should be ok + sleepDuration := time.Duration(math.Pow(2, float64(i-1)) * float64(api.retryPolicy.MinRetryDelay)) + + if sleepDuration > api.retryPolicy.MaxRetryDelay { + sleepDuration = api.retryPolicy.MaxRetryDelay + } + // useful to do some simple logging here, maybe introduce levels later + api.logger.Printf("Sleeping %s before retry attempt number %d for request %s %s", sleepDuration.String(), i, method, uri) + + select { + case <-time.After(sleepDuration): + case <-ctx.Done(): + return nil, errors.Wrap(ctx.Err(), "operation aborted during backoff") + } + + } + err = api.rateLimiter.Wait(context.Background()) + if err != nil { + return nil, errors.Wrap(err, "Error caused by request rate limiting") + } + resp, respErr = api.request(ctx, method, uri, reqBody, authType, headers) + + // retry if the server is rate limiting us or if it failed + // assumes server operations are rolled back on failure + if respErr != nil || resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode >= 500 { + // if we got a valid http response, try to read body so we can reuse the connection + // see https://golang.org/pkg/net/http/#Client.Do + if respErr == nil { + respBody, err = readBody(resp) + + respErr = errors.Wrap(err, "could not read response body") + + api.logger.Printf("Request: %s %s got an error response %d: %s\n", method, uri, resp.StatusCode, + strings.Replace(strings.Replace(string(respBody), "\n", "", -1), "\t", "", -1)) + } else { + api.logger.Printf("Error performing request: %s %s : %s \n", method, uri, respErr.Error()) + } + continue + } else { + break + } + } + if respErr != nil { + return nil, respErr + } + + if resp.StatusCode >= http.StatusBadRequest { + respBody, err = readBody(resp) + if err != nil { + return nil, errors.Wrap(err, "could not read response body") + } + if strings.HasSuffix(resp.Request.URL.Path, "/filters/validate-expr") { + return nil, errors.Errorf("%s", respBody) + } + + if resp.StatusCode > http.StatusInternalServerError { + return nil, errors.Errorf("HTTP status %d: service failure", resp.StatusCode) + } + + // Logpull/received error response are not in json. + if len(respBody) > 0 && respBody[0] == '{' { + errBody := &Response{} + err = json.Unmarshal(respBody, &errBody) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalErrorBody) + } + + return nil, &APIRequestError{ + StatusCode: resp.StatusCode, + Errors: errBody.Errors, + } + } + return nil, &APIRequestError{ + StatusCode: resp.StatusCode, + Errors: []ResponseInfo{{Message: string(respBody)}}, + } + + } + return getBodyReader(resp) +} + +var gzipPool sync.Pool + +type gzipResponseBody struct { + body io.ReadCloser + gzip *gzip.Reader +} + +type closeDiscardBody struct { + body io.ReadCloser +} + +func (b *closeDiscardBody) Read(p []byte) (n int, err error) { + return b.body.Read(p) +} + +func (b *closeDiscardBody) Close() error { + _, _ = io.Copy(ioutil.Discard, b.body) + return b.body.Close() +} + +func newGzipResponseBody(body io.ReadCloser) (io.ReadCloser, error) { + gz := gzipPool.Get() + if gz == nil { + gzipReader, err := gzip.NewReader(body) + if err != nil { + return nil, err + } + return &gzipResponseBody{body: body, gzip: gzipReader}, nil + } + res := gz.(*gzipResponseBody) + err := res.Reset(body) + return res, err +} + +func (b *gzipResponseBody) Read(p []byte) (int, error) { + return b.gzip.Read(p) +} + +func (b *gzipResponseBody) Reset(r io.ReadCloser) error { + b.body = r + return b.gzip.Reset(r) +} + +func (b *gzipResponseBody) Close() error { + err := b.gzip.Close() + if errBody := b.body.Close(); errBody != nil { + if err == nil { + err = errBody + } else { + err = errors.Wrap(err, errBody.Error()) + } + } + gzipPool.Put(b) + return err +} + +func getBodyReader(resp *http.Response) (io.ReadCloser, error) { + body := &closeDiscardBody{body: resp.Body} + if resp.Header.Get("Content-Encoding") == "gzip" { + return newGzipResponseBody(body) + } + return body, nil +} + +func readBody(resp *http.Response) ([]byte, error) { + var ( + body io.ReadCloser + err error + ) + if body, err = getBodyReader(resp); err != nil { + return nil, err + } + defer body.Close() + return io.ReadAll(body) +} + +// request makes a HTTP request to the given API endpoint, returning the raw +// *http.Response, or an error if one occurred. The caller is responsible for +// closing the response body. +func (api *API) request(ctx context.Context, method, uri string, reqBody io.Reader, authType int, headers http.Header) (*http.Response, error) { + req, err := http.NewRequestWithContext(ctx, method, api.BaseURL+uri, reqBody) + if err != nil { + return nil, errors.Wrap(err, "HTTP request creation failed") + } + + combinedHeaders := make(http.Header) + copyHeader(combinedHeaders, api.headers) + copyHeader(combinedHeaders, headers) + req.Header = combinedHeaders + + if authType&AuthKeyEmail != 0 { + req.Header.Set("X-Auth-Key", api.APIKey) + req.Header.Set("X-Auth-Email", api.APIEmail) + } + if authType&AuthUserService != 0 { + req.Header.Set("X-Auth-User-Service-Key", api.APIUserServiceKey) + } + if authType&AuthToken != 0 { + req.Header.Set("Authorization", "Bearer "+api.APIToken) + } + + if api.UserAgent != "" { + req.Header.Set("User-Agent", api.UserAgent) + } + + if req.Header.Get("Content-Type") == "" { + req.Header.Set("Content-Type", "application/json") + } + + resp, err := api.httpClient.Do(req) + if err != nil { + return nil, errors.Wrap(err, "HTTP request failed") + } + + return resp, nil +} + +// Returns the base URL to use for API endpoints that exist for accounts. +// If an account option was used when creating the API instance, returns +// the account URL. +// +// accountBase is the base URL for endpoints referring to the current user. +// It exists as a parameter because it is not consistent across APIs. +func (api *API) userBaseURL(accountBase string) string { + if api.AccountID != "" { + return "/accounts/" + api.AccountID + } + return accountBase +} + +// copyHeader copies all headers for `source` and sets them on `target`. +// based on https://godoc.org/github.com/golang/gddo/httputil/header#Copy +func copyHeader(target, source http.Header) { + for k, vs := range source { + target[k] = vs + } +} + +// ResponseInfo contains a code and message returned by the API as errors or +// informational messages inside the response. +type ResponseInfo struct { + Code int `json:"code"` + Message string `json:"message"` +} + +// Response is a template. There will also be a result struct. There will be a +// unique response type for each response, which will include this type. +type Response struct { + Success bool `json:"success"` + Errors []ResponseInfo `json:"errors"` + Messages []ResponseInfo `json:"messages"` +} + +// ResultInfoCursors contains information about cursors. +type ResultInfoCursors struct { + Before string `json:"before"` + After string `json:"after"` +} + +// ResultInfo contains metadata about the Response. +type ResultInfo struct { + Page int `json:"page"` + PerPage int `json:"per_page"` + TotalPages int `json:"total_pages"` + Count int `json:"count"` + Total int `json:"total_count"` + Cursor string `json:"cursor"` + Cursors ResultInfoCursors `json:"cursors"` +} + +// RawResponse keeps the result as JSON form +type RawResponse struct { + Response + Result json.RawMessage `json:"result"` +} + +// Raw makes a HTTP request with user provided params and returns the +// result as untouched JSON. +func (api *API) Raw(method, endpoint string, data interface{}) (json.RawMessage, error) { + res, err := api.makeRequest(method, endpoint, data) + if err != nil { + return nil, err + } + + var r RawResponse + if err := json.Unmarshal(res, &r); err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// PaginationOptions can be passed to a list request to configure paging +// These values will be defaulted if omitted, and PerPage has min/max limits set by resource +type PaginationOptions struct { + Page int `json:"page,omitempty"` + PerPage int `json:"per_page,omitempty"` +} + +// RetryPolicy specifies number of retries and min/max retry delays +// This config is used when the client exponentially backs off after errored requests +type RetryPolicy struct { + MaxRetries int + MinRetryDelay time.Duration + MaxRetryDelay time.Duration +} + +// Logger defines the interface this library needs to use logging +// This is a subset of the methods implemented in the log package +type Logger interface { + Printf(format string, v ...interface{}) +} + +// ReqOption is a functional option for configuring API requests +type ReqOption func(opt *reqOption) + +type reqOption struct { + params url.Values +} + +// WithZoneFilters applies a filter based on zone properties. +func WithZoneFilters(zoneName, accountID, status string) ReqOption { + return func(opt *reqOption) { + if zoneName != "" { + opt.params.Set("name", normalizeZoneName(zoneName)) + } + + if accountID != "" { + opt.params.Set("account.id", accountID) + } + + if status != "" { + opt.params.Set("status", status) + } + } +} + +// WithPagination configures the pagination for a response. +func WithPagination(opts PaginationOptions) ReqOption { + return func(opt *reqOption) { + if opts.Page > 0 { + opt.params.Set("page", strconv.Itoa(opts.Page)) + } + + if opts.PerPage > 0 { + opt.params.Set("per_page", strconv.Itoa(opts.PerPage)) + } + } +} + +// checkResultInfo checks whether ResultInfo is reasonable except that it currently +// ignores the cursor information. perPage, page, and count are the requested #items +// per page, the requested page number, and the actual length of the Result array. +// +// Responses from the actual Cloudflare servers should pass all these checks (or we +// discover a serious bug in the Cloudflare servers). However, the unit tests can +// easily violate these constraints and this utility function can help debugging. +// Correct pagination information is crucial for more advanced List* functions that +// handle pagination automatically and fetch different pages in parallel. +// +// TODO: check cursors as well. +func checkResultInfo(perPage, page, count int, info *ResultInfo) bool { + if info.Cursor != "" || info.Cursors.Before != "" || info.Cursors.After != "" { + panic("checkResultInfo could not handle cursors yet.") + } + + switch { + case info.PerPage != perPage || info.Page != page || info.Count != count: + return false + + case info.PerPage <= 0: + return false + + case info.Total == 0 && info.TotalPages == 0 && info.Page == 1 && info.Count == 0: + return true + + case info.Total <= 0 || info.TotalPages <= 0: + return false + + case info.Total > info.PerPage*info.TotalPages || info.Total <= info.PerPage*(info.TotalPages-1): + return false + } + + switch { + case info.Page > info.TotalPages || info.Page <= 0: + return false + + case info.Page < info.TotalPages: + return info.Count == info.PerPage + + case info.Page == info.TotalPages: + return info.Count == info.Total-info.PerPage*(info.TotalPages-1) + + default: + // This is actually impossible, but Go compiler does not know trichotomy + panic("checkResultInfo: impossible") + } +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/consts.go b/vendor/github.com/cloudflare/cloudflare-go/consts.go new file mode 100644 index 0000000000000..3faffc8702135 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/consts.go @@ -0,0 +1,17 @@ +package cloudflare + +// RouteRoot represents the name of the route namespace +type RouteRoot string + +const ( + // AccountRouteRoot is the accounts route namespace + AccountRouteRoot RouteRoot = "accounts" + + // ZoneRouteRoot is the zones route namespace + ZoneRouteRoot RouteRoot = "zones" + + // Used for testing + testAccountID = "01a7362d577a6c3019a474fd6f485823" + testZoneID = "d56084adb405e0b7e32c52321bf07be6" + testCertPackUUID = "a77f8bd7-3b47-46b4-a6f1-75cf98109948" +) diff --git a/vendor/github.com/cloudflare/cloudflare-go/custom_hostname.go b/vendor/github.com/cloudflare/cloudflare-go/custom_hostname.go new file mode 100644 index 0000000000000..6e2db05372d8a --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/custom_hostname.go @@ -0,0 +1,317 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/pkg/errors" +) + +// CustomHostnameStatus is the enumeration of valid state values in the CustomHostnameSSL +type CustomHostnameStatus string + +const ( + // PENDING status represents state of CustomHostname is pending. + PENDING CustomHostnameStatus = "pending" + // ACTIVE status represents state of CustomHostname is active. + ACTIVE CustomHostnameStatus = "active" + // MOVED status represents state of CustomHostname is moved. + MOVED CustomHostnameStatus = "moved" + // DELETED status represents state of CustomHostname is removed. + DELETED CustomHostnameStatus = "deleted" +) + +// CustomHostnameSSLSettings represents the SSL settings for a custom hostname. +type CustomHostnameSSLSettings struct { + HTTP2 string `json:"http2,omitempty"` + HTTP3 string `json:"http3,omitempty"` + TLS13 string `json:"tls_1_3,omitempty"` + MinTLSVersion string `json:"min_tls_version,omitempty"` + Ciphers []string `json:"ciphers,omitempty"` + EarlyHints string `json:"early_hints,omitempty"` +} + +//CustomHostnameOwnershipVerification represents ownership verification status of a given custom hostname. +type CustomHostnameOwnershipVerification struct { + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Value string `json:"value,omitempty"` +} + +//CustomHostnameSSLValidationErrors represents errors that occurred during SSL validation. +type CustomHostnameSSLValidationErrors struct { + Message string `json:"message,omitempty"` +} + +// CustomHostnameSSL represents the SSL section in a given custom hostname. +type CustomHostnameSSL struct { + ID string `json:"id,omitempty"` + Status string `json:"status,omitempty"` + Method string `json:"method,omitempty"` + Type string `json:"type,omitempty"` + CnameTarget string `json:"cname_target,omitempty"` + CnameName string `json:"cname,omitempty"` + TxtName string `json:"txt_name,omitempty"` + TxtValue string `json:"txt_value,omitempty"` + Wildcard *bool `json:"wildcard,omitempty"` + CustomCertificate string `json:"custom_certificate,omitempty"` + CustomKey string `json:"custom_key,omitempty"` + CertificateAuthority string `json:"certificate_authority,omitempty"` + Issuer string `json:"issuer,omitempty"` + SerialNumber string `json:"serial_number,omitempty"` + Settings CustomHostnameSSLSettings `json:"settings,omitempty"` + ValidationErrors []CustomHostnameSSLValidationErrors `json:"validation_errors,omitempty"` + HTTPUrl string `json:"http_url,omitempty"` + HTTPBody string `json:"http_body,omitempty"` +} + +// CustomMetadata defines custom metadata for the hostname. This requires logic to be implemented by Cloudflare to act on the data provided. +type CustomMetadata map[string]interface{} + +// CustomHostname represents a custom hostname in a zone. +type CustomHostname struct { + ID string `json:"id,omitempty"` + Hostname string `json:"hostname,omitempty"` + CustomOriginServer string `json:"custom_origin_server,omitempty"` + CustomOriginSNI string `json:"custom_origin_sni,omitempty"` + SSL *CustomHostnameSSL `json:"ssl,omitempty"` + CustomMetadata CustomMetadata `json:"custom_metadata,omitempty"` + Status CustomHostnameStatus `json:"status,omitempty"` + VerificationErrors []string `json:"verification_errors,omitempty"` + OwnershipVerification CustomHostnameOwnershipVerification `json:"ownership_verification,omitempty"` + OwnershipVerificationHTTP CustomHostnameOwnershipVerificationHTTP `json:"ownership_verification_http,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` +} + +// CustomHostnameOwnershipVerificationHTTP represents a response from the Custom Hostnames endpoints. +type CustomHostnameOwnershipVerificationHTTP struct { + HTTPUrl string `json:"http_url,omitempty"` + HTTPBody string `json:"http_body,omitempty"` +} + +// CustomHostnameResponse represents a response from the Custom Hostnames endpoints. +type CustomHostnameResponse struct { + Result CustomHostname `json:"result"` + Response +} + +// CustomHostnameListResponse represents a response from the Custom Hostnames endpoints. +type CustomHostnameListResponse struct { + Result []CustomHostname `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// CustomHostnameFallbackOrigin represents a Custom Hostnames Fallback Origin +type CustomHostnameFallbackOrigin struct { + Origin string `json:"origin,omitempty"` + Status string `json:"status,omitempty"` + Errors []string `json:"errors,omitempty"` +} + +// CustomHostnameFallbackOriginResponse represents a response from the Custom Hostnames Fallback Origin endpoint. +type CustomHostnameFallbackOriginResponse struct { + Result CustomHostnameFallbackOrigin `json:"result"` + Response +} + +// UpdateCustomHostnameSSL modifies SSL configuration for the given custom +// hostname in the given zone. +// +// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-update-custom-hostname-configuration +func (api *API) UpdateCustomHostnameSSL(ctx context.Context, zoneID string, customHostnameID string, ssl *CustomHostnameSSL) (*CustomHostnameResponse, error) { + uri := fmt.Sprintf("/zones/%s/custom_hostnames/%s", zoneID, customHostnameID) + ch := CustomHostname{ + SSL: ssl, + } + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, ch) + if err != nil { + return nil, err + } + + var response *CustomHostnameResponse + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return response, nil +} + +// UpdateCustomHostname modifies configuration for the given custom +// hostname in the given zone. +// +// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-update-custom-hostname-configuration +func (api *API) UpdateCustomHostname(ctx context.Context, zoneID string, customHostnameID string, ch CustomHostname) (*CustomHostnameResponse, error) { + uri := fmt.Sprintf("/zones/%s/custom_hostnames/%s", zoneID, customHostnameID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, ch) + if err != nil { + return nil, err + } + + var response *CustomHostnameResponse + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return response, nil +} + +// DeleteCustomHostname deletes a custom hostname (and any issued SSL +// certificates). +// +// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-delete-a-custom-hostname-and-any-issued-ssl-certificates- +func (api *API) DeleteCustomHostname(ctx context.Context, zoneID string, customHostnameID string) error { + uri := fmt.Sprintf("/zones/%s/custom_hostnames/%s", zoneID, customHostnameID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + var response *CustomHostnameResponse + err = json.Unmarshal(res, &response) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + + return nil +} + +// CreateCustomHostname creates a new custom hostname and requests that an SSL certificate be issued for it. +// +// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-create-custom-hostname +func (api *API) CreateCustomHostname(ctx context.Context, zoneID string, ch CustomHostname) (*CustomHostnameResponse, error) { + uri := fmt.Sprintf("/zones/%s/custom_hostnames", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, ch) + if err != nil { + return nil, err + } + + var response *CustomHostnameResponse + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// CustomHostnames fetches custom hostnames for the given zone, +// by applying filter.Hostname if not empty and scoping the result to page'th 50 items. +// +// The returned ResultInfo can be used to implement pagination. +// +// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-list-custom-hostnames +func (api *API) CustomHostnames(ctx context.Context, zoneID string, page int, filter CustomHostname) ([]CustomHostname, ResultInfo, error) { + v := url.Values{} + v.Set("per_page", "50") + v.Set("page", strconv.Itoa(page)) + if filter.Hostname != "" { + v.Set("hostname", filter.Hostname) + } + + uri := fmt.Sprintf("/zones/%s/custom_hostnames?%s", zoneID, v.Encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []CustomHostname{}, ResultInfo{}, err + } + var customHostnameListResponse CustomHostnameListResponse + err = json.Unmarshal(res, &customHostnameListResponse) + if err != nil { + return []CustomHostname{}, ResultInfo{}, err + } + + return customHostnameListResponse.Result, customHostnameListResponse.ResultInfo, nil +} + +// CustomHostname inspects the given custom hostname in the given zone. +// +// API reference: https://api.cloudflare.com/#custom-hostname-for-a-zone-custom-hostname-configuration-details +func (api *API) CustomHostname(ctx context.Context, zoneID string, customHostnameID string) (CustomHostname, error) { + uri := fmt.Sprintf("/zones/%s/custom_hostnames/%s", zoneID, customHostnameID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return CustomHostname{}, err + } + + var response CustomHostnameResponse + err = json.Unmarshal(res, &response) + if err != nil { + return CustomHostname{}, errors.Wrap(err, errUnmarshalError) + } + + return response.Result, nil +} + +// CustomHostnameIDByName retrieves the ID for the given hostname in the given zone. +func (api *API) CustomHostnameIDByName(ctx context.Context, zoneID string, hostname string) (string, error) { + customHostnames, _, err := api.CustomHostnames(ctx, zoneID, 1, CustomHostname{Hostname: hostname}) + if err != nil { + return "", errors.Wrap(err, "CustomHostnames command failed") + } + for _, ch := range customHostnames { + if ch.Hostname == hostname { + return ch.ID, nil + } + } + return "", errors.New("CustomHostname could not be found") +} + +// UpdateCustomHostnameFallbackOrigin modifies the Custom Hostname Fallback origin in the given zone. +// +// API reference: https://api.cloudflare.com/#custom-hostname-fallback-origin-for-a-zone-update-fallback-origin-for-custom-hostnames +func (api *API) UpdateCustomHostnameFallbackOrigin(ctx context.Context, zoneID string, chfo CustomHostnameFallbackOrigin) (*CustomHostnameFallbackOriginResponse, error) { + uri := fmt.Sprintf("/zones/%s/custom_hostnames/fallback_origin", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, chfo) + if err != nil { + return nil, err + } + + var response *CustomHostnameFallbackOriginResponse + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return response, nil +} + +// DeleteCustomHostnameFallbackOrigin deletes the Custom Hostname Fallback origin in the given zone. +// +// API reference: https://api.cloudflare.com/#custom-hostname-fallback-origin-for-a-zone-delete-fallback-origin-for-custom-hostnames +func (api *API) DeleteCustomHostnameFallbackOrigin(ctx context.Context, zoneID string) error { + uri := fmt.Sprintf("/zones/%s/custom_hostnames/fallback_origin", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + var response *CustomHostnameFallbackOriginResponse + err = json.Unmarshal(res, &response) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +// CustomHostnameFallbackOrigin inspects the Custom Hostname Fallback origin in the given zone. +// +// API reference: https://api.cloudflare.com/#custom-hostname-fallback-origin-for-a-zone-properties +func (api *API) CustomHostnameFallbackOrigin(ctx context.Context, zoneID string) (CustomHostnameFallbackOrigin, error) { + uri := fmt.Sprintf("/zones/%s/custom_hostnames/fallback_origin", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return CustomHostnameFallbackOrigin{}, err + } + + var response CustomHostnameFallbackOriginResponse + err = json.Unmarshal(res, &response) + if err != nil { + return CustomHostnameFallbackOrigin{}, errors.Wrap(err, errUnmarshalError) + } + + return response.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/custom_pages.go b/vendor/github.com/cloudflare/cloudflare-go/custom_pages.go new file mode 100644 index 0000000000000..45fd504af17af --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/custom_pages.go @@ -0,0 +1,178 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// CustomPage represents a custom page configuration. +type CustomPage struct { + CreatedOn time.Time `json:"created_on"` + ModifiedOn time.Time `json:"modified_on"` + URL interface{} `json:"url"` + State string `json:"state"` + RequiredTokens []string `json:"required_tokens"` + PreviewTarget string `json:"preview_target"` + Description string `json:"description"` + ID string `json:"id"` +} + +// CustomPageResponse represents the response from the custom pages endpoint. +type CustomPageResponse struct { + Response + Result []CustomPage `json:"result"` +} + +// CustomPageDetailResponse represents the response from the custom page endpoint. +type CustomPageDetailResponse struct { + Response + Result CustomPage `json:"result"` +} + +// CustomPageOptions is used to determine whether or not the operation +// should take place on an account or zone level based on which is +// provided to the function. +// +// A non-empty value denotes desired use. +type CustomPageOptions struct { + AccountID string + ZoneID string +} + +// CustomPageParameters is used to update a particular custom page with +// the values provided. +type CustomPageParameters struct { + URL interface{} `json:"url"` + State string `json:"state"` +} + +// CustomPages lists custom pages for a zone or account. +// +// Zone API reference: https://api.cloudflare.com/#custom-pages-for-a-zone-list-available-custom-pages +// Account API reference: https://api.cloudflare.com/#custom-pages-account--list-custom-pages +func (api *API) CustomPages(ctx context.Context, options *CustomPageOptions) ([]CustomPage, error) { + var ( + pageType, identifier string + ) + + if options.AccountID == "" && options.ZoneID == "" { + return nil, errors.New("either account ID or zone ID must be provided") + } + + if options.AccountID != "" && options.ZoneID != "" { + return nil, errors.New("account ID and zone ID are mutually exclusive") + } + + // Should the account ID be defined, treat this as an account level operation. + if options.AccountID != "" { + pageType = "accounts" + identifier = options.AccountID + } else { + pageType = "zones" + identifier = options.ZoneID + } + + uri := fmt.Sprintf("/%s/%s/custom_pages", pageType, identifier) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + var customPageResponse CustomPageResponse + err = json.Unmarshal(res, &customPageResponse) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return customPageResponse.Result, nil +} + +// CustomPage lists a single custom page based on the ID. +// +// Zone API reference: https://api.cloudflare.com/#custom-pages-for-a-zone-custom-page-details +// Account API reference: https://api.cloudflare.com/#custom-pages-account--custom-page-details +func (api *API) CustomPage(ctx context.Context, options *CustomPageOptions, customPageID string) (CustomPage, error) { + var ( + pageType, identifier string + ) + + if options.AccountID == "" && options.ZoneID == "" { + return CustomPage{}, errors.New("either account ID or zone ID must be provided") + } + + if options.AccountID != "" && options.ZoneID != "" { + return CustomPage{}, errors.New("account ID and zone ID are mutually exclusive") + } + + // Should the account ID be defined, treat this as an account level operation. + if options.AccountID != "" { + pageType = "accounts" + identifier = options.AccountID + } else { + pageType = "zones" + identifier = options.ZoneID + } + + uri := fmt.Sprintf("/%s/%s/custom_pages/%s", pageType, identifier, customPageID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return CustomPage{}, err + } + + var customPageResponse CustomPageDetailResponse + err = json.Unmarshal(res, &customPageResponse) + if err != nil { + return CustomPage{}, errors.Wrap(err, errUnmarshalError) + } + + return customPageResponse.Result, nil +} + +// UpdateCustomPage updates a single custom page setting. +// +// Zone API reference: https://api.cloudflare.com/#custom-pages-for-a-zone-update-custom-page-url +// Account API reference: https://api.cloudflare.com/#custom-pages-account--update-custom-page +func (api *API) UpdateCustomPage(ctx context.Context, options *CustomPageOptions, customPageID string, pageParameters CustomPageParameters) (CustomPage, error) { + var ( + pageType, identifier string + ) + + if options.AccountID == "" && options.ZoneID == "" { + return CustomPage{}, errors.New("either account ID or zone ID must be provided") + } + + if options.AccountID != "" && options.ZoneID != "" { + return CustomPage{}, errors.New("account ID and zone ID are mutually exclusive") + } + + // Should the account ID be defined, treat this as an account level operation. + if options.AccountID != "" { + pageType = "accounts" + identifier = options.AccountID + } else { + pageType = "zones" + identifier = options.ZoneID + } + + uri := fmt.Sprintf("/%s/%s/custom_pages/%s", pageType, identifier, customPageID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, pageParameters) + if err != nil { + return CustomPage{}, err + } + + var customPageResponse CustomPageDetailResponse + err = json.Unmarshal(res, &customPageResponse) + if err != nil { + return CustomPage{}, errors.Wrap(err, errUnmarshalError) + } + + return customPageResponse.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/device_posture_rule.go b/vendor/github.com/cloudflare/cloudflare-go/device_posture_rule.go new file mode 100644 index 0000000000000..f28af576396f8 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/device_posture_rule.go @@ -0,0 +1,169 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +// DevicePostureRule represents a device posture rule. +type DevicePostureRule struct { + ID string `json:"id,omitempty"` + Type string `json:"type"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Schedule string `json:"schedule,omitempty"` + Match []DevicePostureRuleMatch `json:"match,omitempty"` + Input DevicePostureRuleInput `json:"input,omitempty"` +} + +// DevicePostureRuleMatch represents the conditions that the client must match to run the rule. +type DevicePostureRuleMatch struct { + Platform string `json:"platform,omitempty"` +} + +// DevicePostureRuleInput represents the value to be checked against. +type DevicePostureRuleInput struct { + ID string `json:"id,omitempty"` + Path string `json:"path,omitempty"` + Exists bool `json:"exists,omitempty"` + Thumbprint string `json:"thumbprint,omitempty"` + Sha256 string `json:"sha256,omitempty"` + Running bool `json:"running,omitempty"` + RequireAll bool `json:"requireAll,omitempty"` + Enabled bool `json:"enabled,omitempty"` + Version string `json:"version,omitempty"` + Operator string `json:"operator,omitempty"` + Domain string `json:"domain,omitempty"` +} + +// DevicePostureRuleListResponse represents the response from the list +// device posture rules endpoint. +type DevicePostureRuleListResponse struct { + Result []DevicePostureRule `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// DevicePostureRuleDetailResponse is the API response, containing a single +// device posture rule. +type DevicePostureRuleDetailResponse struct { + Response + Result DevicePostureRule `json:"result"` +} + +// DevicePostureRules returns all device posture rules within an account. +// +// API reference: https://api.cloudflare.com/#device-posture-rules-list-device-posture-rules +func (api *API) DevicePostureRules(ctx context.Context, accountID string) ([]DevicePostureRule, ResultInfo, error) { + uri := fmt.Sprintf("/%s/%s/devices/posture", AccountRouteRoot, accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []DevicePostureRule{}, ResultInfo{}, err + } + + var devicePostureRuleListResponse DevicePostureRuleListResponse + err = json.Unmarshal(res, &devicePostureRuleListResponse) + if err != nil { + return []DevicePostureRule{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return devicePostureRuleListResponse.Result, devicePostureRuleListResponse.ResultInfo, nil +} + +// DevicePostureRule returns a single device posture rule based on the rule ID. +// +// API reference: https://api.cloudflare.com/#device-posture-rules-device-posture-rules-details +func (api *API) DevicePostureRule(ctx context.Context, accountID, ruleID string) (DevicePostureRule, error) { + uri := fmt.Sprintf( + "/%s/%s/devices/posture/%s", + AccountRouteRoot, + accountID, + ruleID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return DevicePostureRule{}, err + } + + var devicePostureRuleDetailResponse DevicePostureRuleDetailResponse + err = json.Unmarshal(res, &devicePostureRuleDetailResponse) + if err != nil { + return DevicePostureRule{}, errors.Wrap(err, errUnmarshalError) + } + + return devicePostureRuleDetailResponse.Result, nil +} + +// CreateDevicePostureRule creates a new device posture rule. +// +// API reference: https://api.cloudflare.com/#device-posture-rules-create-device-posture-rule +func (api *API) CreateDevicePostureRule(ctx context.Context, accountID string, rule DevicePostureRule) (DevicePostureRule, error) { + uri := fmt.Sprintf("/%s/%s/devices/posture", AccountRouteRoot, accountID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, rule) + if err != nil { + return DevicePostureRule{}, err + } + + var devicePostureRuleDetailResponse DevicePostureRuleDetailResponse + err = json.Unmarshal(res, &devicePostureRuleDetailResponse) + if err != nil { + return DevicePostureRule{}, errors.Wrap(err, errUnmarshalError) + } + + return devicePostureRuleDetailResponse.Result, nil +} + +// UpdateDevicePostureRule updates an existing device posture rule. +// +// API reference: https://api.cloudflare.com/#device-posture-rules-update-device-posture-rule +func (api *API) UpdateDevicePostureRule(ctx context.Context, accountID string, rule DevicePostureRule) (DevicePostureRule, error) { + if rule.ID == "" { + return DevicePostureRule{}, errors.Errorf("device posture rule ID cannot be empty") + } + + uri := fmt.Sprintf( + "/%s/%s/devices/posture/%s", + AccountRouteRoot, + accountID, + rule.ID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, rule) + if err != nil { + return DevicePostureRule{}, err + } + + var devicePostureRuleDetailResponse DevicePostureRuleDetailResponse + err = json.Unmarshal(res, &devicePostureRuleDetailResponse) + if err != nil { + return DevicePostureRule{}, errors.Wrap(err, errUnmarshalError) + } + + return devicePostureRuleDetailResponse.Result, nil +} + +// DeleteDevicePostureRule deletes a device posture rule. +// +// API reference: https://api.cloudflare.com/#device-posture-rules-delete-device-posture-rule +func (api *API) DeleteDevicePostureRule(ctx context.Context, accountID, ruleID string) error { + uri := fmt.Sprintf( + "/%s/%s/devices/posture/%s", + AccountRouteRoot, + accountID, + ruleID, + ) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/diagnostics.go b/vendor/github.com/cloudflare/cloudflare-go/diagnostics.go new file mode 100644 index 0000000000000..6fc2daac45937 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/diagnostics.go @@ -0,0 +1,103 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +// DiagnosticsTracerouteConfiguration is the overarching structure of the +// diagnostics traceroute requests. +type DiagnosticsTracerouteConfiguration struct { + Targets []string `json:"targets"` + Colos []string `json:"colos,omitempty"` + Options DiagnosticsTracerouteConfigurationOptions `json:"options,omitempty"` +} + +// DiagnosticsTracerouteConfigurationOptions contains the options for performing +// traceroutes. +type DiagnosticsTracerouteConfigurationOptions struct { + PacketsPerTTL int `json:"packets_per_ttl"` + PacketType string `json:"packet_type"` + MaxTTL int `json:"max_ttl"` + WaitTime int `json:"wait_time"` +} + +// DiagnosticsTracerouteResponse is the outer response of the API response. +type DiagnosticsTracerouteResponse struct { + Response + Result []DiagnosticsTracerouteResponseResult `json:"result"` +} + +// DiagnosticsTracerouteResponseResult is the inner API response for the +// traceroute request. +type DiagnosticsTracerouteResponseResult struct { + Target string `json:"target"` + Colos []DiagnosticsTracerouteResponseColos `json:"colos"` +} + +// DiagnosticsTracerouteResponseColo contains the Name and City of a colocation. +type DiagnosticsTracerouteResponseColo struct { + Name string `json:"name"` + City string `json:"city"` +} + +// DiagnosticsTracerouteResponseNodes holds a summary of nodes contacted in the +// traceroute. +type DiagnosticsTracerouteResponseNodes struct { + Asn string `json:"asn"` + IP string `json:"ip"` + Name string `json:"name"` + PacketCount int `json:"packet_count"` + MeanRttMs float64 `json:"mean_rtt_ms"` + StdDevRttMs float64 `json:"std_dev_rtt_ms"` + MinRttMs float64 `json:"min_rtt_ms"` + MaxRttMs float64 `json:"max_rtt_ms"` +} + +// DiagnosticsTracerouteResponseHops holds packet and node information of the +// hops. +type DiagnosticsTracerouteResponseHops struct { + PacketsTTL int `json:"packets_ttl"` + PacketsSent int `json:"packets_sent"` + PacketsLost int `json:"packets_lost"` + Nodes []DiagnosticsTracerouteResponseNodes `json:"nodes"` +} + +// DiagnosticsTracerouteResponseColos is the summary struct of a colocation test. +type DiagnosticsTracerouteResponseColos struct { + Error string `json:"error"` + Colo DiagnosticsTracerouteResponseColo `json:"colo"` + TracerouteTimeMs int `json:"traceroute_time_ms"` + TargetSummary DiagnosticsTracerouteResponseNodes `json:"target_summary"` + Hops []DiagnosticsTracerouteResponseHops `json:"hops"` +} + +// PerformTraceroute initiates a traceroute from the Cloudflare network to the +// requested targets. +// +// API documentation: https://api.cloudflare.com/#diagnostics-traceroute +func (api *API) PerformTraceroute(ctx context.Context, accountID string, targets, colos []string, tracerouteOptions DiagnosticsTracerouteConfigurationOptions) ([]DiagnosticsTracerouteResponseResult, error) { + uri := fmt.Sprintf("/accounts/%s/diagnostics/traceroute", accountID) + diagnosticsPayload := DiagnosticsTracerouteConfiguration{ + Targets: targets, + Colos: colos, + Options: tracerouteOptions, + } + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, diagnosticsPayload) + if err != nil { + return []DiagnosticsTracerouteResponseResult{}, err + } + + var diagnosticsResponse DiagnosticsTracerouteResponse + err = json.Unmarshal(res, &diagnosticsResponse) + if err != nil { + return []DiagnosticsTracerouteResponseResult{}, errors.Wrap(err, errUnmarshalError) + } + + return diagnosticsResponse.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/dns.go b/vendor/github.com/cloudflare/cloudflare-go/dns.go new file mode 100644 index 0000000000000..fa1a367aa833c --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/dns.go @@ -0,0 +1,204 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/pkg/errors" + "golang.org/x/net/idna" +) + +// DNSRecord represents a DNS record in a zone. +type DNSRecord struct { + ID string `json:"id,omitempty"` + Type string `json:"type,omitempty"` + Name string `json:"name,omitempty"` + Content string `json:"content,omitempty"` + Proxiable bool `json:"proxiable,omitempty"` + Proxied *bool `json:"proxied,omitempty"` + TTL int `json:"ttl,omitempty"` + Locked bool `json:"locked,omitempty"` + ZoneID string `json:"zone_id,omitempty"` + ZoneName string `json:"zone_name,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty"` + Data interface{} `json:"data,omitempty"` // data returned by: SRV, LOC + Meta interface{} `json:"meta,omitempty"` + Priority *uint16 `json:"priority,omitempty"` +} + +// DNSRecordResponse represents the response from the DNS endpoint. +type DNSRecordResponse struct { + Result DNSRecord `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// DNSListResponse represents the response from the list DNS records endpoint. +type DNSListResponse struct { + Result []DNSRecord `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// nontransitionalLookup implements the nontransitional processing as specified in +// Unicode Technical Standard 46 with almost all checkings off to maximize user freedom. +var nontransitionalLookup = idna.New( + idna.MapForLookup(), + idna.StrictDomainName(false), + idna.ValidateLabels(false), +) + +// toUTS46ASCII tries to convert IDNs (international domain names) +// from Unicode form to Punycode, using non-transitional process specified +// in UTS 46. +// +// Note: conversion errors are silently discarded and partial conversion +// results are used. +func toUTS46ASCII(name string) string { + name, _ = nontransitionalLookup.ToASCII(name) + return name +} + +// CreateDNSRecord creates a DNS record for the zone identifier. +// +// API reference: https://api.cloudflare.com/#dns-records-for-a-zone-create-dns-record +func (api *API) CreateDNSRecord(ctx context.Context, zoneID string, rr DNSRecord) (*DNSRecordResponse, error) { + rr.Name = toUTS46ASCII(rr.Name) + + uri := fmt.Sprintf("/zones/%s/dns_records", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, rr) + if err != nil { + return nil, err + } + + var recordResp *DNSRecordResponse + err = json.Unmarshal(res, &recordResp) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return recordResp, nil +} + +// DNSRecords returns a slice of DNS records for the given zone identifier. +// +// This takes a DNSRecord to allow filtering of the results returned. +// +// API reference: https://api.cloudflare.com/#dns-records-for-a-zone-list-dns-records +func (api *API) DNSRecords(ctx context.Context, zoneID string, rr DNSRecord) ([]DNSRecord, error) { + // Construct a query string + v := url.Values{} + // Request as many records as possible per page - API max is 100 + v.Set("per_page", "100") + if rr.Name != "" { + v.Set("name", toUTS46ASCII(rr.Name)) + } + if rr.Type != "" { + v.Set("type", rr.Type) + } + if rr.Content != "" { + v.Set("content", rr.Content) + } + + var records []DNSRecord + page := 1 + + // Loop over makeRequest until what we've fetched all records + for { + v.Set("page", strconv.Itoa(page)) + uri := fmt.Sprintf("/zones/%s/dns_records?%s", zoneID, v.Encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []DNSRecord{}, err + } + var r DNSListResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []DNSRecord{}, errors.Wrap(err, errUnmarshalError) + } + records = append(records, r.Result...) + if r.ResultInfo.Page >= r.ResultInfo.TotalPages { + break + } + // Loop around and fetch the next page + page++ + } + return records, nil +} + +// DNSRecord returns a single DNS record for the given zone & record +// identifiers. +// +// API reference: https://api.cloudflare.com/#dns-records-for-a-zone-dns-record-details +func (api *API) DNSRecord(ctx context.Context, zoneID, recordID string) (DNSRecord, error) { + uri := fmt.Sprintf("/zones/%s/dns_records/%s", zoneID, recordID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return DNSRecord{}, err + } + var r DNSRecordResponse + err = json.Unmarshal(res, &r) + if err != nil { + return DNSRecord{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateDNSRecord updates a single DNS record for the given zone & record +// identifiers. +// +// API reference: https://api.cloudflare.com/#dns-records-for-a-zone-update-dns-record +func (api *API) UpdateDNSRecord(ctx context.Context, zoneID, recordID string, rr DNSRecord) error { + rr.Name = toUTS46ASCII(rr.Name) + + // Populate the record name from the existing one if the update didn't + // specify it. + if rr.Name == "" || rr.Type == "" { + rec, err := api.DNSRecord(ctx, zoneID, recordID) + if err != nil { + return err + } + + if rr.Name == "" { + rr.Name = rec.Name + } + if rr.Type == "" { + rr.Type = rec.Type + } + } + uri := fmt.Sprintf("/zones/%s/dns_records/%s", zoneID, recordID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, rr) + if err != nil { + return err + } + var r DNSRecordResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +// DeleteDNSRecord deletes a single DNS record for the given zone & record +// identifiers. +// +// API reference: https://api.cloudflare.com/#dns-records-for-a-zone-delete-dns-record +func (api *API) DeleteDNSRecord(ctx context.Context, zoneID, recordID string) error { + uri := fmt.Sprintf("/zones/%s/dns_records/%s", zoneID, recordID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + var r DNSRecordResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/duration.go b/vendor/github.com/cloudflare/cloudflare-go/duration.go new file mode 100644 index 0000000000000..ba2418acda61b --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/duration.go @@ -0,0 +1,40 @@ +package cloudflare + +import ( + "encoding/json" + "time" +) + +// Duration implements json.Marshaler and json.Unmarshaler for time.Duration +// using the fmt.Stringer interface of time.Duration and time.ParseDuration. +type Duration struct { + time.Duration +} + +// MarshalJSON encodes a Duration as a JSON string formatted using String. +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(d.Duration.String()) +} + +// UnmarshalJSON decodes a Duration from a JSON string parsed using time.ParseDuration. +func (d *Duration) UnmarshalJSON(buf []byte) error { + var str string + + err := json.Unmarshal(buf, &str) + if err != nil { + return err + } + + dur, err := time.ParseDuration(str) + if err != nil { + return err + } + + d.Duration = dur + return nil +} + +var ( + _ = json.Marshaler((*Duration)(nil)) + _ = json.Unmarshaler((*Duration)(nil)) +) diff --git a/vendor/github.com/cloudflare/cloudflare-go/errors.go b/vendor/github.com/cloudflare/cloudflare-go/errors.go new file mode 100644 index 0000000000000..a08b1f36aa33c --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/errors.go @@ -0,0 +1,125 @@ +package cloudflare + +import ( + "fmt" + "net/http" + "strings" +) + +// Error messages +const ( + errEmptyCredentials = "invalid credentials: key & email must not be empty" + errEmptyAPIToken = "invalid credentials: API Token must not be empty" + errMakeRequestError = "error from makeRequest" + errUnmarshalError = "error unmarshalling the JSON response" + errUnmarshalErrorBody = "error unmarshalling the JSON response error body" + errRequestNotSuccessful = "error reported by API" + errMissingAccountID = "account ID is empty and must be provided" + errOperationStillRunning = "bulk operation did not finish before timeout" + errOperationUnexpectedStatus = "bulk operation returned an unexpected status" + errResultInfo = "incorrect pagination info (result_info) in responses" + errManualPagination = "unexpected pagination options passed to functions that handle pagination automatically" +) + +// APIRequestError is a type of error raised by API calls made by this library. +type APIRequestError struct { + StatusCode int + Errors []ResponseInfo +} + +func (e APIRequestError) Error() string { + errString := "" + errString += fmt.Sprintf("HTTP status %d", e.StatusCode) + + if len(e.Errors) > 0 { + errString += ": " + } + + errMessages := []string{} + for _, err := range e.Errors { + m := "" + if err.Message != "" { + m += err.Message + } + + if err.Code != 0 { + m += fmt.Sprintf(" (%d)", err.Code) + } + + errMessages = append(errMessages, m) + } + + return errString + strings.Join(errMessages, ", ") +} + +// HTTPStatusCode exposes the HTTP status from the error response encountered. +func (e APIRequestError) HTTPStatusCode() int { + return e.StatusCode +} + +// ErrorMessages exposes the error messages as a slice of strings from the error +// response encountered. +func (e *APIRequestError) ErrorMessages() []string { + messages := []string{} + + for _, e := range e.Errors { + messages = append(messages, e.Message) + } + + return messages +} + +// InternalErrorCodes exposes the internal error codes as a slice of int from +// the error response encountered. +func (e *APIRequestError) InternalErrorCodes() []int { + ec := []int{} + + for _, e := range e.Errors { + ec = append(ec, e.Code) + } + + return ec +} + +// ServiceError returns a boolean whether or not the raised error was caused by +// an internal service. +func (e *APIRequestError) ServiceError() bool { + return e.StatusCode >= http.StatusInternalServerError && + e.StatusCode < 600 +} + +// ClientError returns a boolean whether or not the raised error was caused by +// something client side. +func (e *APIRequestError) ClientError() bool { + return e.StatusCode >= http.StatusBadRequest && + e.StatusCode < http.StatusInternalServerError +} + +// ClientRateLimited returns a boolean whether or not the raised error was +// caused by too many requests from the client. +func (e *APIRequestError) ClientRateLimited() bool { + return e.StatusCode == http.StatusTooManyRequests +} + +// InternalErrorCodeIs returns a boolean whether or not the desired internal +// error code is present in `e.InternalErrorCodes`. +func (e *APIRequestError) InternalErrorCodeIs(code int) bool { + for _, errCode := range e.InternalErrorCodes() { + if errCode == code { + return true + } + } + + return false +} + +// ErrorMessageContains returns a boolean whether or not a substring exists in +// any of the `e.ErrorMessages` slice entries. +func (e *APIRequestError) ErrorMessageContains(s string) bool { + for _, errMsg := range e.ErrorMessages() { + if strings.Contains(errMsg, s) { + return true + } + } + return false +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/filter.go b/vendor/github.com/cloudflare/cloudflare-go/filter.go new file mode 100644 index 0000000000000..f657303a9207b --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/filter.go @@ -0,0 +1,242 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +// Filter holds the structure of the filter type. +type Filter struct { + ID string `json:"id,omitempty"` + Expression string `json:"expression"` + Paused bool `json:"paused"` + Description string `json:"description"` + + // Property is mentioned in documentation however isn't populated in + // any of the API requests. For now, let's just omit it unless it's + // provided. + Ref string `json:"ref,omitempty"` +} + +// FiltersDetailResponse is the API response that is returned +// for requesting all filters on a zone. +type FiltersDetailResponse struct { + Result []Filter `json:"result"` + ResultInfo `json:"result_info"` + Response +} + +// FilterDetailResponse is the API response that is returned +// for requesting a single filter on a zone. +type FilterDetailResponse struct { + Result Filter `json:"result"` + ResultInfo `json:"result_info"` + Response +} + +// FilterValidateExpression represents the JSON payload for checking +// an expression. +type FilterValidateExpression struct { + Expression string `json:"expression"` +} + +// FilterValidateExpressionResponse represents the API response for +// checking the expression. It conforms to the JSON API approach however +// we don't need all of the fields exposed. +type FilterValidateExpressionResponse struct { + Success bool `json:"success"` + Errors []FilterValidationExpressionMessage `json:"errors"` +} + +// FilterValidationExpressionMessage represents the API error message. +type FilterValidationExpressionMessage struct { + Message string `json:"message"` +} + +// Filter returns a single filter in a zone based on the filter ID. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/get/#get-by-filter-id +func (api *API) Filter(ctx context.Context, zoneID, filterID string) (Filter, error) { + uri := fmt.Sprintf("/zones/%s/filters/%s", zoneID, filterID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return Filter{}, err + } + + var filterResponse FilterDetailResponse + err = json.Unmarshal(res, &filterResponse) + if err != nil { + return Filter{}, errors.Wrap(err, errUnmarshalError) + } + + return filterResponse.Result, nil +} + +// Filters returns all filters for a zone. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/get/#get-all-filters +func (api *API) Filters(ctx context.Context, zoneID string, pageOpts PaginationOptions) ([]Filter, error) { + uri := fmt.Sprintf("/zones/%s/filters", zoneID) + v := url.Values{} + + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []Filter{}, err + } + + var filtersResponse FiltersDetailResponse + err = json.Unmarshal(res, &filtersResponse) + if err != nil { + return []Filter{}, errors.Wrap(err, errUnmarshalError) + } + + return filtersResponse.Result, nil +} + +// CreateFilters creates new filters. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/post/ +func (api *API) CreateFilters(ctx context.Context, zoneID string, filters []Filter) ([]Filter, error) { + uri := fmt.Sprintf("/zones/%s/filters", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, filters) + if err != nil { + return []Filter{}, err + } + + var filtersResponse FiltersDetailResponse + err = json.Unmarshal(res, &filtersResponse) + if err != nil { + return []Filter{}, errors.Wrap(err, errUnmarshalError) + } + + return filtersResponse.Result, nil +} + +// UpdateFilter updates a single filter. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/put/#update-a-single-filter +func (api *API) UpdateFilter(ctx context.Context, zoneID string, filter Filter) (Filter, error) { + if filter.ID == "" { + return Filter{}, errors.Errorf("filter ID cannot be empty") + } + + uri := fmt.Sprintf("/zones/%s/filters/%s", zoneID, filter.ID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, filter) + if err != nil { + return Filter{}, err + } + + var filterResponse FilterDetailResponse + err = json.Unmarshal(res, &filterResponse) + if err != nil { + return Filter{}, errors.Wrap(err, errUnmarshalError) + } + + return filterResponse.Result, nil +} + +// UpdateFilters updates many filters at once. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/put/#update-multiple-filters +func (api *API) UpdateFilters(ctx context.Context, zoneID string, filters []Filter) ([]Filter, error) { + for _, filter := range filters { + if filter.ID == "" { + return []Filter{}, errors.Errorf("filter ID cannot be empty") + } + } + + uri := fmt.Sprintf("/zones/%s/filters", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, filters) + if err != nil { + return []Filter{}, err + } + + var filtersResponse FiltersDetailResponse + err = json.Unmarshal(res, &filtersResponse) + if err != nil { + return []Filter{}, errors.Wrap(err, errUnmarshalError) + } + + return filtersResponse.Result, nil +} + +// DeleteFilter deletes a single filter. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/delete/#delete-a-single-filter +func (api *API) DeleteFilter(ctx context.Context, zoneID, filterID string) error { + if filterID == "" { + return errors.Errorf("filter ID cannot be empty") + } + + uri := fmt.Sprintf("/zones/%s/filters/%s", zoneID, filterID) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} + +// DeleteFilters deletes multiple filters. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/delete/#delete-multiple-filters +func (api *API) DeleteFilters(ctx context.Context, zoneID string, filterIDs []string) error { + ids := strings.Join(filterIDs, ",") + uri := fmt.Sprintf("/zones/%s/filters?id=%s", zoneID, ids) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} + +// ValidateFilterExpression checks correctness of a filter expression. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/validation/ +func (api *API) ValidateFilterExpression(ctx context.Context, expression string) error { + expressionPayload := FilterValidateExpression{Expression: expression} + + _, err := api.makeRequestContext(ctx, http.MethodPost, "/filters/validate-expr", expressionPayload) + if err != nil { + var filterValidationResponse FilterValidateExpressionResponse + + jsonErr := json.Unmarshal([]byte(err.Error()), &filterValidationResponse) + if jsonErr != nil { + return errors.Wrap(jsonErr, errUnmarshalError) + } + + if !filterValidationResponse.Success { + // Unsure why but the API returns `errors` as an array but it only + // ever shows the issue with one problem at a time ¯\_(ツ)_/¯ + return errors.Errorf(filterValidationResponse.Errors[0].Message) + } + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/firewall.go b/vendor/github.com/cloudflare/cloudflare-go/firewall.go new file mode 100644 index 0000000000000..cbf78da5bbf17 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/firewall.go @@ -0,0 +1,282 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/pkg/errors" +) + +// AccessRule represents a firewall access rule. +type AccessRule struct { + ID string `json:"id,omitempty"` + Notes string `json:"notes,omitempty"` + AllowedModes []string `json:"allowed_modes,omitempty"` + Mode string `json:"mode,omitempty"` + Configuration AccessRuleConfiguration `json:"configuration,omitempty"` + Scope AccessRuleScope `json:"scope,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty"` +} + +// AccessRuleConfiguration represents the configuration of a firewall +// access rule. +type AccessRuleConfiguration struct { + Target string `json:"target,omitempty"` + Value string `json:"value,omitempty"` +} + +// AccessRuleScope represents the scope of a firewall access rule. +type AccessRuleScope struct { + ID string `json:"id,omitempty"` + Email string `json:"email,omitempty"` + Name string `json:"name,omitempty"` + Type string `json:"type,omitempty"` +} + +// AccessRuleResponse represents the response from the firewall access +// rule endpoint. +type AccessRuleResponse struct { + Result AccessRule `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// AccessRuleListResponse represents the response from the list access rules +// endpoint. +type AccessRuleListResponse struct { + Result []AccessRule `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// ListUserAccessRules returns a slice of access rules for the logged-in user. +// +// This takes an AccessRule to allow filtering of the results returned. +// +// API reference: https://api.cloudflare.com/#user-level-firewall-access-rule-list-access-rules +func (api *API) ListUserAccessRules(ctx context.Context, accessRule AccessRule, page int) (*AccessRuleListResponse, error) { + return api.listAccessRules(ctx, "/user", accessRule, page) +} + +// CreateUserAccessRule creates a firewall access rule for the logged-in user. +// +// API reference: https://api.cloudflare.com/#user-level-firewall-access-rule-create-access-rule +func (api *API) CreateUserAccessRule(ctx context.Context, accessRule AccessRule) (*AccessRuleResponse, error) { + return api.createAccessRule(ctx, "/user", accessRule) +} + +// UserAccessRule returns the details of a user's account access rule. +// +// API reference: https://api.cloudflare.com/#user-level-firewall-access-rule-list-access-rules +func (api *API) UserAccessRule(ctx context.Context, accessRuleID string) (*AccessRuleResponse, error) { + return api.retrieveAccessRule(ctx, "/user", accessRuleID) +} + +// UpdateUserAccessRule updates a single access rule for the logged-in user & +// given access rule identifier. +// +// API reference: https://api.cloudflare.com/#user-level-firewall-access-rule-update-access-rule +func (api *API) UpdateUserAccessRule(ctx context.Context, accessRuleID string, accessRule AccessRule) (*AccessRuleResponse, error) { + return api.updateAccessRule(ctx, "/user", accessRuleID, accessRule) +} + +// DeleteUserAccessRule deletes a single access rule for the logged-in user and +// access rule identifiers. +// +// API reference: https://api.cloudflare.com/#user-level-firewall-access-rule-update-access-rule +func (api *API) DeleteUserAccessRule(ctx context.Context, accessRuleID string) (*AccessRuleResponse, error) { + return api.deleteAccessRule(ctx, "/user", accessRuleID) +} + +// ListZoneAccessRules returns a slice of access rules for the given zone +// identifier. +// +// This takes an AccessRule to allow filtering of the results returned. +// +// API reference: https://api.cloudflare.com/#firewall-access-rule-for-a-zone-list-access-rules +func (api *API) ListZoneAccessRules(ctx context.Context, zoneID string, accessRule AccessRule, page int) (*AccessRuleListResponse, error) { + return api.listAccessRules(ctx, fmt.Sprintf("/zones/%s", zoneID), accessRule, page) +} + +// CreateZoneAccessRule creates a firewall access rule for the given zone +// identifier. +// +// API reference: https://api.cloudflare.com/#firewall-access-rule-for-a-zone-create-access-rule +func (api *API) CreateZoneAccessRule(ctx context.Context, zoneID string, accessRule AccessRule) (*AccessRuleResponse, error) { + return api.createAccessRule(ctx, fmt.Sprintf("/zones/%s", zoneID), accessRule) +} + +// ZoneAccessRule returns the details of a zone's access rule. +// +// API reference: https://api.cloudflare.com/#firewall-access-rule-for-a-zone-list-access-rules +func (api *API) ZoneAccessRule(ctx context.Context, zoneID string, accessRuleID string) (*AccessRuleResponse, error) { + return api.retrieveAccessRule(ctx, fmt.Sprintf("/zones/%s", zoneID), accessRuleID) +} + +// UpdateZoneAccessRule updates a single access rule for the given zone & +// access rule identifiers. +// +// API reference: https://api.cloudflare.com/#firewall-access-rule-for-a-zone-update-access-rule +func (api *API) UpdateZoneAccessRule(ctx context.Context, zoneID, accessRuleID string, accessRule AccessRule) (*AccessRuleResponse, error) { + return api.updateAccessRule(ctx, fmt.Sprintf("/zones/%s", zoneID), accessRuleID, accessRule) +} + +// DeleteZoneAccessRule deletes a single access rule for the given zone and +// access rule identifiers. +// +// API reference: https://api.cloudflare.com/#firewall-access-rule-for-a-zone-delete-access-rule +func (api *API) DeleteZoneAccessRule(ctx context.Context, zoneID, accessRuleID string) (*AccessRuleResponse, error) { + return api.deleteAccessRule(ctx, fmt.Sprintf("/zones/%s", zoneID), accessRuleID) +} + +// ListAccountAccessRules returns a slice of access rules for the given +// account identifier. +// +// This takes an AccessRule to allow filtering of the results returned. +// +// API reference: https://api.cloudflare.com/#account-level-firewall-access-rule-list-access-rules +func (api *API) ListAccountAccessRules(ctx context.Context, accountID string, accessRule AccessRule, page int) (*AccessRuleListResponse, error) { + return api.listAccessRules(ctx, fmt.Sprintf("/accounts/%s", accountID), accessRule, page) +} + +// CreateAccountAccessRule creates a firewall access rule for the given +// account identifier. +// +// API reference: https://api.cloudflare.com/#account-level-firewall-access-rule-create-access-rule +func (api *API) CreateAccountAccessRule(ctx context.Context, accountID string, accessRule AccessRule) (*AccessRuleResponse, error) { + return api.createAccessRule(ctx, fmt.Sprintf("/accounts/%s", accountID), accessRule) +} + +// AccountAccessRule returns the details of an account's access rule. +// +// API reference: https://api.cloudflare.com/#account-level-firewall-access-rule-access-rule-details +func (api *API) AccountAccessRule(ctx context.Context, accountID string, accessRuleID string) (*AccessRuleResponse, error) { + return api.retrieveAccessRule(ctx, fmt.Sprintf("/accounts/%s", accountID), accessRuleID) +} + +// UpdateAccountAccessRule updates a single access rule for the given +// account & access rule identifiers. +// +// API reference: https://api.cloudflare.com/#account-level-firewall-access-rule-update-access-rule +func (api *API) UpdateAccountAccessRule(ctx context.Context, accountID, accessRuleID string, accessRule AccessRule) (*AccessRuleResponse, error) { + return api.updateAccessRule(ctx, fmt.Sprintf("/accounts/%s", accountID), accessRuleID, accessRule) +} + +// DeleteAccountAccessRule deletes a single access rule for the given +// account and access rule identifiers. +// +// API reference: https://api.cloudflare.com/#account-level-firewall-access-rule-delete-access-rule +func (api *API) DeleteAccountAccessRule(ctx context.Context, accountID, accessRuleID string) (*AccessRuleResponse, error) { + return api.deleteAccessRule(ctx, fmt.Sprintf("/accounts/%s", accountID), accessRuleID) +} + +func (api *API) listAccessRules(ctx context.Context, prefix string, accessRule AccessRule, page int) (*AccessRuleListResponse, error) { + // Construct a query string + v := url.Values{} + if page <= 0 { + page = 1 + } + v.Set("page", strconv.Itoa(page)) + // Request as many rules as possible per page - API max is 100 + v.Set("per_page", "100") + if accessRule.Notes != "" { + v.Set("notes", accessRule.Notes) + } + if accessRule.Mode != "" { + v.Set("mode", accessRule.Mode) + } + if accessRule.Scope.Type != "" { + v.Set("scope_type", accessRule.Scope.Type) + } + if accessRule.Configuration.Value != "" { + v.Set("configuration_value", accessRule.Configuration.Value) + } + if accessRule.Configuration.Target != "" { + v.Set("configuration_target", accessRule.Configuration.Target) + } + v.Set("page", strconv.Itoa(page)) + + uri := fmt.Sprintf("%s/firewall/access_rules/rules?%s", prefix, v.Encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + response := &AccessRuleListResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return response, nil +} + +func (api *API) createAccessRule(ctx context.Context, prefix string, accessRule AccessRule) (*AccessRuleResponse, error) { + uri := fmt.Sprintf("%s/firewall/access_rules/rules", prefix) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, accessRule) + if err != nil { + return nil, err + } + + response := &AccessRuleResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +func (api *API) retrieveAccessRule(ctx context.Context, prefix, accessRuleID string) (*AccessRuleResponse, error) { + uri := fmt.Sprintf("%s/firewall/access_rules/rules/%s", prefix, accessRuleID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + + if err != nil { + return nil, err + } + + response := &AccessRuleResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +func (api *API) updateAccessRule(ctx context.Context, prefix, accessRuleID string, accessRule AccessRule) (*AccessRuleResponse, error) { + uri := fmt.Sprintf("%s/firewall/access_rules/rules/%s", prefix, accessRuleID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, accessRule) + if err != nil { + return nil, err + } + + response := &AccessRuleResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return response, nil +} + +func (api *API) deleteAccessRule(ctx context.Context, prefix, accessRuleID string) (*AccessRuleResponse, error) { + uri := fmt.Sprintf("%s/firewall/access_rules/rules/%s", prefix, accessRuleID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return nil, err + } + + response := &AccessRuleResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/firewall_rules.go b/vendor/github.com/cloudflare/cloudflare-go/firewall_rules.go new file mode 100644 index 0000000000000..480b38958d52c --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/firewall_rules.go @@ -0,0 +1,203 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/pkg/errors" +) + +// FirewallRule is the struct of the firewall rule. +type FirewallRule struct { + ID string `json:"id,omitempty"` + Paused bool `json:"paused"` + Description string `json:"description"` + Action string `json:"action"` + Priority interface{} `json:"priority"` + Filter Filter `json:"filter"` + Products []string `json:"products,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty"` +} + +// FirewallRulesDetailResponse is the API response for the firewall +// rules. +type FirewallRulesDetailResponse struct { + Result []FirewallRule `json:"result"` + ResultInfo `json:"result_info"` + Response +} + +// FirewallRuleResponse is the API response that is returned +// for requesting a single firewall rule on a zone. +type FirewallRuleResponse struct { + Result FirewallRule `json:"result"` + ResultInfo `json:"result_info"` + Response +} + +// FirewallRules returns all firewall rules. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/get/#get-all-rules +func (api *API) FirewallRules(ctx context.Context, zoneID string, pageOpts PaginationOptions) ([]FirewallRule, error) { + uri := fmt.Sprintf("/zones/%s/firewall/rules", zoneID) + v := url.Values{} + + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []FirewallRule{}, err + } + + var firewallDetailResponse FirewallRulesDetailResponse + err = json.Unmarshal(res, &firewallDetailResponse) + if err != nil { + return []FirewallRule{}, errors.Wrap(err, errUnmarshalError) + } + + return firewallDetailResponse.Result, nil +} + +// FirewallRule returns a single firewall rule based on the ID. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/get/#get-by-rule-id +func (api *API) FirewallRule(ctx context.Context, zoneID, firewallRuleID string) (FirewallRule, error) { + uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", zoneID, firewallRuleID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return FirewallRule{}, err + } + + var firewallRuleResponse FirewallRuleResponse + err = json.Unmarshal(res, &firewallRuleResponse) + if err != nil { + return FirewallRule{}, errors.Wrap(err, errUnmarshalError) + } + + return firewallRuleResponse.Result, nil +} + +// CreateFirewallRules creates new firewall rules. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/post/ +func (api *API) CreateFirewallRules(ctx context.Context, zoneID string, firewallRules []FirewallRule) ([]FirewallRule, error) { + uri := fmt.Sprintf("/zones/%s/firewall/rules", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, firewallRules) + if err != nil { + return []FirewallRule{}, err + } + + var firewallRulesDetailResponse FirewallRulesDetailResponse + err = json.Unmarshal(res, &firewallRulesDetailResponse) + if err != nil { + return []FirewallRule{}, errors.Wrap(err, errUnmarshalError) + } + + return firewallRulesDetailResponse.Result, nil +} + +// UpdateFirewallRule updates a single firewall rule. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/put/#update-a-single-rule +func (api *API) UpdateFirewallRule(ctx context.Context, zoneID string, firewallRule FirewallRule) (FirewallRule, error) { + if firewallRule.ID == "" { + return FirewallRule{}, errors.Errorf("firewall rule ID cannot be empty") + } + + uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", zoneID, firewallRule.ID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, firewallRule) + if err != nil { + return FirewallRule{}, err + } + + var firewallRuleResponse FirewallRuleResponse + err = json.Unmarshal(res, &firewallRuleResponse) + if err != nil { + return FirewallRule{}, errors.Wrap(err, errUnmarshalError) + } + + return firewallRuleResponse.Result, nil +} + +// UpdateFirewallRules updates a single firewall rule. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/put/#update-multiple-rules +func (api *API) UpdateFirewallRules(ctx context.Context, zoneID string, firewallRules []FirewallRule) ([]FirewallRule, error) { + for _, firewallRule := range firewallRules { + if firewallRule.ID == "" { + return []FirewallRule{}, errors.Errorf("firewall ID cannot be empty") + } + } + + uri := fmt.Sprintf("/zones/%s/firewall/rules", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, firewallRules) + if err != nil { + return []FirewallRule{}, err + } + + var firewallRulesDetailResponse FirewallRulesDetailResponse + err = json.Unmarshal(res, &firewallRulesDetailResponse) + if err != nil { + return []FirewallRule{}, errors.Wrap(err, errUnmarshalError) + } + + return firewallRulesDetailResponse.Result, nil +} + +// DeleteFirewallRule deletes a single firewall rule. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/delete/#delete-a-single-rule +func (api *API) DeleteFirewallRule(ctx context.Context, zoneID, firewallRuleID string) error { + if firewallRuleID == "" { + return errors.Errorf("firewall rule ID cannot be empty") + } + + uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", zoneID, firewallRuleID) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} + +// DeleteFirewallRules deletes multiple firewall rules at once. +// +// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/delete/#delete-multiple-rules +func (api *API) DeleteFirewallRules(ctx context.Context, zoneID string, firewallRuleIDs []string) error { + v := url.Values{} + + for _, ruleID := range firewallRuleIDs { + v.Add("id", ruleID) + } + + uri := fmt.Sprintf("/zones/%s/firewall/rules?%s", zoneID, v.Encode()) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/healthchecks.go b/vendor/github.com/cloudflare/cloudflare-go/healthchecks.go new file mode 100644 index 0000000000000..24d33ce846ce3 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/healthchecks.go @@ -0,0 +1,207 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// Healthcheck describes a Healthcheck object. +type Healthcheck struct { + ID string `json:"id,omitempty"` + CreatedOn *time.Time `json:"created_on,omitempty"` + ModifiedOn *time.Time `json:"modified_on,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Suspended bool `json:"suspended"` + Address string `json:"address"` + Retries int `json:"retries,omitempty"` + Timeout int `json:"timeout,omitempty"` + Interval int `json:"interval,omitempty"` + ConsecutiveSuccesses int `json:"consecutive_successes,omitempty"` + ConsecutiveFails int `json:"consecutive_fails,omitempty"` + Type string `json:"type,omitempty"` + CheckRegions []string `json:"check_regions"` + HTTPConfig *HealthcheckHTTPConfig `json:"http_config,omitempty"` + TCPConfig *HealthcheckTCPConfig `json:"tcp_config,omitempty"` + Notification HealthcheckNotification `json:"notification,omitempty"` + Status string `json:"status"` + FailureReason string `json:"failure_reason"` +} + +// HealthcheckHTTPConfig describes configuration for a HTTP healthcheck. +type HealthcheckHTTPConfig struct { + Method string `json:"method"` + Port uint16 `json:"port,omitempty"` + Path string `json:"path"` + ExpectedCodes []string `json:"expected_codes"` + ExpectedBody string `json:"expected_body"` + FollowRedirects bool `json:"follow_redirects"` + AllowInsecure bool `json:"allow_insecure"` + Header map[string][]string `json:"header"` +} + +// HealthcheckTCPConfig describes configuration for a TCP healthcheck. +type HealthcheckTCPConfig struct { + Method string `json:"method"` + Port uint16 `json:"port,omitempty"` +} + +// HealthcheckNotification describes notification configuration for a healthcheck. +type HealthcheckNotification struct { + Suspended bool `json:"suspended,omitempty"` + EmailAddresses []string `json:"email_addresses,omitempty"` +} + +// HealthcheckListResponse is the API response, containing an array of healthchecks. +type HealthcheckListResponse struct { + Response + Result []Healthcheck `json:"result"` + ResultInfo `json:"result_info"` +} + +// HealthcheckResponse is the API response, containing a single healthcheck. +type HealthcheckResponse struct { + Response + Result Healthcheck `json:"result"` +} + +// Healthchecks returns all healthchecks for a zone. +// +// API reference: https://api.cloudflare.com/#health-checks-list-health-checks +func (api *API) Healthchecks(ctx context.Context, zoneID string) ([]Healthcheck, error) { + uri := fmt.Sprintf("/zones/%s/healthchecks", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []Healthcheck{}, err + } + var r HealthcheckListResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []Healthcheck{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// Healthcheck returns a single healthcheck by ID. +// +// API reference: https://api.cloudflare.com/#health-checks-health-check-details +func (api *API) Healthcheck(ctx context.Context, zoneID, healthcheckID string) (Healthcheck, error) { + uri := fmt.Sprintf("/zones/%s/healthchecks/%s", zoneID, healthcheckID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return Healthcheck{}, err + } + var r HealthcheckResponse + err = json.Unmarshal(res, &r) + if err != nil { + return Healthcheck{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// CreateHealthcheck creates a new healthcheck in a zone. +// +// API reference: https://api.cloudflare.com/#health-checks-create-health-check +func (api *API) CreateHealthcheck(ctx context.Context, zoneID string, healthcheck Healthcheck) (Healthcheck, error) { + uri := fmt.Sprintf("/zones/%s/healthchecks", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, healthcheck) + if err != nil { + return Healthcheck{}, err + } + var r HealthcheckResponse + err = json.Unmarshal(res, &r) + if err != nil { + return Healthcheck{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateHealthcheck updates an existing healthcheck. +// +// API reference: https://api.cloudflare.com/#health-checks-update-health-check +func (api *API) UpdateHealthcheck(ctx context.Context, zoneID string, healthcheckID string, healthcheck Healthcheck) (Healthcheck, error) { + uri := fmt.Sprintf("/zones/%s/healthchecks/%s", zoneID, healthcheckID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, healthcheck) + if err != nil { + return Healthcheck{}, err + } + var r HealthcheckResponse + err = json.Unmarshal(res, &r) + if err != nil { + return Healthcheck{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeleteHealthcheck deletes a healthcheck in a zone. +// +// API reference: https://api.cloudflare.com/#health-checks-delete-health-check +func (api *API) DeleteHealthcheck(ctx context.Context, zoneID string, healthcheckID string) error { + uri := fmt.Sprintf("/zones/%s/healthchecks/%s", zoneID, healthcheckID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + var r HealthcheckResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +// CreateHealthcheckPreview creates a new preview of a healthcheck in a zone. +// +// API reference: https://api.cloudflare.com/#health-checks-create-preview-health-check +func (api *API) CreateHealthcheckPreview(ctx context.Context, zoneID string, healthcheck Healthcheck) (Healthcheck, error) { + uri := fmt.Sprintf("/zones/%s/healthchecks/preview", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, healthcheck) + if err != nil { + return Healthcheck{}, err + } + var r HealthcheckResponse + err = json.Unmarshal(res, &r) + if err != nil { + return Healthcheck{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// HealthcheckPreview returns a single healthcheck preview by its ID. +// +// API reference: https://api.cloudflare.com/#health-checks-health-check-preview-details +func (api *API) HealthcheckPreview(ctx context.Context, zoneID, id string) (Healthcheck, error) { + uri := fmt.Sprintf("/zones/%s/healthchecks/preview/%s", zoneID, id) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return Healthcheck{}, err + } + var r HealthcheckResponse + err = json.Unmarshal(res, &r) + if err != nil { + return Healthcheck{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeleteHealthcheckPreview deletes a healthcheck preview in a zone if it exists. +// +// API reference: https://api.cloudflare.com/#health-checks-delete-preview-health-check +func (api *API) DeleteHealthcheckPreview(ctx context.Context, zoneID string, id string) error { + uri := fmt.Sprintf("/zones/%s/healthchecks/preview/%s", zoneID, id) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + var r HealthcheckResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/ip_address_management.go b/vendor/github.com/cloudflare/cloudflare-go/ip_address_management.go new file mode 100644 index 0000000000000..de6fcb76ca881 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/ip_address_management.go @@ -0,0 +1,150 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// IPPrefix contains information about an IP prefix +type IPPrefix struct { + ID string `json:"id"` + CreatedAt *time.Time `json:"created_at"` + ModifiedAt *time.Time `json:"modified_at"` + CIDR string `json:"cidr"` + AccountID string `json:"account_id"` + Description string `json:"description"` + Approved string `json:"approved"` + OnDemandEnabled bool `json:"on_demand_enabled"` + OnDemandLocked bool `json:"on_demand_locked"` + Advertised bool `json:"advertised"` + AdvertisedModifiedAt *time.Time `json:"advertised_modified_at"` +} + +// AdvertisementStatus contains information about the BGP status of an IP prefix +type AdvertisementStatus struct { + Advertised bool `json:"advertised"` + AdvertisedModifiedAt *time.Time `json:"advertised_modified_at"` +} + +// ListIPPrefixResponse contains a slice of IP prefixes +type ListIPPrefixResponse struct { + Response + Result []IPPrefix `json:"result"` +} + +// GetIPPrefixResponse contains a specific IP prefix's API Response +type GetIPPrefixResponse struct { + Response + Result IPPrefix `json:"result"` +} + +// GetAdvertisementStatusResponse contains an API Response for the BGP status of the IP Prefix +type GetAdvertisementStatusResponse struct { + Response + Result AdvertisementStatus `json:"result"` +} + +// IPPrefixUpdateRequest contains information about prefix updates +type IPPrefixUpdateRequest struct { + Description string `json:"description"` +} + +// AdvertisementStatusUpdateRequest contains information about bgp status updates +type AdvertisementStatusUpdateRequest struct { + Advertised bool `json:"advertised"` +} + +// ListPrefixes lists all IP prefixes for a given account +// +// API reference: https://api.cloudflare.com/#ip-address-management-prefixes-list-prefixes +func (api *API) ListPrefixes(ctx context.Context) ([]IPPrefix, error) { + uri := fmt.Sprintf("/accounts/%s/addressing/prefixes", api.AccountID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []IPPrefix{}, err + } + + result := ListIPPrefixResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return []IPPrefix{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// GetPrefix returns a specific IP prefix +// +// API reference: https://api.cloudflare.com/#ip-address-management-prefixes-prefix-details +func (api *API) GetPrefix(ctx context.Context, id string) (IPPrefix, error) { + uri := fmt.Sprintf("/accounts/%s/addressing/prefixes/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return IPPrefix{}, err + } + + result := GetIPPrefixResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPPrefix{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// UpdatePrefixDescription edits the description of the IP prefix +// +// API reference: https://api.cloudflare.com/#ip-address-management-prefixes-update-prefix-description +func (api *API) UpdatePrefixDescription(ctx context.Context, id string, description string) (IPPrefix, error) { + uri := fmt.Sprintf("/accounts/%s/addressing/prefixes/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, IPPrefixUpdateRequest{Description: description}) + if err != nil { + return IPPrefix{}, err + } + + result := GetIPPrefixResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPPrefix{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// GetAdvertisementStatus returns the BGP status of the IP prefix +// +// API reference: https://api.cloudflare.com/#ip-address-management-prefixes-update-prefix-description +func (api *API) GetAdvertisementStatus(ctx context.Context, id string) (AdvertisementStatus, error) { + uri := fmt.Sprintf("/accounts/%s/addressing/prefixes/%s/bgp/status", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return AdvertisementStatus{}, err + } + + result := GetAdvertisementStatusResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return AdvertisementStatus{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// UpdateAdvertisementStatus changes the BGP status of an IP prefix +// +// API reference: https://api.cloudflare.com/#ip-address-management-prefixes-update-prefix-description +func (api *API) UpdateAdvertisementStatus(ctx context.Context, id string, advertised bool) (AdvertisementStatus, error) { + uri := fmt.Sprintf("/accounts/%s/addressing/prefixes/%s/bgp/status", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, AdvertisementStatusUpdateRequest{Advertised: advertised}) + if err != nil { + return AdvertisementStatus{}, err + } + + result := GetAdvertisementStatusResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return AdvertisementStatus{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/ip_list.go b/vendor/github.com/cloudflare/cloudflare-go/ip_list.go new file mode 100644 index 0000000000000..e09b1f1ae4a15 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/ip_list.go @@ -0,0 +1,463 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +const ( + // IPListTypeIP specifies a list containing IP addresses + IPListTypeIP = "ip" +) + +// IPListBulkOperation contains information about a Bulk Operation +type IPListBulkOperation struct { + ID string `json:"id"` + Status string `json:"status"` + Error string `json:"error"` + Completed *time.Time `json:"completed"` +} + +// IPList contains information about an IP List +type IPList struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Kind string `json:"kind"` + NumItems int `json:"num_items"` + NumReferencingFilters int `json:"num_referencing_filters"` + CreatedOn *time.Time `json:"created_on"` + ModifiedOn *time.Time `json:"modified_on"` +} + +// IPListItem contains information about a single IP List Item +type IPListItem struct { + ID string `json:"id"` + IP string `json:"ip"` + Comment string `json:"comment"` + CreatedOn *time.Time `json:"created_on"` + ModifiedOn *time.Time `json:"modified_on"` +} + +// IPListCreateRequest contains data for a new IP List +type IPListCreateRequest struct { + Name string `json:"name"` + Description string `json:"description"` + Kind string `json:"kind"` +} + +// IPListItemCreateRequest contains data for a new IP List Item +type IPListItemCreateRequest struct { + IP string `json:"ip"` + Comment string `json:"comment"` +} + +// IPListItemDeleteRequest wraps IP List Items that shall be deleted +type IPListItemDeleteRequest struct { + Items []IPListItemDeleteItemRequest `json:"items"` +} + +// IPListItemDeleteItemRequest contains single IP List Items that shall be deleted +type IPListItemDeleteItemRequest struct { + ID string `json:"id"` +} + +// IPListUpdateRequest contains data for an IP List update +type IPListUpdateRequest struct { + Description string `json:"description"` +} + +// IPListResponse contains a single IP List +type IPListResponse struct { + Response + Result IPList `json:"result"` +} + +// IPListItemCreateResponse contains information about the creation of an IP List Item +type IPListItemCreateResponse struct { + Response + Result struct { + OperationID string `json:"operation_id"` + } `json:"result"` +} + +// IPListListResponse contains a slice of IP Lists +type IPListListResponse struct { + Response + Result []IPList `json:"result"` +} + +// IPListBulkOperationResponse contains information about a Bulk Operation +type IPListBulkOperationResponse struct { + Response + Result IPListBulkOperation `json:"result"` +} + +// IPListDeleteResponse contains information about the deletion of an IP List +type IPListDeleteResponse struct { + Response + Result struct { + ID string `json:"id"` + } `json:"result"` +} + +// IPListItemsListResponse contains information about IP List Items +type IPListItemsListResponse struct { + Response + ResultInfo `json:"result_info"` + Result []IPListItem `json:"result"` +} + +// IPListItemDeleteResponse contains information about the deletion of an IP List Item +type IPListItemDeleteResponse struct { + Response + Result struct { + OperationID string `json:"operation_id"` + } `json:"result"` +} + +// IPListItemsGetResponse contains information about a single IP List Item +type IPListItemsGetResponse struct { + Response + Result IPListItem `json:"result"` +} + +// ListIPLists lists all IP Lists +// +// API reference: https://api.cloudflare.com/#rules-lists-list-lists +func (api *API) ListIPLists(ctx context.Context) ([]IPList, error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists", api.AccountID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []IPList{}, err + } + + result := IPListListResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return []IPList{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// CreateIPList creates a new IP List +// +// API reference: https://api.cloudflare.com/#rules-lists-create-list +func (api *API) CreateIPList(ctx context.Context, name string, description string, kind string) (IPList, + error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists", api.AccountID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, + IPListCreateRequest{Name: name, Description: description, Kind: kind}) + if err != nil { + return IPList{}, err + } + + result := IPListResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPList{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// GetIPList returns a single IP List +// +// API reference: https://api.cloudflare.com/#rules-lists-get-list +func (api *API) GetIPList(ctx context.Context, id string) (IPList, error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return IPList{}, err + } + + result := IPListResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPList{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// UpdateIPList updates the description of an existing IP List +// +// API reference: https://api.cloudflare.com/#rules-lists-update-list +func (api *API) UpdateIPList(ctx context.Context, id string, description string) (IPList, error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, IPListUpdateRequest{Description: description}) + if err != nil { + return IPList{}, err + } + + result := IPListResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPList{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// DeleteIPList deletes an IP List +// +// API reference: https://api.cloudflare.com/#rules-lists-delete-list +func (api *API) DeleteIPList(ctx context.Context, id string) (IPListDeleteResponse, error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return IPListDeleteResponse{}, err + } + + result := IPListDeleteResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPListDeleteResponse{}, errors.Wrap(err, errUnmarshalError) + } + + return result, nil +} + +// ListIPListItems returns a list with all items in an IP List +// +// API reference: https://api.cloudflare.com/#rules-lists-list-list-items +func (api *API) ListIPListItems(ctx context.Context, id string) ([]IPListItem, error) { + var list []IPListItem + var cursor string + var cursorQuery string + + for { + if len(cursor) > 0 { + cursorQuery = fmt.Sprintf("?cursor=%s", cursor) + } + uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items%s", api.AccountID, id, cursorQuery) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []IPListItem{}, err + } + + result := IPListItemsListResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return []IPListItem{}, errors.Wrap(err, errUnmarshalError) + } + + list = append(list, result.Result...) + if cursor = result.ResultInfo.Cursors.After; cursor == "" { + break + } + } + + return list, nil +} + +// CreateIPListItemAsync creates a new IP List Item asynchronously. Users have to poll the operation status by +// using the operation_id returned by this function. +// +// API reference: https://api.cloudflare.com/#rules-lists-create-list-items +func (api *API) CreateIPListItemAsync(ctx context.Context, id, ip, comment string) (IPListItemCreateResponse, error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, []IPListItemCreateRequest{{IP: ip, Comment: comment}}) + if err != nil { + return IPListItemCreateResponse{}, err + } + + result := IPListItemCreateResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPListItemCreateResponse{}, errors.Wrap(err, errUnmarshalError) + } + + return result, nil +} + +// CreateIPListItem creates a new IP List Item synchronously and returns the current set of IP List Items +func (api *API) CreateIPListItem(ctx context.Context, id, ip, comment string) ([]IPListItem, error) { + result, err := api.CreateIPListItemAsync(ctx, id, ip, comment) + + if err != nil { + return []IPListItem{}, err + } + + err = api.pollIPListBulkOperation(ctx, result.Result.OperationID) + if err != nil { + return []IPListItem{}, err + } + + return api.ListIPListItems(ctx, id) +} + +// CreateIPListItemsAsync bulk creates many IP List Items asynchronously. Users have to poll the operation status by +// using the operation_id returned by this function. +// +// API reference: https://api.cloudflare.com/#rules-lists-create-list-items +func (api *API) CreateIPListItemsAsync(ctx context.Context, id string, items []IPListItemCreateRequest) ( + IPListItemCreateResponse, error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, items) + if err != nil { + return IPListItemCreateResponse{}, err + } + + result := IPListItemCreateResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPListItemCreateResponse{}, errors.Wrap(err, errUnmarshalError) + } + + return result, nil +} + +// CreateIPListItems bulk creates many IP List Items synchronously and returns the current set of IP List Items +func (api *API) CreateIPListItems(ctx context.Context, id string, items []IPListItemCreateRequest) ( + []IPListItem, error) { + result, err := api.CreateIPListItemsAsync(ctx, id, items) + if err != nil { + return []IPListItem{}, err + } + + err = api.pollIPListBulkOperation(ctx, result.Result.OperationID) + if err != nil { + return []IPListItem{}, err + } + + return api.ListIPListItems(ctx, id) +} + +// ReplaceIPListItemsAsync replaces all IP List Items asynchronously. Users have to poll the operation status by +// using the operation_id returned by this function. +// +// API reference: https://api.cloudflare.com/#rules-lists-replace-list-items +func (api *API) ReplaceIPListItemsAsync(ctx context.Context, id string, items []IPListItemCreateRequest) ( + IPListItemCreateResponse, error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, items) + if err != nil { + return IPListItemCreateResponse{}, err + } + + result := IPListItemCreateResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPListItemCreateResponse{}, errors.Wrap(err, errUnmarshalError) + } + + return result, nil +} + +// ReplaceIPListItems replaces all IP List Items synchronously and returns the current set of IP List Items +func (api *API) ReplaceIPListItems(ctx context.Context, id string, items []IPListItemCreateRequest) ( + []IPListItem, error) { + result, err := api.ReplaceIPListItemsAsync(ctx, id, items) + if err != nil { + return []IPListItem{}, err + } + + err = api.pollIPListBulkOperation(ctx, result.Result.OperationID) + if err != nil { + return []IPListItem{}, err + } + + return api.ListIPListItems(ctx, id) +} + +// DeleteIPListItemsAsync removes specific Items of an IP List by their ID asynchronously. Users have to poll the +// operation status by using the operation_id returned by this function. +// +// API reference: https://api.cloudflare.com/#rules-lists-delete-list-items +func (api *API) DeleteIPListItemsAsync(ctx context.Context, id string, items IPListItemDeleteRequest) ( + IPListItemDeleteResponse, error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, items) + if err != nil { + return IPListItemDeleteResponse{}, err + } + + result := IPListItemDeleteResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPListItemDeleteResponse{}, errors.Wrap(err, errUnmarshalError) + } + + return result, nil +} + +// DeleteIPListItems removes specific Items of an IP List by their ID synchronously and returns the current set +// of IP List Items +func (api *API) DeleteIPListItems(ctx context.Context, id string, items IPListItemDeleteRequest) ( + []IPListItem, error) { + result, err := api.DeleteIPListItemsAsync(ctx, id, items) + if err != nil { + return []IPListItem{}, err + } + + err = api.pollIPListBulkOperation(ctx, result.Result.OperationID) + if err != nil { + return []IPListItem{}, err + } + + return api.ListIPListItems(ctx, id) +} + +// GetIPListItem returns a single IP List Item +// +// API reference: https://api.cloudflare.com/#rules-lists-get-list-item +func (api *API) GetIPListItem(ctx context.Context, listID, id string) (IPListItem, error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists/%s/items/%s", api.AccountID, listID, id) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return IPListItem{}, err + } + + result := IPListItemsGetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPListItem{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// GetIPListBulkOperation returns the status of a bulk operation +// +// API reference: https://api.cloudflare.com/#rules-lists-get-bulk-operation +func (api *API) GetIPListBulkOperation(ctx context.Context, id string) (IPListBulkOperation, error) { + uri := fmt.Sprintf("/accounts/%s/rules/lists/bulk_operations/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return IPListBulkOperation{}, err + } + + result := IPListBulkOperationResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return IPListBulkOperation{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// pollIPListBulkOperation implements synchronous behaviour for some asynchronous endpoints. +// bulk-operation status can be either pending, running, failed or completed +func (api *API) pollIPListBulkOperation(ctx context.Context, id string) error { + for i := uint8(0); i < 16; i++ { + sleepDuration := 1 << (i / 2) * time.Second + select { + case <-time.After(sleepDuration): + case <-ctx.Done(): + return errors.Wrap(ctx.Err(), "operation aborted during backoff") + } + + bulkResult, err := api.GetIPListBulkOperation(ctx, id) + if err != nil { + return err + } + + switch bulkResult.Status { + case "failed": + return errors.New(bulkResult.Error) + case "pending", "running": + continue + case "completed": + return nil + default: + return errors.New(fmt.Sprintf("%s: %s", errOperationUnexpectedStatus, bulkResult.Status)) + } + } + + return errors.New(errOperationStillRunning) +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/ips.go b/vendor/github.com/cloudflare/cloudflare-go/ips.go new file mode 100644 index 0000000000000..db54751af0c84 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/ips.go @@ -0,0 +1,69 @@ +package cloudflare + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + + "github.com/pkg/errors" +) + +// IPRangesResponse contains the structure for the API response, not modified. +type IPRangesResponse struct { + IPv4CIDRs []string `json:"ipv4_cidrs"` + IPv6CIDRs []string `json:"ipv6_cidrs"` + ChinaColos []string `json:"china_colos"` +} + +// IPRanges contains lists of IPv4 and IPv6 CIDRs. +type IPRanges struct { + IPv4CIDRs []string `json:"ipv4_cidrs"` + IPv6CIDRs []string `json:"ipv6_cidrs"` + ChinaIPv4CIDRs []string `json:"china_ipv4_cidrs"` + ChinaIPv6CIDRs []string `json:"china_ipv6_cidrs"` +} + +// IPsResponse is the API response containing a list of IPs. +type IPsResponse struct { + Response + Result IPRangesResponse `json:"result"` +} + +// IPs gets a list of Cloudflare's IP ranges. +// +// This does not require logging in to the API. +// +// API reference: https://api.cloudflare.com/#cloudflare-ips +func IPs() (IPRanges, error) { + uri := fmt.Sprintf("%s/ips?china_colo=1", apiURL) + resp, err := http.Get(uri) + if err != nil { + return IPRanges{}, errors.Wrap(err, "HTTP request failed") + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return IPRanges{}, errors.Wrap(err, "Response body could not be read") + } + var r IPsResponse + err = json.Unmarshal(body, &r) + if err != nil { + return IPRanges{}, errors.Wrap(err, errUnmarshalError) + } + + var ips IPRanges + ips.IPv4CIDRs = r.Result.IPv4CIDRs + ips.IPv6CIDRs = r.Result.IPv6CIDRs + + for _, ip := range r.Result.ChinaColos { + if strings.Contains(ip, ":") { + ips.ChinaIPv6CIDRs = append(ips.ChinaIPv6CIDRs, ip) + } else { + ips.ChinaIPv4CIDRs = append(ips.ChinaIPv4CIDRs, ip) + } + } + + return ips, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/keyless.go b/vendor/github.com/cloudflare/cloudflare-go/keyless.go new file mode 100644 index 0000000000000..4558091d0e8fa --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/keyless.go @@ -0,0 +1,147 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// KeylessSSL represents Keyless SSL configuration. +type KeylessSSL struct { + ID string `json:"id"` + Name string `json:"name"` + Host string `json:"host"` + Port int `json:"port"` + Status string `json:"status"` + Enabled bool `json:"enabled"` + Permissions []string `json:"permissions"` + CreatedOn time.Time `json:"created_on"` + ModifiedOn time.Time `json:"modified_on"` +} + +// KeylessSSLCreateRequest represents the request format made for creating KeylessSSL. +type KeylessSSLCreateRequest struct { + Host string `json:"host"` + Port int `json:"port"` + Certificate string `json:"certificate"` + Name string `json:"name,omitempty"` + BundleMethod string `json:"bundle_method,omitempty"` +} + +// KeylessSSLDetailResponse is the API response, containing a single Keyless SSL. +type KeylessSSLDetailResponse struct { + Response + Result KeylessSSL `json:"result"` +} + +// KeylessSSLListResponse represents the response from the Keyless SSL list endpoint. +type KeylessSSLListResponse struct { + Response + Result []KeylessSSL `json:"result"` +} + +// KeylessSSLUpdateRequest represents the request for updating KeylessSSL. +type KeylessSSLUpdateRequest struct { + Host string `json:"host,omitempty"` + Name string `json:"name,omitempty"` + Port int `json:"port,omitempty"` + Enabled *bool `json:"enabled,omitempty"` +} + +// CreateKeylessSSL creates a new Keyless SSL configuration for the zone. +// +// API reference: https://api.cloudflare.com/#keyless-ssl-for-a-zone-create-keyless-ssl-configuration +func (api *API) CreateKeylessSSL(ctx context.Context, zoneID string, keylessSSL KeylessSSLCreateRequest) (KeylessSSL, error) { + uri := fmt.Sprintf("/zones/%s/keyless_certificates", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, keylessSSL) + if err != nil { + return KeylessSSL{}, err + } + + var keylessSSLDetailResponse KeylessSSLDetailResponse + err = json.Unmarshal(res, &keylessSSLDetailResponse) + if err != nil { + return KeylessSSL{}, errors.Wrap(err, errUnmarshalError) + } + + return keylessSSLDetailResponse.Result, nil +} + +// ListKeylessSSL lists Keyless SSL configurations for a zone. +// +// API reference: https://api.cloudflare.com/#keyless-ssl-for-a-zone-list-keyless-ssl-configurations +func (api *API) ListKeylessSSL(ctx context.Context, zoneID string) ([]KeylessSSL, error) { + uri := fmt.Sprintf("/zones/%s/keyless_certificates", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + var keylessSSLListResponse KeylessSSLListResponse + err = json.Unmarshal(res, &keylessSSLListResponse) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return keylessSSLListResponse.Result, nil +} + +// KeylessSSL provides the configuration for a given Keyless SSL identifier. +// +// API reference: https://api.cloudflare.com/#keyless-ssl-for-a-zone-keyless-ssl-details +func (api *API) KeylessSSL(ctx context.Context, zoneID, keylessSSLID string) (KeylessSSL, error) { + uri := fmt.Sprintf("/zones/%s/keyless_certificates/%s", zoneID, keylessSSLID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return KeylessSSL{}, err + } + + var keylessResponse KeylessSSLDetailResponse + err = json.Unmarshal(res, &keylessResponse) + if err != nil { + return KeylessSSL{}, errors.Wrap(err, errUnmarshalError) + } + + return keylessResponse.Result, nil +} + +// UpdateKeylessSSL updates an existing Keyless SSL configuration. +// +// API reference: https://api.cloudflare.com/#keyless-ssl-for-a-zone-edit-keyless-ssl-configuration +func (api *API) UpdateKeylessSSL(ctx context.Context, zoneID, kelessSSLID string, keylessSSL KeylessSSLUpdateRequest) (KeylessSSL, error) { + uri := fmt.Sprintf("/zones/%s/keyless_certificates/%s", zoneID, kelessSSLID) + + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, keylessSSL) + if err != nil { + return KeylessSSL{}, err + } + + var keylessSSLDetailResponse KeylessSSLDetailResponse + err = json.Unmarshal(res, &keylessSSLDetailResponse) + if err != nil { + return KeylessSSL{}, errors.Wrap(err, errUnmarshalError) + } + + return keylessSSLDetailResponse.Result, nil +} + +// DeleteKeylessSSL deletes an existing Keyless SSL configuration. +// +// API reference: https://api.cloudflare.com/#keyless-ssl-for-a-zone-delete-keyless-ssl-configuration +func (api *API) DeleteKeylessSSL(ctx context.Context, zoneID, keylessSSLID string) error { + uri := fmt.Sprintf("/zones/%s/keyless_certificates/%s", zoneID, keylessSSLID) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/load_balancing.go b/vendor/github.com/cloudflare/cloudflare-go/load_balancing.go new file mode 100644 index 0000000000000..1c2a7d4ff635d --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/load_balancing.go @@ -0,0 +1,479 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// LoadBalancerPool represents a load balancer pool's properties. +type LoadBalancerPool struct { + ID string `json:"id,omitempty"` + CreatedOn *time.Time `json:"created_on,omitempty"` + ModifiedOn *time.Time `json:"modified_on,omitempty"` + Description string `json:"description"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + MinimumOrigins int `json:"minimum_origins,omitempty"` + Monitor string `json:"monitor,omitempty"` + Origins []LoadBalancerOrigin `json:"origins"` + NotificationEmail string `json:"notification_email,omitempty"` + Latitude *float32 `json:"latitude,omitempty"` + Longitude *float32 `json:"longitude,omitempty"` + LoadShedding *LoadBalancerLoadShedding `json:"load_shedding,omitempty"` + OriginSteering *LoadBalancerOriginSteering `json:"origin_steering,omitempty"` + + // CheckRegions defines the geographic region(s) from where to run health-checks from - e.g. "WNAM", "WEU", "SAF", "SAM". + // Providing a null/empty value means "all regions", which may not be available to all plan types. + CheckRegions []string `json:"check_regions"` +} + +// LoadBalancerOrigin represents a Load Balancer origin's properties. +type LoadBalancerOrigin struct { + Name string `json:"name"` + Address string `json:"address"` + Enabled bool `json:"enabled"` + Weight float64 `json:"weight"` + Header map[string][]string `json:"header"` +} + +// LoadBalancerOriginSteering controls origin selection for new sessions and traffic without session affinity. +type LoadBalancerOriginSteering struct { + // Policy defaults to "random" (weighted) when empty or unspecified. + Policy string `json:"policy,omitempty"` +} + +// LoadBalancerMonitor represents a load balancer monitor's properties. +type LoadBalancerMonitor struct { + ID string `json:"id,omitempty"` + CreatedOn *time.Time `json:"created_on,omitempty"` + ModifiedOn *time.Time `json:"modified_on,omitempty"` + Type string `json:"type"` + Description string `json:"description"` + Method string `json:"method"` + Path string `json:"path"` + Header map[string][]string `json:"header"` + Timeout int `json:"timeout"` + Retries int `json:"retries"` + Interval int `json:"interval"` + Port uint16 `json:"port,omitempty"` + ExpectedBody string `json:"expected_body"` + ExpectedCodes string `json:"expected_codes"` + FollowRedirects bool `json:"follow_redirects"` + AllowInsecure bool `json:"allow_insecure"` + ProbeZone string `json:"probe_zone"` +} + +// LoadBalancer represents a load balancer's properties. +type LoadBalancer struct { + ID string `json:"id,omitempty"` + CreatedOn *time.Time `json:"created_on,omitempty"` + ModifiedOn *time.Time `json:"modified_on,omitempty"` + Description string `json:"description"` + Name string `json:"name"` + TTL int `json:"ttl,omitempty"` + FallbackPool string `json:"fallback_pool"` + DefaultPools []string `json:"default_pools"` + RegionPools map[string][]string `json:"region_pools"` + PopPools map[string][]string `json:"pop_pools"` + Proxied bool `json:"proxied"` + Enabled *bool `json:"enabled,omitempty"` + Persistence string `json:"session_affinity,omitempty"` + PersistenceTTL int `json:"session_affinity_ttl,omitempty"` + SessionAffinityAttributes *SessionAffinityAttributes `json:"session_affinity_attributes,omitempty"` + Rules []*LoadBalancerRule `json:"rules,omitempty"` + + // SteeringPolicy controls pool selection logic. + // "off" select pools in DefaultPools order + // "geo" select pools based on RegionPools/PopPools + // "dynamic_latency" select pools based on RTT (requires health checks) + // "random" selects pools in a random order + // "proximity" select pools based on 'distance' from request + // "" maps to "geo" if RegionPools or PopPools have entries otherwise "off" + SteeringPolicy string `json:"steering_policy,omitempty"` +} + +// LoadBalancerLoadShedding contains the settings for controlling load shedding +type LoadBalancerLoadShedding struct { + DefaultPercent float32 `json:"default_percent,omitempty"` + DefaultPolicy string `json:"default_policy,omitempty"` + SessionPercent float32 `json:"session_percent,omitempty"` + SessionPolicy string `json:"session_policy,omitempty"` +} + +// LoadBalancerRule represents a single rule entry for a Load Balancer. Each rules +// is run one after the other in priority order. Disabled rules are skipped. +type LoadBalancerRule struct { + // Name is required but is only used for human readability + Name string `json:"name"` + // Priority controls the order of rule execution the lowest value will be invoked first + Priority int `json:"priority"` + Disabled bool `json:"disabled"` + + Condition string `json:"condition"` + Overrides LoadBalancerRuleOverrides `json:"overrides"` + + // Terminates flag this rule as 'terminating'. No further rules will + // be executed after this one. + Terminates bool `json:"terminates,omitempty"` + + // FixedResponse if set and the condition is true we will not run + // routing logic but rather directly respond with the provided fields. + // FixedResponse implies terminates. + FixedResponse *LoadBalancerFixedResponseData `json:"fixed_response,omitempty"` +} + +// LoadBalancerFixedResponseData contains all the data needed to generate +// a fixed response from a Load Balancer. This behavior can be enabled via Rules. +type LoadBalancerFixedResponseData struct { + // MessageBody data to write into the http body + MessageBody string `json:"message_body,omitempty"` + // StatusCode the http status code to response with + StatusCode int `json:"status_code,omitempty"` + // ContentType value of the http 'content-type' header + ContentType string `json:"content_type,omitempty"` + // Location value of the http 'location' header + Location string `json:"location,omitempty"` +} + +// LoadBalancerRuleOverrides are the set of field overridable by the rules system. +type LoadBalancerRuleOverrides struct { + // session affinity + Persistence string `json:"session_affinity,omitempty"` + PersistenceTTL *uint `json:"session_affinity_ttl,omitempty"` + + SessionAffinityAttrs *LoadBalancerRuleOverridesSessionAffinityAttrs `json:"session_affinity_attributes,omitempty"` + + TTL uint `json:"ttl,omitempty"` + + SteeringPolicy string `json:"steering_policy,omitempty"` + FallbackPool string `json:"fallback_pool,omitempty"` + + DefaultPools []string `json:"default_pools,omitempty"` + PoPPools map[string][]string `json:"pop_pools,omitempty"` + RegionPools map[string][]string `json:"region_pools,omitempty"` +} + +// LoadBalancerRuleOverridesSessionAffinityAttrs mimics SessionAffinityAttributes without the +// DrainDuration field as that field can not be overwritten via rules. +type LoadBalancerRuleOverridesSessionAffinityAttrs struct { + SameSite string `json:"samesite,omitempty"` + Secure string `json:"secure,omitempty"` +} + +// SessionAffinityAttributes represents the fields used to set attributes in a load balancer session affinity cookie. +type SessionAffinityAttributes struct { + SameSite string `json:"samesite,omitempty"` + Secure string `json:"secure,omitempty"` + DrainDuration int `json:"drain_duration,omitempty"` +} + +// LoadBalancerOriginHealth represents the health of the origin. +type LoadBalancerOriginHealth struct { + Healthy bool `json:"healthy,omitempty"` + RTT Duration `json:"rtt,omitempty"` + FailureReason string `json:"failure_reason,omitempty"` + ResponseCode int `json:"response_code,omitempty"` +} + +// LoadBalancerPoolPopHealth represents the health of the pool for given PoP. +type LoadBalancerPoolPopHealth struct { + Healthy bool `json:"healthy,omitempty"` + Origins []map[string]LoadBalancerOriginHealth `json:"origins,omitempty"` +} + +// LoadBalancerPoolHealth represents the healthchecks from different PoPs for a pool. +type LoadBalancerPoolHealth struct { + ID string `json:"pool_id,omitempty"` + PopHealth map[string]LoadBalancerPoolPopHealth `json:"pop_health,omitempty"` +} + +// loadBalancerPoolResponse represents the response from the load balancer pool endpoints. +type loadBalancerPoolResponse struct { + Response + Result LoadBalancerPool `json:"result"` +} + +// loadBalancerPoolListResponse represents the response from the List Pools endpoint. +type loadBalancerPoolListResponse struct { + Response + Result []LoadBalancerPool `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// loadBalancerMonitorResponse represents the response from the load balancer monitor endpoints. +type loadBalancerMonitorResponse struct { + Response + Result LoadBalancerMonitor `json:"result"` +} + +// loadBalancerMonitorListResponse represents the response from the List Monitors endpoint. +type loadBalancerMonitorListResponse struct { + Response + Result []LoadBalancerMonitor `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// loadBalancerResponse represents the response from the load balancer endpoints. +type loadBalancerResponse struct { + Response + Result LoadBalancer `json:"result"` +} + +// loadBalancerListResponse represents the response from the List Load Balancers endpoint. +type loadBalancerListResponse struct { + Response + Result []LoadBalancer `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// loadBalancerPoolHealthResponse represents the response from the Pool Health Details endpoint. +type loadBalancerPoolHealthResponse struct { + Response + Result LoadBalancerPoolHealth `json:"result"` +} + +// CreateLoadBalancerPool creates a new load balancer pool. +// +// API reference: https://api.cloudflare.com/#load-balancer-pools-create-pool +func (api *API) CreateLoadBalancerPool(ctx context.Context, pool LoadBalancerPool) (LoadBalancerPool, error) { + uri := fmt.Sprintf("%s/load_balancers/pools", api.userBaseURL("/user")) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, pool) + if err != nil { + return LoadBalancerPool{}, err + } + var r loadBalancerPoolResponse + if err := json.Unmarshal(res, &r); err != nil { + return LoadBalancerPool{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListLoadBalancerPools lists load balancer pools connected to an account. +// +// API reference: https://api.cloudflare.com/#load-balancer-pools-list-pools +func (api *API) ListLoadBalancerPools(ctx context.Context) ([]LoadBalancerPool, error) { + uri := fmt.Sprintf("%s/load_balancers/pools", api.userBaseURL("/user")) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + var r loadBalancerPoolListResponse + if err := json.Unmarshal(res, &r); err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// LoadBalancerPoolDetails returns the details for a load balancer pool. +// +// API reference: https://api.cloudflare.com/#load-balancer-pools-pool-details +func (api *API) LoadBalancerPoolDetails(ctx context.Context, poolID string) (LoadBalancerPool, error) { + uri := fmt.Sprintf("%s/load_balancers/pools/%s", api.userBaseURL("/user"), poolID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return LoadBalancerPool{}, err + } + var r loadBalancerPoolResponse + if err := json.Unmarshal(res, &r); err != nil { + return LoadBalancerPool{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeleteLoadBalancerPool disables and deletes a load balancer pool. +// +// API reference: https://api.cloudflare.com/#load-balancer-pools-delete-pool +func (api *API) DeleteLoadBalancerPool(ctx context.Context, poolID string) error { + uri := fmt.Sprintf("%s/load_balancers/pools/%s", api.userBaseURL("/user"), poolID) + if _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil); err != nil { + return err + } + return nil +} + +// ModifyLoadBalancerPool modifies a configured load balancer pool. +// +// API reference: https://api.cloudflare.com/#load-balancer-pools-update-pool +func (api *API) ModifyLoadBalancerPool(ctx context.Context, pool LoadBalancerPool) (LoadBalancerPool, error) { + uri := fmt.Sprintf("%s/load_balancers/pools/%s", api.userBaseURL("/user"), pool.ID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, pool) + if err != nil { + return LoadBalancerPool{}, err + } + var r loadBalancerPoolResponse + if err := json.Unmarshal(res, &r); err != nil { + return LoadBalancerPool{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// CreateLoadBalancerMonitor creates a new load balancer monitor. +// +// API reference: https://api.cloudflare.com/#load-balancer-monitors-create-monitor +func (api *API) CreateLoadBalancerMonitor(ctx context.Context, monitor LoadBalancerMonitor) (LoadBalancerMonitor, error) { + uri := fmt.Sprintf("%s/load_balancers/monitors", api.userBaseURL("/user")) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, monitor) + if err != nil { + return LoadBalancerMonitor{}, err + } + var r loadBalancerMonitorResponse + if err := json.Unmarshal(res, &r); err != nil { + return LoadBalancerMonitor{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListLoadBalancerMonitors lists load balancer monitors connected to an account. +// +// API reference: https://api.cloudflare.com/#load-balancer-monitors-list-monitors +func (api *API) ListLoadBalancerMonitors(ctx context.Context) ([]LoadBalancerMonitor, error) { + uri := fmt.Sprintf("%s/load_balancers/monitors", api.userBaseURL("/user")) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + var r loadBalancerMonitorListResponse + if err := json.Unmarshal(res, &r); err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// LoadBalancerMonitorDetails returns the details for a load balancer monitor. +// +// API reference: https://api.cloudflare.com/#load-balancer-monitors-monitor-details +func (api *API) LoadBalancerMonitorDetails(ctx context.Context, monitorID string) (LoadBalancerMonitor, error) { + uri := fmt.Sprintf("%s/load_balancers/monitors/%s", api.userBaseURL("/user"), monitorID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return LoadBalancerMonitor{}, err + } + var r loadBalancerMonitorResponse + if err := json.Unmarshal(res, &r); err != nil { + return LoadBalancerMonitor{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeleteLoadBalancerMonitor disables and deletes a load balancer monitor. +// +// API reference: https://api.cloudflare.com/#load-balancer-monitors-delete-monitor +func (api *API) DeleteLoadBalancerMonitor(ctx context.Context, monitorID string) error { + uri := fmt.Sprintf("%s/load_balancers/monitors/%s", api.userBaseURL("/user"), monitorID) + if _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil); err != nil { + return err + } + return nil +} + +// ModifyLoadBalancerMonitor modifies a configured load balancer monitor. +// +// API reference: https://api.cloudflare.com/#load-balancer-monitors-update-monitor +func (api *API) ModifyLoadBalancerMonitor(ctx context.Context, monitor LoadBalancerMonitor) (LoadBalancerMonitor, error) { + uri := fmt.Sprintf("%s/load_balancers/monitors/%s", api.userBaseURL("/user"), monitor.ID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, monitor) + if err != nil { + return LoadBalancerMonitor{}, err + } + var r loadBalancerMonitorResponse + if err := json.Unmarshal(res, &r); err != nil { + return LoadBalancerMonitor{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// CreateLoadBalancer creates a new load balancer. +// +// API reference: https://api.cloudflare.com/#load-balancers-create-load-balancer +func (api *API) CreateLoadBalancer(ctx context.Context, zoneID string, lb LoadBalancer) (LoadBalancer, error) { + uri := fmt.Sprintf("/zones/%s/load_balancers", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, lb) + if err != nil { + return LoadBalancer{}, err + } + var r loadBalancerResponse + if err := json.Unmarshal(res, &r); err != nil { + return LoadBalancer{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListLoadBalancers lists load balancers configured on a zone. +// +// API reference: https://api.cloudflare.com/#load-balancers-list-load-balancers +func (api *API) ListLoadBalancers(ctx context.Context, zoneID string) ([]LoadBalancer, error) { + uri := fmt.Sprintf("/zones/%s/load_balancers", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + var r loadBalancerListResponse + if err := json.Unmarshal(res, &r); err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// LoadBalancerDetails returns the details for a load balancer. +// +// API reference: https://api.cloudflare.com/#load-balancers-load-balancer-details +func (api *API) LoadBalancerDetails(ctx context.Context, zoneID, lbID string) (LoadBalancer, error) { + uri := fmt.Sprintf("/zones/%s/load_balancers/%s", zoneID, lbID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return LoadBalancer{}, err + } + var r loadBalancerResponse + if err := json.Unmarshal(res, &r); err != nil { + return LoadBalancer{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeleteLoadBalancer disables and deletes a load balancer. +// +// API reference: https://api.cloudflare.com/#load-balancers-delete-load-balancer +func (api *API) DeleteLoadBalancer(ctx context.Context, zoneID, lbID string) error { + uri := fmt.Sprintf("/zones/%s/load_balancers/%s", zoneID, lbID) + if _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil); err != nil { + return err + } + return nil +} + +// ModifyLoadBalancer modifies a configured load balancer. +// +// API reference: https://api.cloudflare.com/#load-balancers-update-load-balancer +func (api *API) ModifyLoadBalancer(ctx context.Context, zoneID string, lb LoadBalancer) (LoadBalancer, error) { + uri := fmt.Sprintf("/zones/%s/load_balancers/%s", zoneID, lb.ID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, lb) + if err != nil { + return LoadBalancer{}, err + } + var r loadBalancerResponse + if err := json.Unmarshal(res, &r); err != nil { + return LoadBalancer{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// PoolHealthDetails fetches the latest healtcheck details for a single pool. +// +// API reference: https://api.cloudflare.com/#load-balancer-pools-pool-health-details +func (api *API) PoolHealthDetails(ctx context.Context, poolID string) (LoadBalancerPoolHealth, error) { + uri := fmt.Sprintf("%s/load_balancers/pools/%s/health", api.userBaseURL("/user"), poolID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return LoadBalancerPoolHealth{}, err + } + var r loadBalancerPoolHealthResponse + if err := json.Unmarshal(res, &r); err != nil { + return LoadBalancerPoolHealth{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/lockdown.go b/vendor/github.com/cloudflare/cloudflare-go/lockdown.go new file mode 100644 index 0000000000000..ef17ac8fc4890 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/lockdown.go @@ -0,0 +1,156 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/pkg/errors" +) + +// ZoneLockdown represents a Zone Lockdown rule. A rule only permits access to +// the provided URL pattern(s) from the given IP address(es) or subnet(s). +type ZoneLockdown struct { + ID string `json:"id"` + Description string `json:"description"` + URLs []string `json:"urls"` + Configurations []ZoneLockdownConfig `json:"configurations"` + Paused bool `json:"paused"` + Priority int `json:"priority,omitempty"` + CreatedOn *time.Time `json:"created_on,omitempty"` + ModifiedOn *time.Time `json:"modified_on,omitempty"` +} + +// ZoneLockdownConfig represents a Zone Lockdown config, which comprises +// a Target ("ip" or "ip_range") and a Value (an IP address or IP+mask, +// respectively.) +type ZoneLockdownConfig struct { + Target string `json:"target"` + Value string `json:"value"` +} + +// ZoneLockdownResponse represents a response from the Zone Lockdown endpoint. +type ZoneLockdownResponse struct { + Result ZoneLockdown `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// ZoneLockdownListResponse represents a response from the List Zone Lockdown +// endpoint. +type ZoneLockdownListResponse struct { + Result []ZoneLockdown `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// CreateZoneLockdown creates a Zone ZoneLockdown rule for the given zone ID. +// +// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-create-a-ZoneLockdown-rule +func (api *API) CreateZoneLockdown(ctx context.Context, zoneID string, ld ZoneLockdown) (*ZoneLockdownResponse, error) { + uri := fmt.Sprintf("/zones/%s/firewall/lockdowns", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, ld) + if err != nil { + return nil, err + } + + response := &ZoneLockdownResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// UpdateZoneLockdown updates a Zone ZoneLockdown rule (based on the ID) for the +// given zone ID. +// +// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-update-ZoneLockdown-rule +func (api *API) UpdateZoneLockdown(ctx context.Context, zoneID string, id string, ld ZoneLockdown) (*ZoneLockdownResponse, error) { + uri := fmt.Sprintf("/zones/%s/firewall/lockdowns/%s", zoneID, id) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, ld) + if err != nil { + return nil, err + } + + response := &ZoneLockdownResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// DeleteZoneLockdown deletes a Zone ZoneLockdown rule (based on the ID) for the +// given zone ID. +// +// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-delete-ZoneLockdown-rule +func (api *API) DeleteZoneLockdown(ctx context.Context, zoneID string, id string) (*ZoneLockdownResponse, error) { + uri := fmt.Sprintf("/zones/%s/firewall/lockdowns/%s", zoneID, id) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return nil, err + } + + response := &ZoneLockdownResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// ZoneLockdown retrieves a Zone ZoneLockdown rule (based on the ID) for the +// given zone ID. +// +// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-ZoneLockdown-rule-details +func (api *API) ZoneLockdown(ctx context.Context, zoneID string, id string) (*ZoneLockdownResponse, error) { + uri := fmt.Sprintf("/zones/%s/firewall/lockdowns/%s", zoneID, id) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + response := &ZoneLockdownResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// ListZoneLockdowns retrieves a list of Zone ZoneLockdown rules for a given +// zone ID by page number. +// +// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-list-ZoneLockdown-rules +func (api *API) ListZoneLockdowns(ctx context.Context, zoneID string, page int) (*ZoneLockdownListResponse, error) { + v := url.Values{} + if page <= 0 { + page = 1 + } + + v.Set("page", strconv.Itoa(page)) + v.Set("per_page", strconv.Itoa(100)) + + uri := fmt.Sprintf("/zones/%s/firewall/lockdowns?%s", zoneID, v.Encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + response := &ZoneLockdownListResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/logpull.go b/vendor/github.com/cloudflare/cloudflare-go/logpull.go new file mode 100644 index 0000000000000..8351b4942919d --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/logpull.go @@ -0,0 +1,175 @@ +package cloudflare + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" +) + +// LogpullRetentionConfiguration describes a the structure of a Logpull Retention +// payload. +type LogpullRetentionConfiguration struct { + Flag bool `json:"flag"` +} + +// LogpullRetentionConfigurationResponse is the API response, containing the +// Logpull retention result. +type LogpullRetentionConfigurationResponse struct { + Response + Result LogpullRetentionConfiguration `json:"result"` +} + +// GetLogpullRetentionFlag gets the current setting flag. +// +// API reference: https://developers.cloudflare.com/logs/logpull-api/enabling-log-retention/ +func (api *API) GetLogpullRetentionFlag(ctx context.Context, zoneID string) (*LogpullRetentionConfiguration, error) { + uri := fmt.Sprintf("/zones/%s/logs/control/retention/flag", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return &LogpullRetentionConfiguration{}, err + } + var r LogpullRetentionConfigurationResponse + err = json.Unmarshal(res, &r) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return &r.Result, nil +} + +// SetLogpullRetentionFlag updates the retention flag to the defined boolean. +// +// API reference: https://developers.cloudflare.com/logs/logpull-api/enabling-log-retention/ +func (api *API) SetLogpullRetentionFlag(ctx context.Context, zoneID string, enabled bool) (*LogpullRetentionConfiguration, error) { + uri := fmt.Sprintf("/zones/%s/logs/control/retention/flag", zoneID) + flagPayload := LogpullRetentionConfiguration{Flag: enabled} + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, flagPayload) + if err != nil { + return &LogpullRetentionConfiguration{}, err + } + var r LogpullRetentionConfigurationResponse + err = json.Unmarshal(res, &r) + if err != nil { + return &LogpullRetentionConfiguration{}, err + } + return &r.Result, nil +} + +type ( + LogpullReceivedIterator interface { + Next() bool + Err() error + Line() []byte + Fields() (map[string]string, error) + Close() error + } + logpullReceivedResponse struct { + scanner *bufio.Scanner + reader io.ReadCloser + } + LogpullReceivedOption struct { + Fields []string + Count *int64 + Sample *float64 + } +) + +// LogpullReceived return the logs received for a given zone. +// The response returned is an iterator over the logs. +// Use Next() to iterate over the logs, and Close() to close the iterator. +// `Err()` returned the last error encountered while iterating. +// The logs are returned in the order they were received. +// Calling `Line()` return the current raw log line as a slice of bytes, you must copy the line as each call to `Next() will change its value. +// `Fields()` return the parsed log fields. +// +// Usage: +// +// defer r.Close() +// for r.Next() { +// if r.Err() != nil { +// return r.Err() +// } +// l := r.Line() +// } +// +// API reference: https://developers.cloudflare.com/logs/logpull/requesting-logs +func (api *API) LogpullReceived(ctx context.Context, zoneID string, start, end time.Time, opts LogpullReceivedOption) (LogpullReceivedIterator, error) { + uri := fmt.Sprintf("/zones/%s/logs/received", zoneID) + v := url.Values{} + + v.Set("start", strconv.FormatInt(start.UnixNano(), 10)) + v.Set("end", strconv.FormatInt(end.UnixNano(), 10)) + + if opts.Fields != nil { + v.Set("fields", strings.Join(opts.Fields, ",")) + } + + if opts.Count != nil { + v.Set("count", strconv.FormatInt(*opts.Count, 10)) + } + + if opts.Sample != nil { + v.Set("sample", strconv.FormatFloat(*opts.Sample, 'f', -1, 64)) + } + + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + reader, err := api.makeRequestWithAuthTypeAndHeaders(ctx, http.MethodGet, uri, nil, api.authType, http.Header{"Accept-Encoding": []string{"gzip"}}) + if err != nil { + return nil, err + } + return &logpullReceivedResponse{ + scanner: bufio.NewScanner(reader), + reader: reader, + }, nil +} + +func (r *logpullReceivedResponse) Next() bool { + return r.scanner.Scan() +} + +func (r *logpullReceivedResponse) Err() error { + return r.scanner.Err() +} + +func (r *logpullReceivedResponse) Line() []byte { + return r.scanner.Bytes() +} + +func (r *logpullReceivedResponse) Fields() (map[string]string, error) { + var fields map[string]string + data := r.Line() + err := json.Unmarshal(data, &fields) + return fields, err +} + +func (r *logpullReceivedResponse) Close() error { + return r.reader.Close() +} + +// LogpullFields returns the list of all available log fields. +// +// API reference: https://developers.cloudflare.com/logs/logpull/requesting-logs +func (api *API) LogpullFields(ctx context.Context, zoneID string) (map[string]string, error) { + uri := fmt.Sprintf("/zones/%s/logs/received/fields", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + var r map[string]string + err = json.Unmarshal(res, &r) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/logpush.go b/vendor/github.com/cloudflare/cloudflare-go/logpush.go new file mode 100644 index 0000000000000..1e5f363aa95a2 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/logpush.go @@ -0,0 +1,275 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// LogpushJob describes a Logpush job. +type LogpushJob struct { + ID int `json:"id,omitempty"` + Dataset string `json:"dataset"` + Enabled bool `json:"enabled"` + Name string `json:"name"` + LogpullOptions string `json:"logpull_options"` + DestinationConf string `json:"destination_conf"` + OwnershipChallenge string `json:"ownership_challenge,omitempty"` + LastComplete *time.Time `json:"last_complete,omitempty"` + LastError *time.Time `json:"last_error,omitempty"` + ErrorMessage string `json:"error_message,omitempty"` +} + +// LogpushJobsResponse is the API response, containing an array of Logpush Jobs. +type LogpushJobsResponse struct { + Response + Result []LogpushJob `json:"result"` +} + +// LogpushJobDetailsResponse is the API response, containing a single Logpush Job. +type LogpushJobDetailsResponse struct { + Response + Result LogpushJob `json:"result"` +} + +// LogpushFieldsResponse is the API response for a datasets fields +type LogpushFieldsResponse struct { + Response + Result LogpushFields `json:"result"` +} + +// LogpushFields is a map of available Logpush field names & descriptions +type LogpushFields map[string]string + +// LogpushGetOwnershipChallenge describes a ownership validation. +type LogpushGetOwnershipChallenge struct { + Filename string `json:"filename"` + Valid bool `json:"valid"` + Message string `json:"message"` +} + +// LogpushGetOwnershipChallengeResponse is the API response, containing a ownership challenge. +type LogpushGetOwnershipChallengeResponse struct { + Response + Result LogpushGetOwnershipChallenge `json:"result"` +} + +// LogpushGetOwnershipChallengeRequest is the API request for get ownership challenge. +type LogpushGetOwnershipChallengeRequest struct { + DestinationConf string `json:"destination_conf"` +} + +// LogpushOwnershipChallengeValidationResponse is the API response, +// containing a ownership challenge validation result. +type LogpushOwnershipChallengeValidationResponse struct { + Response + Result struct { + Valid bool `json:"valid"` + } +} + +// LogpushValidateOwnershipChallengeRequest is the API request for validate ownership challenge. +type LogpushValidateOwnershipChallengeRequest struct { + DestinationConf string `json:"destination_conf"` + OwnershipChallenge string `json:"ownership_challenge"` +} + +// LogpushDestinationExistsResponse is the API response, +// containing a destination exists check result. +type LogpushDestinationExistsResponse struct { + Response + Result struct { + Exists bool `json:"exists"` + } +} + +// LogpushDestinationExistsRequest is the API request for check destination exists. +type LogpushDestinationExistsRequest struct { + DestinationConf string `json:"destination_conf"` +} + +// CreateLogpushJob creates a new LogpushJob for a zone. +// +// API reference: https://api.cloudflare.com/#logpush-jobs-create-logpush-job +func (api *API) CreateLogpushJob(ctx context.Context, zoneID string, job LogpushJob) (*LogpushJob, error) { + uri := fmt.Sprintf("/zones/%s/logpush/jobs", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, job) + if err != nil { + return nil, err + } + var r LogpushJobDetailsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return &r.Result, nil +} + +// LogpushJobs returns all Logpush Jobs for a zone. +// +// API reference: https://api.cloudflare.com/#logpush-jobs-list-logpush-jobs +func (api *API) LogpushJobs(ctx context.Context, zoneID string) ([]LogpushJob, error) { + uri := fmt.Sprintf("/zones/%s/logpush/jobs", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []LogpushJob{}, err + } + var r LogpushJobsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []LogpushJob{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// LogpushJobsForDataset returns all Logpush Jobs for a dataset in a zone. +// +// API reference: https://api.cloudflare.com/#logpush-jobs-list-logpush-jobs-for-a-dataset +func (api *API) LogpushJobsForDataset(ctx context.Context, zoneID, dataset string) ([]LogpushJob, error) { + uri := fmt.Sprintf("/zones/%s/logpush/datasets/%s/jobs", zoneID, dataset) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []LogpushJob{}, err + } + var r LogpushJobsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []LogpushJob{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// LogpushFields returns fields for a given dataset. +// +// API reference: https://api.cloudflare.com/#logpush-jobs-list-logpush-jobs +func (api *API) LogpushFields(ctx context.Context, zoneID, dataset string) (LogpushFields, error) { + uri := fmt.Sprintf("/zones/%s/logpush/datasets/%s/fields", zoneID, dataset) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return LogpushFields{}, err + } + var r LogpushFieldsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return LogpushFields{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// LogpushJob fetches detail about one Logpush Job for a zone. +// +// API reference: https://api.cloudflare.com/#logpush-jobs-logpush-job-details +func (api *API) LogpushJob(ctx context.Context, zoneID string, jobID int) (LogpushJob, error) { + uri := fmt.Sprintf("/zones/%s/logpush/jobs/%d", zoneID, jobID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return LogpushJob{}, err + } + var r LogpushJobDetailsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return LogpushJob{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateLogpushJob lets you update a Logpush Job. +// +// API reference: https://api.cloudflare.com/#logpush-jobs-update-logpush-job +func (api *API) UpdateLogpushJob(ctx context.Context, zoneID string, jobID int, job LogpushJob) error { + uri := fmt.Sprintf("/zones/%s/logpush/jobs/%d", zoneID, jobID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, job) + if err != nil { + return err + } + var r LogpushJobDetailsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +// DeleteLogpushJob deletes a Logpush Job for a zone. +// +// API reference: https://api.cloudflare.com/#logpush-jobs-delete-logpush-job +func (api *API) DeleteLogpushJob(ctx context.Context, zoneID string, jobID int) error { + uri := fmt.Sprintf("/zones/%s/logpush/jobs/%d", zoneID, jobID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + var r LogpushJobDetailsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +// GetLogpushOwnershipChallenge returns ownership challenge. +// +// API reference: https://api.cloudflare.com/#logpush-jobs-get-ownership-challenge +func (api *API) GetLogpushOwnershipChallenge(ctx context.Context, zoneID, destinationConf string) (*LogpushGetOwnershipChallenge, error) { + uri := fmt.Sprintf("/zones/%s/logpush/ownership", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, LogpushGetOwnershipChallengeRequest{ + DestinationConf: destinationConf, + }) + if err != nil { + return nil, err + } + var r LogpushGetOwnershipChallengeResponse + err = json.Unmarshal(res, &r) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + if !r.Result.Valid { + return nil, errors.New(r.Result.Message) + } + + return &r.Result, nil +} + +// ValidateLogpushOwnershipChallenge returns ownership challenge validation result. +// +// API reference: https://api.cloudflare.com/#logpush-jobs-validate-ownership-challenge +func (api *API) ValidateLogpushOwnershipChallenge(ctx context.Context, zoneID, destinationConf, ownershipChallenge string) (bool, error) { + uri := fmt.Sprintf("/zones/%s/logpush/ownership/validate", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, LogpushValidateOwnershipChallengeRequest{ + DestinationConf: destinationConf, + OwnershipChallenge: ownershipChallenge, + }) + if err != nil { + return false, err + } + var r LogpushGetOwnershipChallengeResponse + err = json.Unmarshal(res, &r) + if err != nil { + return false, errors.Wrap(err, errUnmarshalError) + } + return r.Result.Valid, nil +} + +// CheckLogpushDestinationExists returns destination exists check result. +// +// API reference: https://api.cloudflare.com/#logpush-jobs-check-destination-exists +func (api *API) CheckLogpushDestinationExists(ctx context.Context, zoneID, destinationConf string) (bool, error) { + uri := fmt.Sprintf("/zones/%s/logpush/validate/destination/exists", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, LogpushDestinationExistsRequest{ + DestinationConf: destinationConf, + }) + if err != nil { + return false, err + } + var r LogpushDestinationExistsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return false, errors.Wrap(err, errUnmarshalError) + } + return r.Result.Exists, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/magic_firewall_rulesets.go b/vendor/github.com/cloudflare/cloudflare-go/magic_firewall_rulesets.go new file mode 100644 index 0000000000000..c463569af6406 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/magic_firewall_rulesets.go @@ -0,0 +1,224 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +const ( + // MagicFirewallRulesetKindRoot specifies a root Ruleset + MagicFirewallRulesetKindRoot = "root" + + // MagicFirewallRulesetPhaseMagicTransit specifies the Magic Transit Ruleset phase + MagicFirewallRulesetPhaseMagicTransit = "magic_transit" + + // MagicFirewallRulesetRuleActionSkip specifies a skip (allow) action + MagicFirewallRulesetRuleActionSkip MagicFirewallRulesetRuleAction = "skip" + + // MagicFirewallRulesetRuleActionBlock specifies a block action + MagicFirewallRulesetRuleActionBlock MagicFirewallRulesetRuleAction = "block" +) + +// MagicFirewallRulesetRuleAction specifies the action for a Firewall rule +type MagicFirewallRulesetRuleAction string + +// MagicFirewallRuleset contains information about a Firewall Ruleset +type MagicFirewallRuleset struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Kind string `json:"kind"` + Version string `json:"version,omitempty"` + LastUpdated *time.Time `json:"last_updated,omitempty"` + Phase string `json:"phase"` + Rules []MagicFirewallRulesetRule `json:"rules"` +} + +// MagicFirewallRulesetRuleActionParameters specifies the action parameters for a Firewall rule +type MagicFirewallRulesetRuleActionParameters struct { + Ruleset string `json:"ruleset,omitempty"` +} + +// MagicFirewallRulesetRule contains information about a single Magic Firewall rule +type MagicFirewallRulesetRule struct { + ID string `json:"id,omitempty"` + Version string `json:"version,omitempty"` + Action MagicFirewallRulesetRuleAction `json:"action"` + ActionParameters *MagicFirewallRulesetRuleActionParameters `json:"action_parameters,omitempty"` + Expression string `json:"expression"` + Description string `json:"description"` + LastUpdated *time.Time `json:"last_updated,omitempty"` + Ref string `json:"ref,omitempty"` + Enabled bool `json:"enabled"` +} + +// CreateMagicFirewallRulesetRequest contains data for a new Firewall ruleset +type CreateMagicFirewallRulesetRequest struct { + Name string `json:"name"` + Description string `json:"description"` + Kind string `json:"kind"` + Phase string `json:"phase"` + Rules []MagicFirewallRulesetRule `json:"rules"` +} + +// UpdateMagicFirewallRulesetRequest contains data for a Magic Firewall ruleset update +type UpdateMagicFirewallRulesetRequest struct { + Description string `json:"description"` + Rules []MagicFirewallRulesetRule `json:"rules"` +} + +// ListMagicFirewallRulesetResponse contains a list of Magic Firewall rulesets +type ListMagicFirewallRulesetResponse struct { + Response + Result []MagicFirewallRuleset `json:"result"` +} + +// GetMagicFirewallRulesetResponse contains a single Magic Firewall Ruleset +type GetMagicFirewallRulesetResponse struct { + Response + Result MagicFirewallRuleset `json:"result"` +} + +// CreateMagicFirewallRulesetResponse contains response data when creating a new Magic Firewall ruleset +type CreateMagicFirewallRulesetResponse struct { + Response + Result MagicFirewallRuleset `json:"result"` +} + +// UpdateMagicFirewallRulesetResponse contains response data when updating an existing Magic Firewall ruleset +type UpdateMagicFirewallRulesetResponse struct { + Response + Result MagicFirewallRuleset `json:"result"` +} + +// ListMagicFirewallRulesets lists all Rulesets for a given account +// +// API reference: https://api.cloudflare.com/#rulesets-list-rulesets +func (api *API) ListMagicFirewallRulesets(ctx context.Context) ([]MagicFirewallRuleset, error) { + if err := api.checkAccountID(); err != nil { + return []MagicFirewallRuleset{}, err + } + + uri := fmt.Sprintf("/accounts/%s/rulesets", api.AccountID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []MagicFirewallRuleset{}, err + } + + result := ListMagicFirewallRulesetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return []MagicFirewallRuleset{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// GetMagicFirewallRuleset returns a specific Magic Firewall Ruleset +// +// API reference: https://api.cloudflare.com/#rulesets-get-a-ruleset +func (api *API) GetMagicFirewallRuleset(ctx context.Context, id string) (MagicFirewallRuleset, error) { + if err := api.checkAccountID(); err != nil { + return MagicFirewallRuleset{}, err + } + + uri := fmt.Sprintf("/accounts/%s/rulesets/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return MagicFirewallRuleset{}, err + } + + result := GetMagicFirewallRulesetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return MagicFirewallRuleset{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// CreateMagicFirewallRuleset creates a Magic Firewall ruleset +// +// API reference: https://api.cloudflare.com/#rulesets-list-rulesets +func (api *API) CreateMagicFirewallRuleset(ctx context.Context, name string, description string, rules []MagicFirewallRulesetRule) (MagicFirewallRuleset, error) { + if err := api.checkAccountID(); err != nil { + return MagicFirewallRuleset{}, err + } + + uri := fmt.Sprintf("/accounts/%s/rulesets", api.AccountID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, + CreateMagicFirewallRulesetRequest{ + Name: name, + Description: description, + Kind: MagicFirewallRulesetKindRoot, + Phase: MagicFirewallRulesetPhaseMagicTransit, + Rules: rules}) + if err != nil { + return MagicFirewallRuleset{}, err + } + + result := CreateMagicFirewallRulesetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return MagicFirewallRuleset{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// DeleteMagicFirewallRuleset deletes a Magic Firewall ruleset +// +// API reference: https://api.cloudflare.com/#rulesets-delete-ruleset +func (api *API) DeleteMagicFirewallRuleset(ctx context.Context, id string) error { + if err := api.checkAccountID(); err != nil { + return err + } + + uri := fmt.Sprintf("/accounts/%s/rulesets/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + + if err != nil { + return err + } + + // Firewall API is not implementing the standard response blob but returns an empty response (204) in case + // of a success. So we are checking for the response body size here + if len(res) > 0 { + return errors.Wrap(errors.New(string(res)), errMakeRequestError) + } + + return nil +} + +// UpdateMagicFirewallRuleset updates a Magic Firewall ruleset +// +// API reference: https://api.cloudflare.com/#rulesets-update-ruleset +func (api *API) UpdateMagicFirewallRuleset(ctx context.Context, id string, description string, rules []MagicFirewallRulesetRule) (MagicFirewallRuleset, error) { + if err := api.checkAccountID(); err != nil { + return MagicFirewallRuleset{}, err + } + + uri := fmt.Sprintf("/accounts/%s/rulesets/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, + UpdateMagicFirewallRulesetRequest{Description: description, Rules: rules}) + if err != nil { + return MagicFirewallRuleset{}, err + } + + result := UpdateMagicFirewallRulesetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return MagicFirewallRuleset{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +func (api *API) checkAccountID() error { + if api.AccountID == "" { + return fmt.Errorf("account ID must not be empty") + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/magic_transit_static_routes.go b/vendor/github.com/cloudflare/cloudflare-go/magic_transit_static_routes.go new file mode 100644 index 0000000000000..cb402a688001d --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/magic_transit_static_routes.go @@ -0,0 +1,199 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "github.com/pkg/errors" + "net/http" + "time" +) + +// Magic Transit Static Routes Error messages +const ( + errMagicTransitStaticRouteNotModified = "When trying to modify static route, API returned modified: false" + errMagicTransitStaticRouteNotDeleted = "When trying to delete static route, API returned deleted: false" +) + +// MagicTransitStaticRouteScope contains information about a static route's scope +type MagicTransitStaticRouteScope struct { + ColoRegions []string `json:"colo_regions,omitempty"` + ColoNames []string `json:"colo_names,omitempty"` +} + +// MagicTransitStaticRoute contains information about a static route +type MagicTransitStaticRoute struct { + ID string `json:"id,omitempty"` + Prefix string `json:"prefix"` + CreatedOn *time.Time `json:"created_on,omitempty"` + ModifiedOn *time.Time `json:"modified_on,omitempty"` + Nexthop string `json:"nexthop"` + Priority int `json:"priority,omitempty"` + Description string `json:"description,omitempty"` + Weight int `json:"weight,omitempty"` + Scope MagicTransitStaticRouteScope `json:"scope,omitempty"` +} + +// ListMagicTransitStaticRoutesResponse contains a response including Magic Transit static routes +type ListMagicTransitStaticRoutesResponse struct { + Response + Result struct { + Routes []MagicTransitStaticRoute `json:"routes"` + } `json:"result"` +} + +// GetMagicTransitStaticRouteResponse contains a response including exactly one static route +type GetMagicTransitStaticRouteResponse struct { + Response + Result struct { + Route MagicTransitStaticRoute `json:"route"` + } `json:"result"` +} + +// UpdateMagicTransitStaticRouteResponse contains a static route update response +type UpdateMagicTransitStaticRouteResponse struct { + Response + Result struct { + Modified bool `json:"modified"` + ModifiedRoute MagicTransitStaticRoute `json:"modified_route"` + } `json:"result"` +} + +// DeleteMagicTransitStaticRouteResponse contains a static route deletion response +type DeleteMagicTransitStaticRouteResponse struct { + Response + Result struct { + Deleted bool `json:"deleted"` + DeletedRoute MagicTransitStaticRoute `json:"deleted_route"` + } `json:"result"` +} + +// CreateMagicTransitStaticRoutesRequest is an array of static routes to create +type CreateMagicTransitStaticRoutesRequest struct { + Routes []MagicTransitStaticRoute `json:"routes"` +} + +// ListMagicTransitStaticRoutes lists all static routes for a given account +// +// API reference: https://api.cloudflare.com/#magic-transit-static-routes-list-routes +func (api *API) ListMagicTransitStaticRoutes(ctx context.Context) ([]MagicTransitStaticRoute, error) { + if err := api.checkAccountID(); err != nil { + return []MagicTransitStaticRoute{}, err + } + + uri := fmt.Sprintf("/accounts/%s/magic/routes", api.AccountID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []MagicTransitStaticRoute{}, err + } + + result := ListMagicTransitStaticRoutesResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return []MagicTransitStaticRoute{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result.Routes, nil +} + +// GetMagicTransitStaticRoute returns exactly one static route +// +// API reference: https://api.cloudflare.com/#magic-transit-static-routes-route-details +func (api *API) GetMagicTransitStaticRoute(ctx context.Context, id string) (MagicTransitStaticRoute, error) { + if err := api.checkAccountID(); err != nil { + return MagicTransitStaticRoute{}, err + } + + uri := fmt.Sprintf("/accounts/%s/magic/routes/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return MagicTransitStaticRoute{}, err + } + + result := GetMagicTransitStaticRouteResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return MagicTransitStaticRoute{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result.Route, nil +} + +// CreateMagicTransitStaticRoute creates a new static route +// +// API reference: https://api.cloudflare.com/#magic-transit-static-routes-create-routes +func (api *API) CreateMagicTransitStaticRoute(ctx context.Context, route MagicTransitStaticRoute) ([]MagicTransitStaticRoute, error) { + if err := api.checkAccountID(); err != nil { + return []MagicTransitStaticRoute{}, err + } + + uri := fmt.Sprintf("/accounts/%s/magic/routes", api.AccountID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, CreateMagicTransitStaticRoutesRequest{ + Routes: []MagicTransitStaticRoute{ + route, + }, + }) + + if err != nil { + return []MagicTransitStaticRoute{}, err + } + + result := ListMagicTransitStaticRoutesResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return []MagicTransitStaticRoute{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result.Routes, nil +} + +// UpdateMagicTransitStaticRoute updates a static route +// +// API reference: https://api.cloudflare.com/#magic-transit-static-routes-update-route +func (api *API) UpdateMagicTransitStaticRoute(ctx context.Context, id string, route MagicTransitStaticRoute) (MagicTransitStaticRoute, error) { + if err := api.checkAccountID(); err != nil { + return MagicTransitStaticRoute{}, err + } + + uri := fmt.Sprintf("/accounts/%s/magic/routes/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, route) + + if err != nil { + return MagicTransitStaticRoute{}, err + } + + result := UpdateMagicTransitStaticRouteResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return MagicTransitStaticRoute{}, errors.Wrap(err, errUnmarshalError) + } + + if !result.Result.Modified { + return MagicTransitStaticRoute{}, errors.New(errMagicTransitStaticRouteNotModified) + } + + return result.Result.ModifiedRoute, nil +} + +// DeleteMagicTransitStaticRoute deletes a static route +// +// API reference: https://api.cloudflare.com/#magic-transit-static-routes-delete-route +func (api *API) DeleteMagicTransitStaticRoute(ctx context.Context, id string) (MagicTransitStaticRoute, error) { + if err := api.checkAccountID(); err != nil { + return MagicTransitStaticRoute{}, err + } + + uri := fmt.Sprintf("/accounts/%s/magic/routes/%s", api.AccountID, id) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + + if err != nil { + return MagicTransitStaticRoute{}, err + } + + result := DeleteMagicTransitStaticRouteResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return MagicTransitStaticRoute{}, errors.Wrap(err, errUnmarshalError) + } + + if !result.Result.Deleted { + return MagicTransitStaticRoute{}, errors.New(errMagicTransitStaticRouteNotDeleted) + } + + return result.Result.DeletedRoute, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/notifications.go b/vendor/github.com/cloudflare/cloudflare-go/notifications.go new file mode 100644 index 0000000000000..fe7630c650fe2 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/notifications.go @@ -0,0 +1,450 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" +) + +// NotificationMechanismData holds a single public facing mechanism data +// integation. +type NotificationMechanismData struct { + Name string `json:"name"` + ID string `json:"id"` +} + +// NotificationMechanismIntegrations is a list of all the integrations of a +// certain mechanism type e.g. all email integrations +type NotificationMechanismIntegrations []NotificationMechanismData + +// NotificationPolicy represents the notification policy created along with +// the destinations. +type NotificationPolicy struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Enabled bool `json:"enabled"` + AlertType string `json:"alert_type"` + Mechanisms map[string]NotificationMechanismIntegrations `json:"mechanisms"` + Created time.Time `json:"created"` + Modified time.Time `json:"modified"` + Conditions map[string]interface{} `json:"conditions"` + Filters map[string][]string `json:"filters"` +} + +// NotificationPoliciesResponse holds the response for listing all +// notification policies for an account. +type NotificationPoliciesResponse struct { + Response + ResultInfo + Result []NotificationPolicy +} + +// NotificationPolicyResponse holds the response type when a single policy +// is retrieved. +type NotificationPolicyResponse struct { + Response + Result NotificationPolicy +} + +// NotificationWebhookIntegration describes the webhook information along +// with its status. +type NotificationWebhookIntegration struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + URL string `json:"url"` + CreatedAt time.Time `json:"created_at"` + LastSuccess *time.Time `json:"last_success"` + LastFailure *time.Time `json:"last_failure"` +} + +// NotificationWebhookResponse describes a single webhook retrieved. +type NotificationWebhookResponse struct { + Response + ResultInfo + Result NotificationWebhookIntegration +} + +// NotificationWebhooksResponse describes a list of webhooks retrieved. +type NotificationWebhooksResponse struct { + Response + ResultInfo + Result []NotificationWebhookIntegration +} + +// NotificationUpsertWebhooks describes a valid webhook request. +type NotificationUpsertWebhooks struct { + Name string `json:"name"` + URL string `json:"url"` + Secret string `json:"secret"` +} + +// NotificationPagerDutyResource describes a PagerDuty integration. +type NotificationPagerDutyResource struct { + ID string `json:"id"` + Name string `json:"name"` +} + +// NotificationPagerDutyResponse describes the PagerDuty integration +// retrieved. +type NotificationPagerDutyResponse struct { + Response + ResultInfo + Result NotificationPagerDutyResource +} + +// NotificationResource describes the id of an inserted/updated/deleted +// resource. +type NotificationResource struct { + ID string +} + +// SaveResponse is returned when a resource is inserted/updated/deleted. +type SaveResponse struct { + Response + Result NotificationResource +} + +// NotificationMechanismMetaData represents the state of the delivery +// mechanism. +type NotificationMechanismMetaData struct { + Eligible bool `json:"eligible"` + Ready bool `json:"ready"` + Type string `json:"type"` +} + +// NotificationMechanisms are the different possible delivery mechanisms. +type NotificationMechanisms struct { + Email NotificationMechanismMetaData `json:"email"` + PagerDuty NotificationMechanismMetaData `json:"pagerduty"` + Webhooks NotificationMechanismMetaData `json:"webhooks,omitempty"` +} + +// NotificationEligibilityResponse describes the eligible mechanisms that +// can be configured for a notification. +type NotificationEligibilityResponse struct { + Response + Result NotificationMechanisms +} + +// NotificationsGroupedByProduct are grouped by products. +type NotificationsGroupedByProduct map[string][]NotificationAlertWithDescription + +// NotificationAlertWithDescription represents the alert/notification +// available. +type NotificationAlertWithDescription struct { + DisplayName string `json:"display_name"` + Type string `json:"type"` + Description string `json:"description"` +} + +// NotificationAvailableAlertsResponse describes the available +// alerts/notifications grouped by products. +type NotificationAvailableAlertsResponse struct { + Response + Result NotificationsGroupedByProduct +} + +// NotificationHistory describes the history +// of notifications sent for an account. +type NotificationHistory struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + AlertBody string `json:"alert_body"` + AlertType string `json:"alert_type"` + Mechanism string `json:"mechanism"` + MechanismType string `json:"mechanism_type"` + Sent time.Time `json:"sent"` +} + +// NotificationHistoryResponse describes the notification history +// response for an account for a specific time period. +type NotificationHistoryResponse struct { + Response + ResultInfo `json:"result_info"` + Result []NotificationHistory +} + +// ListNotificationPolicies will return the notification policies +// created by a user for a specific account. +// +// API Reference: https://api.cloudflare.com/#notification-policies-properties +func (api *API) ListNotificationPolicies(ctx context.Context, accountID string) (NotificationPoliciesResponse, error) { + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/policies", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil) + if err != nil { + return NotificationPoliciesResponse{}, err + } + var r NotificationPoliciesResponse + err = json.Unmarshal(res, &r) + if err != nil { + return r, err + } + return r, nil +} + +// GetNotificationPolicy returns a specific created by a user, given the account +// id and the policy id. +// +// API Reference: https://api.cloudflare.com/#notification-policies-properties +func (api *API) GetNotificationPolicy(ctx context.Context, accountID, policyID string) (NotificationPolicyResponse, error) { + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/policies/%s", accountID, policyID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil) + if err != nil { + return NotificationPolicyResponse{}, err + } + var r NotificationPolicyResponse + err = json.Unmarshal(res, &r) + if err != nil { + return r, err + } + return r, nil +} + +// CreateNotificationPolicy creates a notification policy for an account. +// +// API Reference: https://api.cloudflare.com/#notification-policies-create-notification-policy +func (api *API) CreateNotificationPolicy(ctx context.Context, accountID string, policy NotificationPolicy) (SaveResponse, error) { + + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/policies", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, baseURL, policy) + if err != nil { + return SaveResponse{}, err + } + return unmarshalNotificationSaveResponse(res) +} + +// UpdateNotificationPolicy updates a notification policy, given the +// account id and the policy id and returns the policy id. +// +// API Reference: https://api.cloudflare.com/#notification-policies-update-notification-policy +func (api *API) UpdateNotificationPolicy(ctx context.Context, accountID string, policy *NotificationPolicy) (SaveResponse, error) { + if policy == nil { + return SaveResponse{}, fmt.Errorf("policy cannot be nil") + } + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/policies/%s", accountID, policy.ID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, baseURL, policy) + if err != nil { + return SaveResponse{}, err + } + return unmarshalNotificationSaveResponse(res) +} + +// DeleteNotificationPolicy deletes a notification policy for an account. +// +// API Reference: https://api.cloudflare.com/#notification-policies-delete-notification-policy +func (api *API) DeleteNotificationPolicy(ctx context.Context, accountID, policyID string) (SaveResponse, error) { + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/policies/%s", accountID, policyID) + + res, err := api.makeRequestContext(ctx, http.MethodDelete, baseURL, nil) + if err != nil { + return SaveResponse{}, err + } + return unmarshalNotificationSaveResponse(res) +} + +// ListNotificationWebhooks will return the webhook destinations configured +// for an account. +// +// API Reference: https://api.cloudflare.com/#notification-webhooks-list-webhooks +func (api *API) ListNotificationWebhooks(ctx context.Context, accountID string) (NotificationWebhooksResponse, error) { + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/webhooks", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil) + if err != nil { + return NotificationWebhooksResponse{}, err + } + var r NotificationWebhooksResponse + err = json.Unmarshal(res, &r) + if err != nil { + return r, err + } + return r, nil + +} + +// CreateNotificationWebhooks will help connect a webhooks destination. +// A test message will be sent to the webhooks endpoint during creation. +// If added successfully, the webhooks can be setup as a destination mechanism +// while creating policies. +// +// Notifications will be posted to this URL. +// +// API Reference: https://api.cloudflare.com/#notification-webhooks-create-webhook +func (api *API) CreateNotificationWebhooks(ctx context.Context, accountID string, webhooks *NotificationUpsertWebhooks) (SaveResponse, error) { + if webhooks == nil { + return SaveResponse{}, fmt.Errorf("webhooks cannot be nil") + } + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/webhooks", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, baseURL, webhooks) + if err != nil { + return SaveResponse{}, err + } + + return unmarshalNotificationSaveResponse(res) +} + +// GetNotificationWebhooks will return a specific webhook destination, +// given the account and webhooks ids. +// +// API Reference: https://api.cloudflare.com/#notification-webhooks-get-webhook +func (api *API) GetNotificationWebhooks(ctx context.Context, accountID, webhookID string) (NotificationWebhookResponse, error) { + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/webhooks/%s", accountID, webhookID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil) + if err != nil { + return NotificationWebhookResponse{}, err + } + var r NotificationWebhookResponse + err = json.Unmarshal(res, &r) + if err != nil { + return r, err + } + return r, nil +} + +// UpdateNotificationWebhooks will update a particular webhook's name, +// given the account and webhooks ids. +// +// The webhook url and secret cannot be updated. +// +// API Reference: https://api.cloudflare.com/#notification-webhooks-update-webhook +func (api *API) UpdateNotificationWebhooks(ctx context.Context, accountID, webhookID string, webhooks *NotificationUpsertWebhooks) (SaveResponse, error) { + if webhooks == nil { + return SaveResponse{}, fmt.Errorf("webhooks cannot be nil") + } + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/webhooks/%s", accountID, webhookID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, baseURL, webhooks) + if err != nil { + return SaveResponse{}, err + } + + return unmarshalNotificationSaveResponse(res) +} + +// DeleteNotificationWebhooks will delete a webhook, given the account and +// webhooks ids. Deleting the webhooks will remove it from any connected +// notification policies. +// +// API Reference: https://api.cloudflare.com/#notification-webhooks-delete-webhook +func (api *API) DeleteNotificationWebhooks(ctx context.Context, accountID, webhookID string) (SaveResponse, error) { + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/webhooks/%s", accountID, webhookID) + + res, err := api.makeRequestContext(ctx, http.MethodDelete, baseURL, nil) + if err != nil { + return SaveResponse{}, err + } + + return unmarshalNotificationSaveResponse(res) +} + +// ListPagerDutyNotificationDestinations will return the pagerduty +// destinations configured for an account. +// +// API Reference: https://api.cloudflare.com/#notification-destinations-with-pagerduty-list-pagerduty-services +func (api *API) ListPagerDutyNotificationDestinations(ctx context.Context, accountID string) (NotificationPagerDutyResponse, error) { + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/pagerduty", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil) + if err != nil { + return NotificationPagerDutyResponse{}, err + } + var r NotificationPagerDutyResponse + err = json.Unmarshal(res, &r) + if err != nil { + return r, err + } + return r, nil +} + +// GetEligibleNotificationDestinations will return the types of +// destinations an account is eligible to configure. +// +// API Reference: https://api.cloudflare.com/#notification-mechanism-eligibility-properties +func (api *API) GetEligibleNotificationDestinations(ctx context.Context, accountID string) (NotificationEligibilityResponse, error) { + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/destinations/eligible", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil) + if err != nil { + return NotificationEligibilityResponse{}, err + } + var r NotificationEligibilityResponse + err = json.Unmarshal(res, &r) + if err != nil { + return r, err + } + return r, nil +} + +// GetAvailableNotificationTypes will return the alert types available for +// a given account. +// +// API Reference: https://api.cloudflare.com/#notification-mechanism-eligibility-properties +func (api *API) GetAvailableNotificationTypes(ctx context.Context, accountID string) (NotificationAvailableAlertsResponse, error) { + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/available_alerts", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil) + if err != nil { + return NotificationAvailableAlertsResponse{}, err + } + var r NotificationAvailableAlertsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return r, err + } + return r, nil +} + +// ListNotificationHistory will return the history of alerts sent for +// a given account. The time period varies based on zone plan. +// Free, Biz, Pro = 30 days +// Ent = 90 days +// +// API Reference: https://api.cloudflare.com/#notification-history-list-history +func (api *API) ListNotificationHistory(ctx context.Context, accountID string, pageOpts PaginationOptions) ([]NotificationHistory, ResultInfo, error) { + v := url.Values{} + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + baseURL := fmt.Sprintf("/accounts/%s/alerting/v3/history", accountID) + if len(v) > 0 { + baseURL = fmt.Sprintf("%s?%s", baseURL, v.Encode()) + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, baseURL, nil) + if err != nil { + return []NotificationHistory{}, ResultInfo{}, err + } + var r NotificationHistoryResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []NotificationHistory{}, ResultInfo{}, err + } + return r.Result, r.ResultInfo, nil +} + +// unmarshal will unmarshal bytes and return a SaveResponse +func unmarshalNotificationSaveResponse(res []byte) (SaveResponse, error) { + var r SaveResponse + err := json.Unmarshal(res, &r) + if err != nil { + return r, err + } + return r, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/options.go b/vendor/github.com/cloudflare/cloudflare-go/options.go new file mode 100644 index 0000000000000..35f10f3bb845c --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/options.go @@ -0,0 +1,109 @@ +package cloudflare + +import ( + "net/http" + + "time" + + "golang.org/x/time/rate" +) + +// Option is a functional option for configuring the API client. +type Option func(*API) error + +// HTTPClient accepts a custom *http.Client for making API calls. +func HTTPClient(client *http.Client) Option { + return func(api *API) error { + api.httpClient = client + return nil + } +} + +// Headers allows you to set custom HTTP headers when making API calls (e.g. for +// satisfying HTTP proxies, or for debugging). +func Headers(headers http.Header) Option { + return func(api *API) error { + api.headers = headers + return nil + } +} + +// UsingAccount allows you to apply account-level changes (Load Balancing, +// Railguns) to an account instead. +func UsingAccount(accountID string) Option { + return func(api *API) error { + api.AccountID = accountID + return nil + } +} + +// UsingRateLimit applies a non-default rate limit to client API requests +// If not specified the default of 4rps will be applied +func UsingRateLimit(rps float64) Option { + return func(api *API) error { + // because ratelimiter doesnt do any windowing + // setting burst makes it difficult to enforce a fixed rate + // so setting it equal to 1 this effectively disables bursting + // this doesn't check for sensible values, ultimately the api will enforce that the value is ok + api.rateLimiter = rate.NewLimiter(rate.Limit(rps), 1) + return nil + } +} + +// UsingRetryPolicy applies a non-default number of retries and min/max retry delays +// This will be used when the client exponentially backs off after errored requests +func UsingRetryPolicy(maxRetries int, minRetryDelaySecs int, maxRetryDelaySecs int) Option { + // seconds is very granular for a minimum delay - but this is only in case of failure + return func(api *API) error { + api.retryPolicy = RetryPolicy{ + MaxRetries: maxRetries, + MinRetryDelay: time.Duration(minRetryDelaySecs) * time.Second, + MaxRetryDelay: time.Duration(maxRetryDelaySecs) * time.Second, + } + return nil + } +} + +// UsingLogger can be set if you want to get log output from this API instance +// By default no log output is emitted +func UsingLogger(logger Logger) Option { + return func(api *API) error { + api.logger = logger + return nil + } +} + +// UserAgent can be set if you want to send a software name and version for HTTP access logs. +// It is recommended to set it in order to help future Customer Support diagnostics +// and prevent collateral damage by sharing generic User-Agent string with abusive users. +// E.g. "my-software/1.2.3". By default generic Go User-Agent is used. +func UserAgent(userAgent string) Option { + return func(api *API) error { + api.UserAgent = userAgent + return nil + } +} + +// BaseURL allows you to override the default HTTP base URL used for API calls. +func BaseURL(baseURL string) Option { + return func(api *API) error { + api.BaseURL = baseURL + return nil + } +} + +// parseOptions parses the supplied options functions and returns a configured +// *API instance. +func (api *API) parseOptions(opts ...Option) error { + // Range over each options function and apply it to our API type to + // configure it. Options functions are applied in order, with any + // conflicting options overriding earlier calls. + for _, option := range opts { + err := option(api) + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/origin_ca.go b/vendor/github.com/cloudflare/cloudflare-go/origin_ca.go new file mode 100644 index 0000000000000..4d6181d995d50 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/origin_ca.go @@ -0,0 +1,230 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "time" + + "github.com/pkg/errors" +) + +// OriginCACertificate represents a Cloudflare-issued certificate. +// +// API reference: https://api.cloudflare.com/#cloudflare-ca +type OriginCACertificate struct { + ID string `json:"id"` + Certificate string `json:"certificate"` + Hostnames []string `json:"hostnames"` + ExpiresOn time.Time `json:"expires_on"` + RequestType string `json:"request_type"` + RequestValidity int `json:"requested_validity"` + RevokedAt time.Time `json:"revoked_at,omitempty"` + CSR string `json:"csr"` +} + +// UnmarshalJSON handles custom parsing from an API response to an OriginCACertificate +// http://choly.ca/post/go-json-marshalling/ +func (c *OriginCACertificate) UnmarshalJSON(data []byte) error { + type alias OriginCACertificate + + aux := &struct { + ExpiresOn string `json:"expires_on"` + *alias + }{ + alias: (*alias)(c), + } + + var err error + + if err = json.Unmarshal(data, &aux); err != nil { + return err + } + + // This format comes from time.Time.String() source + c.ExpiresOn, err = time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", aux.ExpiresOn) + + if err != nil { + c.ExpiresOn, err = time.Parse(time.RFC3339, aux.ExpiresOn) + } + + if err != nil { + return err + } + + return nil +} + +// OriginCACertificateListOptions represents the parameters used to list Cloudflare-issued certificates. +type OriginCACertificateListOptions struct { + ZoneID string +} + +// OriginCACertificateID represents the ID of the revoked certificate from the Revoke Certificate endpoint. +type OriginCACertificateID struct { + ID string `json:"id"` +} + +// originCACertificateResponse represents the response from the Create Certificate and the Certificate Details endpoints. +type originCACertificateResponse struct { + Response + Result OriginCACertificate `json:"result"` +} + +// originCACertificateResponseList represents the response from the List Certificates endpoint. +type originCACertificateResponseList struct { + Response + Result []OriginCACertificate `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// originCACertificateResponseRevoke represents the response from the Revoke Certificate endpoint. +type originCACertificateResponseRevoke struct { + Response + Result OriginCACertificateID `json:"result"` +} + +// CreateOriginCertificate creates a Cloudflare-signed certificate. +// +// This function requires api.APIUserServiceKey be set to your Certificates API key. +// +// API reference: https://api.cloudflare.com/#cloudflare-ca-create-certificate +func (api *API) CreateOriginCertificate(ctx context.Context, certificate OriginCACertificate) (*OriginCACertificate, error) { + uri := "/certificates" + res, err := api.makeRequestWithAuthType(ctx, http.MethodPost, uri, certificate, AuthUserService) + + if err != nil { + return nil, err + } + + var originResponse *originCACertificateResponse + + err = json.Unmarshal(res, &originResponse) + + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + if !originResponse.Success { + return nil, errors.New(errRequestNotSuccessful) + } + + return &originResponse.Result, nil +} + +// OriginCertificates lists all Cloudflare-issued certificates. +// +// This function requires api.APIUserServiceKey be set to your Certificates API key. +// +// API reference: https://api.cloudflare.com/#cloudflare-ca-list-certificates +func (api *API) OriginCertificates(ctx context.Context, options OriginCACertificateListOptions) ([]OriginCACertificate, error) { + v := url.Values{} + if options.ZoneID != "" { + v.Set("zone_id", options.ZoneID) + } + uri := fmt.Sprintf("/certificates?%s", v.Encode()) + res, err := api.makeRequestWithAuthType(ctx, http.MethodGet, uri, nil, AuthUserService) + + if err != nil { + return nil, err + } + + var originResponse *originCACertificateResponseList + + err = json.Unmarshal(res, &originResponse) + + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + if !originResponse.Success { + return nil, errors.New(errRequestNotSuccessful) + } + + return originResponse.Result, nil +} + +// OriginCertificate returns the details for a Cloudflare-issued certificate. +// +// This function requires api.APIUserServiceKey be set to your Certificates API key. +// +// API reference: https://api.cloudflare.com/#cloudflare-ca-certificate-details +func (api *API) OriginCertificate(ctx context.Context, certificateID string) (*OriginCACertificate, error) { + uri := fmt.Sprintf("/certificates/%s", certificateID) + res, err := api.makeRequestWithAuthType(ctx, http.MethodGet, uri, nil, AuthUserService) + + if err != nil { + return nil, err + } + + var originResponse *originCACertificateResponse + + err = json.Unmarshal(res, &originResponse) + + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + if !originResponse.Success { + return nil, errors.New(errRequestNotSuccessful) + } + + return &originResponse.Result, nil +} + +// RevokeOriginCertificate revokes a created certificate for a zone. +// +// This function requires api.APIUserServiceKey be set to your Certificates API key. +// +// API reference: https://api.cloudflare.com/#cloudflare-ca-revoke-certificate +func (api *API) RevokeOriginCertificate(ctx context.Context, certificateID string) (*OriginCACertificateID, error) { + uri := fmt.Sprintf("/certificates/%s", certificateID) + res, err := api.makeRequestWithAuthType(ctx, http.MethodDelete, uri, nil, AuthUserService) + + if err != nil { + return nil, err + } + + var originResponse *originCACertificateResponseRevoke + + err = json.Unmarshal(res, &originResponse) + + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + if !originResponse.Success { + return nil, errors.New(errRequestNotSuccessful) + } + + return &originResponse.Result, nil +} + +// Gets the Cloudflare Origin CA Root Certificate for a given algorithm in PEM format. +// Algorithm must be one of ['ecc', 'rsa']. +func OriginCARootCertificate(algorithm string) ([]byte, error) { + var url string + switch algorithm { + case "ecc": + url = originCARootCertEccURL + case "rsa": + url = originCARootCertRsaURL + default: + return nil, fmt.Errorf("invalid algorithm: must be one of ['ecc', 'rsa']") + } + + resp, err := http.Get(url) + if err != nil { + return nil, errors.Wrap(err, "HTTP request failed") + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrap(err, "Response body could not be read") + } + + return body, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/page_rules.go b/vendor/github.com/cloudflare/cloudflare-go/page_rules.go new file mode 100644 index 0000000000000..f345b646efe32 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/page_rules.go @@ -0,0 +1,240 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// PageRuleTarget is the target to evaluate on a request. +// +// Currently Target must always be "url" and Operator must be "matches". Value +// is the URL pattern to match against. +type PageRuleTarget struct { + Target string `json:"target"` + Constraint struct { + Operator string `json:"operator"` + Value string `json:"value"` + } `json:"constraint"` +} + +/* +PageRuleAction is the action to take when the target is matched. + +Valid IDs are: + always_online + always_use_https + automatic_https_rewrites + browser_cache_ttl + browser_check + bypass_cache_on_cookie + cache_by_device_type + cache_deception_armor + cache_level + cache_key_fields + cache_on_cookie + disable_apps + disable_performance + disable_railgun + disable_security + edge_cache_ttl + email_obfuscation + explicit_cache_control + forwarding_url + host_header_override + ip_geolocation + minify + mirage + opportunistic_encryption + origin_error_page_pass_thru + polish + resolve_override + respect_strong_etag + response_buffering + rocket_loader + security_level + server_side_exclude + sort_query_string_for_cache + ssl + true_client_ip_header + waf +*/ +type PageRuleAction struct { + ID string `json:"id"` + Value interface{} `json:"value"` +} + +// PageRuleActions maps API action IDs to human-readable strings. +var PageRuleActions = map[string]string{ + "always_online": "Always Online", // Value of type string + "always_use_https": "Always Use HTTPS", // Value of type interface{} + "automatic_https_rewrites": "Automatic HTTPS Rewrites", // Value of type string + "browser_cache_ttl": "Browser Cache TTL", // Value of type int + "browser_check": "Browser Integrity Check", // Value of type string + "bypass_cache_on_cookie": "Bypass Cache on Cookie", // Value of type string + "cache_by_device_type": "Cache By Device Type", // Value of type string + "cache_deception_armor": "Cache Deception Armor", // Value of type string + "cache_level": "Cache Level", // Value of type string + "cache_key_fields": "Custom Cache Key", // Value of type map[string]interface + "cache_on_cookie": "Cache On Cookie", // Value of type string + "disable_apps": "Disable Apps", // Value of type interface{} + "disable_performance": "Disable Performance", // Value of type interface{} + "disable_railgun": "Disable Railgun", // Value of type string + "disable_security": "Disable Security", // Value of type interface{} + "edge_cache_ttl": "Edge Cache TTL", // Value of type int + "email_obfuscation": "Email Obfuscation", // Value of type string + "explicit_cache_control": "Origin Cache Control", // Value of type string + "forwarding_url": "Forwarding URL", // Value of type map[string]interface + "host_header_override": "Host Header Override", // Value of type string + "ip_geolocation": "IP Geolocation Header", // Value of type string + "minify": "Minify", // Value of type map[string]interface + "mirage": "Mirage", // Value of type string + "opportunistic_encryption": "Opportunistic Encryption", // Value of type string + "origin_error_page_pass_thru": "Origin Error Page Pass-thru", // Value of type string + "polish": "Polish", // Value of type string + "resolve_override": "Resolve Override", // Value of type string + "respect_strong_etag": "Respect Strong ETags", // Value of type string + "response_buffering": "Response Buffering", // Value of type string + "rocket_loader": "Rocker Loader", // Value of type string + "security_level": "Security Level", // Value of type string + "server_side_exclude": "Server Side Excludes", // Value of type string + "sort_query_string_for_cache": "Query String Sort", // Value of type string + "ssl": "SSL", // Value of type string + "true_client_ip_header": "True Client IP Header", // Value of type string + "waf": "Web Application Firewall", // Value of type string +} + +// PageRule describes a Page Rule. +type PageRule struct { + ID string `json:"id,omitempty"` + Targets []PageRuleTarget `json:"targets"` + Actions []PageRuleAction `json:"actions"` + Priority int `json:"priority"` + Status string `json:"status"` + ModifiedOn time.Time `json:"modified_on,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty"` +} + +// PageRuleDetailResponse is the API response, containing a single PageRule. +type PageRuleDetailResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result PageRule `json:"result"` +} + +// PageRulesResponse is the API response, containing an array of PageRules. +type PageRulesResponse struct { + Success bool `json:"success"` + Errors []string `json:"errors"` + Messages []string `json:"messages"` + Result []PageRule `json:"result"` +} + +// CreatePageRule creates a new Page Rule for a zone. +// +// API reference: https://api.cloudflare.com/#page-rules-for-a-zone-create-a-page-rule +func (api *API) CreatePageRule(ctx context.Context, zoneID string, rule PageRule) (*PageRule, error) { + uri := fmt.Sprintf("/zones/%s/pagerules", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, rule) + if err != nil { + return nil, err + } + var r PageRuleDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return &r.Result, nil +} + +// ListPageRules returns all Page Rules for a zone. +// +// API reference: https://api.cloudflare.com/#page-rules-for-a-zone-list-page-rules +func (api *API) ListPageRules(ctx context.Context, zoneID string) ([]PageRule, error) { + uri := fmt.Sprintf("/zones/%s/pagerules", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []PageRule{}, err + } + var r PageRulesResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []PageRule{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// PageRule fetches detail about one Page Rule for a zone. +// +// API reference: https://api.cloudflare.com/#page-rules-for-a-zone-page-rule-details +func (api *API) PageRule(ctx context.Context, zoneID, ruleID string) (PageRule, error) { + uri := fmt.Sprintf("/zones/%s/pagerules/%s", zoneID, ruleID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return PageRule{}, err + } + var r PageRuleDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return PageRule{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ChangePageRule lets you change individual settings for a Page Rule. This is +// in contrast to UpdatePageRule which replaces the entire Page Rule. +// +// API reference: https://api.cloudflare.com/#page-rules-for-a-zone-change-a-page-rule +func (api *API) ChangePageRule(ctx context.Context, zoneID, ruleID string, rule PageRule) error { + uri := fmt.Sprintf("/zones/%s/pagerules/%s", zoneID, ruleID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, rule) + if err != nil { + return err + } + var r PageRuleDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +// UpdatePageRule lets you replace a Page Rule. This is in contrast to +// ChangePageRule which lets you change individual settings. +// +// API reference: https://api.cloudflare.com/#page-rules-for-a-zone-update-a-page-rule +func (api *API) UpdatePageRule(ctx context.Context, zoneID, ruleID string, rule PageRule) error { + uri := fmt.Sprintf("/zones/%s/pagerules/%s", zoneID, ruleID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, rule) + if err != nil { + return err + } + var r PageRuleDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} + +// DeletePageRule deletes a Page Rule for a zone. +// +// API reference: https://api.cloudflare.com/#page-rules-for-a-zone-delete-a-page-rule +func (api *API) DeletePageRule(ctx context.Context, zoneID, ruleID string) error { + uri := fmt.Sprintf("/zones/%s/pagerules/%s", zoneID, ruleID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + var r PageRuleDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/pages_project.go b/vendor/github.com/cloudflare/cloudflare-go/pages_project.go new file mode 100644 index 0000000000000..8a6604e642dc4 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/pages_project.go @@ -0,0 +1,220 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "time" + + "github.com/pkg/errors" +) + +// PagesProject represents a Pages project. +type PagesProject struct { + Name string `json:"name"` + ID string `json:"id"` + CreatedOn *time.Time `json:"created_on"` + SubDomain string `json:"subdomain"` + Domains []string `json:"domains,omitempty"` + Source PagesProjectSource `json:"source"` + BuildConfig PagesProjectBuildConfig `json:"build_config"` + DeploymentConfigs PagesProjectDeploymentConfigs `json:"deployment_configs"` + LatestDeployment PagesProjectDeployment `json:"latest_deployment"` + CanonicalDeployment PagesProjectDeployment `json:"canonical_deployment"` +} + +// PagesProjectSource represents the configuration of a Pages project source. +type PagesProjectSource struct { + Type string `json:"type"` + Config *PagesProjectSourceConfig `json:"config"` +} + +// PagesProjectSourceConfig represents the properties use to configure a Pages project source. +type PagesProjectSourceConfig struct { + Owner string `json:"owner"` + RepoName string `json:"repo_name"` + ProductionBranch string `json:"production_branch"` + PRCommentsEnabled bool `json:"pr_comments_enabled"` + DeploymentsEnabled bool `json:"deployments_enabled"` +} + +// PagesProjectBuildConfig represents the configuration of a Pages project build process. +type PagesProjectBuildConfig struct { + BuildCommand string `json:"build_command"` + DestinationDir string `json:"destination_dir"` + RootDir string `json:"root_dir"` + WebAnalyticsTag string `json:"web_analytics_tag"` + WebAnalyticsToken string `json:"web_analytics_token"` +} + +// PagesProjectDeploymentConfigs represents the configuration for deployments in a Pages project. +type PagesProjectDeploymentConfigs struct { + Preview PagesProjectDeploymentConfigEnvironment `json:"preview"` + Production PagesProjectDeploymentConfigEnvironment `json:"production"` +} + +// PagesProjectDeploymentConfigEnvironment represents the configuration for preview or production deploys. +type PagesProjectDeploymentConfigEnvironment struct { + EnvVars PagesProjectDeploymentConfigEnvVars `json:"env_vars"` +} + +// PagesProjectDeploymentConfigEnvVars represents the BUILD_VERSION environment variables for a specific build config. +type PagesProjectDeploymentConfigEnvVars struct { + BuildVersion PagesProjectDeploymentConfigBuildVersion `json:"BUILD_VERSION"` +} + +// PagesProjectDeploymentConfigBuildVersion represents a value for a BUILD_VERSION. +type PagesProjectDeploymentConfigBuildVersion struct { + Value string `json:"value"` +} + +// PagesProjectDeployment represents a deployment to a Pages project. +type PagesProjectDeployment struct { + ID string `json:"id"` + ShortID string `json:"short_id"` + ProjectID string `json:"project_id"` + ProjectName string `json:"project_name"` + Environment string `json:"environment"` + URL string `json:"url"` + CreatedOn *time.Time `json:"created_on"` + ModifiedOn *time.Time `json:"modified_on"` + Aliases []string `json:"aliases,omitempty"` + LatestStage PagesProjectDeploymentStage `json:"latest_stage"` + EnvVars map[string]map[string]string `json:"env_vars"` + DeploymentTrigger PagesProjectDeploymentTrigger `json:"deployment_trigger"` + Stages []PagesProjectDeploymentStage `json:"stages"` + BuildConfig PagesProjectBuildConfig `json:"build_config"` + Source PagesProjectSource `json:"source"` +} + +// PagesProjectDeploymentStage represents an individual stage in a Pages project deployment. +type PagesProjectDeploymentStage struct { + Name string `json:"name"` + StartedOn *time.Time `json:"started_on,omitempty"` + EndedOn *time.Time `json:"ended_on,omitempty"` + Status string `json:"status"` +} + +// PagesProjectDeploymentTrigger represents information about what caused a deployment. +type PagesProjectDeploymentTrigger struct { + Type string `json:"type"` + Metadata *PagesProjectDeploymentTriggerMetadata `json:"metadata"` +} + +// PagesProjectDeploymentTriggerMetadata represents additional information about the cause of a deployment. +type PagesProjectDeploymentTriggerMetadata struct { + Branch string `json:"branch"` + CommitHash string `json:"commit_hash"` + CommitMessage string `json:"commit_message"` +} + +type pagesProjectResponse struct { + Response + Result PagesProject `json:"result"` +} + +type pagesProjectListResponse struct { + Response + Result []PagesProject `json:"result"` + ResultInfo `json:"result_info"` +} + +// ListPagesProjects returns all Pages projects for an account. +// +// API reference: https://api.cloudflare.com/#pages-project-get-projects +func (api *API) ListPagesProjects(ctx context.Context, accountID string, pageOpts PaginationOptions) ([]PagesProject, ResultInfo, error) { + v := url.Values{} + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + uri := fmt.Sprintf("/accounts/%s/pages/projects", accountID) + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []PagesProject{}, ResultInfo{}, err + } + var r pagesProjectListResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []PagesProject{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, r.ResultInfo, nil +} + +// PagesProject returns a single Pages project by name. +// +// API reference: https://api.cloudflare.com/#pages-project-get-project +func (api *API) PagesProject(ctx context.Context, accountID, projectName string) (PagesProject, error) { + uri := fmt.Sprintf("/accounts/%s/pages/projects/%s", accountID, projectName) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return PagesProject{}, err + } + var r pagesProjectResponse + err = json.Unmarshal(res, &r) + if err != nil { + return PagesProject{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// CreatePagesProject creates a new Pages project in an account. +// +// API reference: https://api.cloudflare.com/#pages-project-create-project +func (api *API) CreatePagesProject(ctx context.Context, accountID string, pagesProject PagesProject) (PagesProject, error) { + uri := fmt.Sprintf("/accounts/%s/pages/projects", accountID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, pagesProject) + if err != nil { + return PagesProject{}, err + } + var r pagesProjectResponse + err = json.Unmarshal(res, &r) + if err != nil { + return PagesProject{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdatePagesProject updates an existing Pages project. +// +// API reference: https://api.cloudflare.com/#pages-project-update-project +func (api *API) UpdatePagesProject(ctx context.Context, accountID, projectName string, pagesProject PagesProject) (PagesProject, error) { + uri := fmt.Sprintf("/accounts/%s/pages/projects/%s", accountID, projectName) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, pagesProject) + if err != nil { + return PagesProject{}, err + } + var r pagesProjectResponse + err = json.Unmarshal(res, &r) + if err != nil { + return PagesProject{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeletePagesProject deletes a Pages project by name. +// +// API reference: https://api.cloudflare.com/#pages-project-delete-project +func (api *API) DeletePagesProject(ctx context.Context, accountID, projectName string) error { + uri := fmt.Sprintf("/accounts/%s/pages/projects/%s", accountID, projectName) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + var r pagesProjectResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/railgun.go b/vendor/github.com/cloudflare/cloudflare-go/railgun.go new file mode 100644 index 0000000000000..b85e0336f1ffb --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/railgun.go @@ -0,0 +1,300 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/pkg/errors" +) + +// Railgun represents a Railgun's properties. +type Railgun struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Enabled bool `json:"enabled"` + ZonesConnected int `json:"zones_connected"` + Build string `json:"build"` + Version string `json:"version"` + Revision string `json:"revision"` + ActivationKey string `json:"activation_key"` + ActivatedOn time.Time `json:"activated_on"` + CreatedOn time.Time `json:"created_on"` + ModifiedOn time.Time `json:"modified_on"` + UpgradeInfo struct { + LatestVersion string `json:"latest_version"` + DownloadLink string `json:"download_link"` + } `json:"upgrade_info"` +} + +// RailgunListOptions represents the parameters used to list railguns. +type RailgunListOptions struct { + Direction string +} + +// railgunResponse represents the response from the Create Railgun and the Railgun Details endpoints. +type railgunResponse struct { + Response + Result Railgun `json:"result"` +} + +// railgunsResponse represents the response from the List Railguns endpoint. +type railgunsResponse struct { + Response + Result []Railgun `json:"result"` +} + +// CreateRailgun creates a new Railgun. +// +// API reference: https://api.cloudflare.com/#railgun-create-railgun +func (api *API) CreateRailgun(ctx context.Context, name string) (Railgun, error) { + uri := fmt.Sprintf("%s/railguns", api.userBaseURL("")) + params := struct { + Name string `json:"name"` + }{ + Name: name, + } + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params) + if err != nil { + return Railgun{}, err + } + var r railgunResponse + if err := json.Unmarshal(res, &r); err != nil { + return Railgun{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListRailguns lists Railguns connected to an account. +// +// API reference: https://api.cloudflare.com/#railgun-list-railguns +func (api *API) ListRailguns(ctx context.Context, options RailgunListOptions) ([]Railgun, error) { + v := url.Values{} + if options.Direction != "" { + v.Set("direction", options.Direction) + } + uri := fmt.Sprintf("%s/railguns?%s", api.userBaseURL(""), v.Encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + var r railgunsResponse + if err := json.Unmarshal(res, &r); err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// RailgunDetails returns the details for a Railgun. +// +// API reference: https://api.cloudflare.com/#railgun-railgun-details +func (api *API) RailgunDetails(ctx context.Context, railgunID string) (Railgun, error) { + uri := fmt.Sprintf("%s/railguns/%s", api.userBaseURL(""), railgunID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return Railgun{}, err + } + var r railgunResponse + if err := json.Unmarshal(res, &r); err != nil { + return Railgun{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// RailgunZones returns the zones that are currently using a Railgun. +// +// API reference: https://api.cloudflare.com/#railgun-get-zones-connected-to-a-railgun +func (api *API) RailgunZones(ctx context.Context, railgunID string) ([]Zone, error) { + uri := fmt.Sprintf("%s/railguns/%s/zones", api.userBaseURL(""), railgunID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + var r ZonesResponse + if err := json.Unmarshal(res, &r); err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// enableRailgun enables (true) or disables (false) a Railgun for all zones connected to it. +// +// API reference: https://api.cloudflare.com/#railgun-enable-or-disable-a-railgun +func (api *API) enableRailgun(ctx context.Context, railgunID string, enable bool) (Railgun, error) { + uri := fmt.Sprintf("%s/railguns/%s", api.userBaseURL(""), railgunID) + params := struct { + Enabled bool `json:"enabled"` + }{ + Enabled: enable, + } + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params) + if err != nil { + return Railgun{}, err + } + var r railgunResponse + if err := json.Unmarshal(res, &r); err != nil { + return Railgun{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// EnableRailgun enables a Railgun for all zones connected to it. +// +// API reference: https://api.cloudflare.com/#railgun-enable-or-disable-a-railgun +func (api *API) EnableRailgun(ctx context.Context, railgunID string) (Railgun, error) { + return api.enableRailgun(ctx, railgunID, true) +} + +// DisableRailgun enables a Railgun for all zones connected to it. +// +// API reference: https://api.cloudflare.com/#railgun-enable-or-disable-a-railgun +func (api *API) DisableRailgun(ctx context.Context, railgunID string) (Railgun, error) { + return api.enableRailgun(ctx, railgunID, false) +} + +// DeleteRailgun disables and deletes a Railgun. +// +// API reference: https://api.cloudflare.com/#railgun-delete-railgun +func (api *API) DeleteRailgun(ctx context.Context, railgunID string) error { + uri := fmt.Sprintf("%s/railguns/%s", api.userBaseURL(""), railgunID) + if _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil); err != nil { + return err + } + return nil +} + +// ZoneRailgun represents the status of a Railgun on a zone. +type ZoneRailgun struct { + ID string `json:"id"` + Name string `json:"name"` + Enabled bool `json:"enabled"` + Connected bool `json:"connected"` +} + +// zoneRailgunResponse represents the response from the Zone Railgun Details endpoint. +type zoneRailgunResponse struct { + Response + Result ZoneRailgun `json:"result"` +} + +// zoneRailgunsResponse represents the response from the Zone Railgun endpoint. +type zoneRailgunsResponse struct { + Response + Result []ZoneRailgun `json:"result"` +} + +// RailgunDiagnosis represents the test results from testing railgun connections +// to a zone. +type RailgunDiagnosis struct { + Method string `json:"method"` + HostName string `json:"host_name"` + HTTPStatus int `json:"http_status"` + Railgun string `json:"railgun"` + URL string `json:"url"` + ResponseStatus string `json:"response_status"` + Protocol string `json:"protocol"` + ElapsedTime string `json:"elapsed_time"` + BodySize string `json:"body_size"` + BodyHash string `json:"body_hash"` + MissingHeaders string `json:"missing_headers"` + ConnectionClose bool `json:"connection_close"` + Cloudflare string `json:"cloudflare"` + CFRay string `json:"cf-ray"` + // NOTE: Cloudflare's online API documentation does not yet have definitions + // for the following fields. See: https://api.cloudflare.com/#railgun-connections-for-a-zone-test-railgun-connection/ + CFWANError string `json:"cf-wan-error"` + CFCacheStatus string `json:"cf-cache-status"` +} + +// railgunDiagnosisResponse represents the response from the Test Railgun Connection endpoint. +type railgunDiagnosisResponse struct { + Response + Result RailgunDiagnosis `json:"result"` +} + +// ZoneRailguns returns the available Railguns for a zone. +// +// API reference: https://api.cloudflare.com/#railguns-for-a-zone-get-available-railguns +func (api *API) ZoneRailguns(ctx context.Context, zoneID string) ([]ZoneRailgun, error) { + uri := fmt.Sprintf("/zones/%s/railguns", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + var r zoneRailgunsResponse + if err := json.Unmarshal(res, &r); err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ZoneRailgunDetails returns the configuration for a given Railgun. +// +// API reference: https://api.cloudflare.com/#railguns-for-a-zone-get-railgun-details +func (api *API) ZoneRailgunDetails(ctx context.Context, zoneID, railgunID string) (ZoneRailgun, error) { + uri := fmt.Sprintf("/zones/%s/railguns/%s", zoneID, railgunID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return ZoneRailgun{}, err + } + var r zoneRailgunResponse + if err := json.Unmarshal(res, &r); err != nil { + return ZoneRailgun{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// TestRailgunConnection tests a Railgun connection for a given zone. +// +// API reference: https://api.cloudflare.com/#railgun-connections-for-a-zone-test-railgun-connection +func (api *API) TestRailgunConnection(ctx context.Context, zoneID, railgunID string) (RailgunDiagnosis, error) { + uri := fmt.Sprintf("/zones/%s/railguns/%s/diagnose", zoneID, railgunID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return RailgunDiagnosis{}, err + } + var r railgunDiagnosisResponse + if err := json.Unmarshal(res, &r); err != nil { + return RailgunDiagnosis{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// connectZoneRailgun connects (true) or disconnects (false) a Railgun for a given zone. +// +// API reference: https://api.cloudflare.com/#railguns-for-a-zone-connect-or-disconnect-a-railgun +func (api *API) connectZoneRailgun(ctx context.Context, zoneID, railgunID string, connect bool) (ZoneRailgun, error) { + uri := fmt.Sprintf("/zones/%s/railguns/%s", zoneID, railgunID) + params := struct { + Connected bool `json:"connected"` + }{ + Connected: connect, + } + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params) + if err != nil { + return ZoneRailgun{}, err + } + var r zoneRailgunResponse + if err := json.Unmarshal(res, &r); err != nil { + return ZoneRailgun{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ConnectZoneRailgun connects a Railgun for a given zone. +// +// API reference: https://api.cloudflare.com/#railguns-for-a-zone-connect-or-disconnect-a-railgun +func (api *API) ConnectZoneRailgun(ctx context.Context, zoneID, railgunID string) (ZoneRailgun, error) { + return api.connectZoneRailgun(ctx, zoneID, railgunID, true) +} + +// DisconnectZoneRailgun disconnects a Railgun for a given zone. +// +// API reference: https://api.cloudflare.com/#railguns-for-a-zone-connect-or-disconnect-a-railgun +func (api *API) DisconnectZoneRailgun(ctx context.Context, zoneID, railgunID string) (ZoneRailgun, error) { + return api.connectZoneRailgun(ctx, zoneID, railgunID, false) +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/rate_limiting.go b/vendor/github.com/cloudflare/cloudflare-go/rate_limiting.go new file mode 100644 index 0000000000000..d0be433dc2d94 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/rate_limiting.go @@ -0,0 +1,213 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + + "github.com/pkg/errors" +) + +// RateLimit is a policy than can be applied to limit traffic within a customer domain +type RateLimit struct { + ID string `json:"id,omitempty"` + Disabled bool `json:"disabled,omitempty"` + Description string `json:"description,omitempty"` + Match RateLimitTrafficMatcher `json:"match"` + Bypass []RateLimitKeyValue `json:"bypass,omitempty"` + Threshold int `json:"threshold"` + Period int `json:"period"` + Action RateLimitAction `json:"action"` + Correlate *RateLimitCorrelate `json:"correlate,omitempty"` +} + +// RateLimitTrafficMatcher contains the rules that will be used to apply a rate limit to traffic +type RateLimitTrafficMatcher struct { + Request RateLimitRequestMatcher `json:"request"` + Response RateLimitResponseMatcher `json:"response"` +} + +// RateLimitRequestMatcher contains the matching rules pertaining to requests +type RateLimitRequestMatcher struct { + Methods []string `json:"methods,omitempty"` + Schemes []string `json:"schemes,omitempty"` + URLPattern string `json:"url,omitempty"` +} + +// RateLimitResponseMatcher contains the matching rules pertaining to responses +type RateLimitResponseMatcher struct { + Statuses []int `json:"status,omitempty"` + OriginTraffic *bool `json:"origin_traffic,omitempty"` // api defaults to true so we need an explicit empty value + Headers []RateLimitResponseMatcherHeader `json:"headers,omitempty"` +} + +// RateLimitResponseMatcherHeader contains the structure of the origin +// HTTP headers used in request matcher checks. +type RateLimitResponseMatcherHeader struct { + Name string `json:"name"` + Op string `json:"op"` + Value string `json:"value"` +} + +// RateLimitKeyValue is k-v formatted as expected in the rate limit description +type RateLimitKeyValue struct { + Name string `json:"name"` + Value string `json:"value"` +} + +// RateLimitAction is the action that will be taken when the rate limit threshold is reached +type RateLimitAction struct { + Mode string `json:"mode"` + Timeout int `json:"timeout"` + Response *RateLimitActionResponse `json:"response"` +} + +// RateLimitActionResponse is the response that will be returned when rate limit action is triggered +type RateLimitActionResponse struct { + ContentType string `json:"content_type"` + Body string `json:"body"` +} + +// RateLimitCorrelate pertainings to NAT support +type RateLimitCorrelate struct { + By string `json:"by"` +} + +type rateLimitResponse struct { + Response + Result RateLimit `json:"result"` +} + +type rateLimitListResponse struct { + Response + Result []RateLimit `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// CreateRateLimit creates a new rate limit for a zone. +// +// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-create-a-ratelimit +func (api *API) CreateRateLimit(ctx context.Context, zoneID string, limit RateLimit) (RateLimit, error) { + uri := fmt.Sprintf("/zones/%s/rate_limits", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, limit) + if err != nil { + return RateLimit{}, err + } + var r rateLimitResponse + if err := json.Unmarshal(res, &r); err != nil { + return RateLimit{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListRateLimits returns Rate Limits for a zone, paginated according to the provided options +// +// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-list-rate-limits +func (api *API) ListRateLimits(ctx context.Context, zoneID string, pageOpts PaginationOptions) ([]RateLimit, ResultInfo, error) { + v := url.Values{} + if pageOpts.PerPage > 0 { + v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) + } + if pageOpts.Page > 0 { + v.Set("page", strconv.Itoa(pageOpts.Page)) + } + + uri := fmt.Sprintf("/zones/%s/rate_limits", zoneID) + if len(v) > 0 { + uri = fmt.Sprintf("%s?%s", uri, v.Encode()) + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []RateLimit{}, ResultInfo{}, err + } + + var r rateLimitListResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []RateLimit{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, r.ResultInfo, nil +} + +// ListAllRateLimits returns all Rate Limits for a zone. +// +// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-list-rate-limits +func (api *API) ListAllRateLimits(ctx context.Context, zoneID string) ([]RateLimit, error) { + pageOpts := PaginationOptions{ + PerPage: 100, // this is the max page size allowed + Page: 1, + } + + allRateLimits := make([]RateLimit, 0) + for { + rateLimits, resultInfo, err := api.ListRateLimits(ctx, zoneID, pageOpts) + if err != nil { + return []RateLimit{}, err + } + allRateLimits = append(allRateLimits, rateLimits...) + // total pages is not returned on this call + // if number of records is less than the max, this must be the last page + // in case TotalCount % PerPage = 0, the last request will return an empty list + if resultInfo.Count < resultInfo.PerPage { + break + } + // continue with the next page + pageOpts.Page = pageOpts.Page + 1 + } + + return allRateLimits, nil +} + +// RateLimit fetches detail about one Rate Limit for a zone. +// +// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-rate-limit-details +func (api *API) RateLimit(ctx context.Context, zoneID, limitID string) (RateLimit, error) { + uri := fmt.Sprintf("/zones/%s/rate_limits/%s", zoneID, limitID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return RateLimit{}, err + } + var r rateLimitResponse + err = json.Unmarshal(res, &r) + if err != nil { + return RateLimit{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateRateLimit lets you replace a Rate Limit for a zone. +// +// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-update-rate-limit +func (api *API) UpdateRateLimit(ctx context.Context, zoneID, limitID string, limit RateLimit) (RateLimit, error) { + uri := fmt.Sprintf("/zones/%s/rate_limits/%s", zoneID, limitID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, limit) + if err != nil { + return RateLimit{}, err + } + var r rateLimitResponse + if err := json.Unmarshal(res, &r); err != nil { + return RateLimit{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeleteRateLimit deletes a Rate Limit for a zone. +// +// API reference: https://api.cloudflare.com/#rate-limits-for-a-zone-delete-rate-limit +func (api *API) DeleteRateLimit(ctx context.Context, zoneID, limitID string) error { + uri := fmt.Sprintf("/zones/%s/rate_limits/%s", zoneID, limitID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + var r rateLimitResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/registrar.go b/vendor/github.com/cloudflare/cloudflare-go/registrar.go new file mode 100644 index 0000000000000..9f3aa784cb151 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/registrar.go @@ -0,0 +1,177 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// RegistrarDomain is the structure of the API response for a new +// Cloudflare Registrar domain. +type RegistrarDomain struct { + ID string `json:"id"` + Available bool `json:"available"` + SupportedTLD bool `json:"supported_tld"` + CanRegister bool `json:"can_register"` + TransferIn RegistrarTransferIn `json:"transfer_in"` + CurrentRegistrar string `json:"current_registrar"` + ExpiresAt time.Time `json:"expires_at"` + RegistryStatuses string `json:"registry_statuses"` + Locked bool `json:"locked"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + RegistrantContact RegistrantContact `json:"registrant_contact"` +} + +// RegistrarTransferIn contains the structure for a domain transfer in +// request. +type RegistrarTransferIn struct { + UnlockDomain string `json:"unlock_domain"` + DisablePrivacy string `json:"disable_privacy"` + EnterAuthCode string `json:"enter_auth_code"` + ApproveTransfer string `json:"approve_transfer"` + AcceptFoa string `json:"accept_foa"` + CanCancelTransfer bool `json:"can_cancel_transfer"` +} + +// RegistrantContact is the contact details for the domain registration. +type RegistrantContact struct { + ID string `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Organization string `json:"organization"` + Address string `json:"address"` + Address2 string `json:"address2"` + City string `json:"city"` + State string `json:"state"` + Zip string `json:"zip"` + Country string `json:"country"` + Phone string `json:"phone"` + Email string `json:"email"` + Fax string `json:"fax"` +} + +// RegistrarDomainConfiguration is the structure for making updates to +// and existing domain. +type RegistrarDomainConfiguration struct { + NameServers []string `json:"name_servers"` + Privacy bool `json:"privacy"` + Locked bool `json:"locked"` + AutoRenew bool `json:"auto_renew"` +} + +// RegistrarDomainDetailResponse is the structure of the detailed +// response from the API for a single domain. +type RegistrarDomainDetailResponse struct { + Response + Result RegistrarDomain `json:"result"` +} + +// RegistrarDomainsDetailResponse is the structure of the detailed +// response from the API. +type RegistrarDomainsDetailResponse struct { + Response + Result []RegistrarDomain `json:"result"` +} + +// RegistrarDomain returns a single domain based on the account ID and +// domain name. +// +// API reference: https://api.cloudflare.com/#registrar-domains-get-domain +func (api *API) RegistrarDomain(ctx context.Context, accountID, domainName string) (RegistrarDomain, error) { + uri := fmt.Sprintf("/accounts/%s/registrar/domains/%s", accountID, domainName) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return RegistrarDomain{}, err + } + + var r RegistrarDomainDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return RegistrarDomain{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// RegistrarDomains returns all registrar domains based on the account +// ID. +// +// API reference: https://api.cloudflare.com/#registrar-domains-list-domains +func (api *API) RegistrarDomains(ctx context.Context, accountID string) ([]RegistrarDomain, error) { + uri := fmt.Sprintf("/accounts/%s/registrar/domains", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return []RegistrarDomain{}, err + } + + var r RegistrarDomainsDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []RegistrarDomain{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// TransferRegistrarDomain initiates the transfer from another registrar +// to Cloudflare Registrar. +// +// API reference: https://api.cloudflare.com/#registrar-domains-transfer-domain +func (api *API) TransferRegistrarDomain(ctx context.Context, accountID, domainName string) ([]RegistrarDomain, error) { + uri := fmt.Sprintf("/accounts/%s/registrar/domains/%s/transfer", accountID, domainName) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return []RegistrarDomain{}, err + } + + var r RegistrarDomainsDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []RegistrarDomain{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// CancelRegistrarDomainTransfer cancels a pending domain transfer. +// +// API reference: https://api.cloudflare.com/#registrar-domains-cancel-transfer +func (api *API) CancelRegistrarDomainTransfer(ctx context.Context, accountID, domainName string) ([]RegistrarDomain, error) { + uri := fmt.Sprintf("/accounts/%s/registrar/domains/%s/cancel_transfer", accountID, domainName) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil) + if err != nil { + return []RegistrarDomain{}, err + } + + var r RegistrarDomainsDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []RegistrarDomain{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateRegistrarDomain updates an existing Registrar Domain configuration. +// +// API reference: https://api.cloudflare.com/#registrar-domains-update-domain +func (api *API) UpdateRegistrarDomain(ctx context.Context, accountID, domainName string, domainConfiguration RegistrarDomainConfiguration) (RegistrarDomain, error) { + uri := fmt.Sprintf("/accounts/%s/registrar/domains/%s", accountID, domainName) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, domainConfiguration) + if err != nil { + return RegistrarDomain{}, err + } + + var r RegistrarDomainDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return RegistrarDomain{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/rulesets.go b/vendor/github.com/cloudflare/cloudflare-go/rulesets.go new file mode 100644 index 0000000000000..d1bdd526afe01 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/rulesets.go @@ -0,0 +1,514 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +const ( + RulesetKindCustom RulesetKind = "custom" + RulesetKindManaged RulesetKind = "managed" + RulesetKindRoot RulesetKind = "root" + RulesetKindSchema RulesetKind = "schema" + RulesetKindZone RulesetKind = "zone" + + RulesetPhaseDDoSL4 RulesetPhase = "ddos_l4" + RulesetPhaseDDoSL7 RulesetPhase = "ddos_l7" + RulesetPhaseHTTPRequestFirewallCustom RulesetPhase = "http_request_firewall_custom" + RulesetPhaseHTTPRequestFirewallManaged RulesetPhase = "http_request_firewall_managed" + RulesetPhaseHTTPRequestLateTransform RulesetPhase = "http_request_late_transform" + RulesetPhaseHTTPRequestMain RulesetPhase = "http_request_main" + RulesetPhaseHTTPRequestSanitize RulesetPhase = "http_request_sanitize" + RulesetPhaseHTTPRequestTransform RulesetPhase = "http_request_transform" + RulesetPhaseHTTPResponseFirewallManaged RulesetPhase = "http_response_firewall_managed" + RulesetPhaseMagicTransit RulesetPhase = "magic_transit" + RulesetPhaseRateLimit RulesetPhase = "http_ratelimit" + + RulesetRuleActionBlock RulesetRuleAction = "block" + RulesetRuleActionChallenge RulesetRuleAction = "challenge" + RulesetRuleActionDDoSDynamic RulesetRuleAction = "ddos_dynamic" + RulesetRuleActionExecute RulesetRuleAction = "execute" + RulesetRuleActionForceConnectionClose RulesetRuleAction = "force_connection_close" + RulesetRuleActionJSChallenge RulesetRuleAction = "js_challenge" + RulesetRuleActionLog RulesetRuleAction = "log" + RulesetRuleActionRewrite RulesetRuleAction = "rewrite" + RulesetRuleActionScore RulesetRuleAction = "score" + RulesetRuleActionSkip RulesetRuleAction = "skip" + + RulesetActionParameterProductBIC RulesetActionParameterProduct = "bic" + RulesetActionParameterProductHOT RulesetActionParameterProduct = "hot" + RulesetActionParameterProductRateLimit RulesetActionParameterProduct = "ratelimit" + RulesetActionParameterProductSecurityLevel RulesetActionParameterProduct = "securityLevel" + RulesetActionParameterProductUABlock RulesetActionParameterProduct = "uablock" + RulesetActionParameterProductWAF RulesetActionParameterProduct = "waf" + RulesetActionParameterProductZoneLockdown RulesetActionParameterProduct = "zonelockdown" + + RulesetRuleActionParametersHTTPHeaderOperationRemove RulesetRuleActionParametersHTTPHeaderOperation = "remove" + RulesetRuleActionParametersHTTPHeaderOperationSet RulesetRuleActionParametersHTTPHeaderOperation = "set" +) + +// RulesetKindValues exposes all the available `RulesetKind` values as a slice +// of strings. +func RulesetKindValues() []string { + return []string{ + string(RulesetKindCustom), + string(RulesetKindManaged), + string(RulesetKindRoot), + string(RulesetKindSchema), + string(RulesetKindZone), + } +} + +// RulesetPhaseValues exposes all the available `RulesetPhase` values as a slice +// of strings. +func RulesetPhaseValues() []string { + return []string{ + string(RulesetPhaseDDoSL4), + string(RulesetPhaseDDoSL7), + string(RulesetPhaseHTTPRequestFirewallCustom), + string(RulesetPhaseHTTPRequestFirewallManaged), + string(RulesetPhaseHTTPRequestLateTransform), + string(RulesetPhaseHTTPRequestMain), + string(RulesetPhaseHTTPRequestSanitize), + string(RulesetPhaseHTTPRequestTransform), + string(RulesetPhaseHTTPResponseFirewallManaged), + string(RulesetPhaseMagicTransit), + string(RulesetPhaseRateLimit), + } +} + +// RulesetRuleActionValues exposes all the available `RulesetRuleAction` values +// as a slice of strings. +func RulesetRuleActionValues() []string { + return []string{ + string(RulesetRuleActionBlock), + string(RulesetRuleActionChallenge), + string(RulesetRuleActionDDoSDynamic), + string(RulesetRuleActionExecute), + string(RulesetRuleActionForceConnectionClose), + string(RulesetRuleActionJSChallenge), + string(RulesetRuleActionLog), + string(RulesetRuleActionRewrite), + string(RulesetRuleActionScore), + string(RulesetRuleActionSkip), + } +} + +// RulesetActionParameterProductValues exposes all the available +// `RulesetActionParameterProduct` values as a slice of strings. +func RulesetActionParameterProductValues() []string { + return []string{ + string(RulesetActionParameterProductBIC), + string(RulesetActionParameterProductHOT), + string(RulesetActionParameterProductRateLimit), + string(RulesetActionParameterProductSecurityLevel), + string(RulesetActionParameterProductUABlock), + string(RulesetActionParameterProductWAF), + string(RulesetActionParameterProductZoneLockdown), + } +} + +func RulesetRuleActionParametersHTTPHeaderOperationValues() []string { + return []string{ + string(RulesetRuleActionParametersHTTPHeaderOperationRemove), + string(RulesetRuleActionParametersHTTPHeaderOperationSet), + } +} + +// RulesetRuleAction defines a custom type that is used to express allowed +// values for the rule action. +type RulesetRuleAction string + +// RulesetKind is the custom type for allowed variances of rulesets. +type RulesetKind string + +// RulesetPhase is the custom type for defining at what point the ruleset will +// be applied in the request pipeline. +type RulesetPhase string + +// RulesetActionParameterProduct is the custom type for defining what products +// can be used within the action parameters of a ruleset. +type RulesetActionParameterProduct string + +// RulesetRuleActionParametersHTTPHeaderOperation defines available options for +// HTTP header operations in actions. +type RulesetRuleActionParametersHTTPHeaderOperation string + +// Ruleset contains the structure of a Ruleset. Using `string` for Kind and +// Phase is a developer nicety to support downstream clients like Terraform who +// don't really have a strong and expansive type system. As always, the +// recommendation is to use the types provided where possible to avoid +// surprises. +type Ruleset struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Kind string `json:"kind,omitempty"` + Version string `json:"version,omitempty"` + LastUpdated *time.Time `json:"last_updated,omitempty"` + Phase string `json:"phase,omitempty"` + Rules []RulesetRule `json:"rules"` + ShareableEntitlementName string `json:"shareable_entitlement_name,omitempty"` +} + +// RulesetRuleActionParameters specifies the action parameters for a Ruleset +// rule. +type RulesetRuleActionParameters struct { + ID string `json:"id,omitempty"` + Ruleset string `json:"ruleset,omitempty"` + Rulesets []string `json:"rulesets,omitempty"` + Rules map[string][]string `json:"rules,omitempty"` + Increment int `json:"increment,omitempty"` + URI *RulesetRuleActionParametersURI `json:"uri,omitempty"` + Headers map[string]RulesetRuleActionParametersHTTPHeader `json:"headers,omitempty"` + Products []string `json:"products,omitempty"` + Overrides *RulesetRuleActionParametersOverrides `json:"overrides,omitempty"` + MatchedData *RulesetRuleActionParametersMatchedData `json:"matched_data,omitempty"` + Version string `json:"version,omitempty"` +} + +// RulesetRuleActionParametersURI holds the URI struct for an action parameter. +type RulesetRuleActionParametersURI struct { + Path *RulesetRuleActionParametersURIPath `json:"path,omitempty"` + Query *RulesetRuleActionParametersURIQuery `json:"query,omitempty"` + Origin bool `json:"origin,omitempty"` +} + +// RulesetRuleActionParametersURIPath holds the path specific portion of a URI +// action parameter. +type RulesetRuleActionParametersURIPath struct { + Value string `json:"value,omitempty"` + Expression string `json:"expression,omitempty"` +} + +// RulesetRuleActionParametersURIQuery holds the query specific portion of a URI +// action parameter. +type RulesetRuleActionParametersURIQuery struct { + Value string `json:"value,omitempty"` + Expression string `json:"expression,omitempty"` +} + +// RulesetRuleActionParametersHTTPHeader is the definition for define action +// parameters that involve HTTP headers. +type RulesetRuleActionParametersHTTPHeader struct { + Operation string `json:"operation,omitempty"` + Value string `json:"value,omitempty"` + Expression string `json:"expression,omitempty"` +} + +type RulesetRuleActionParametersOverrides struct { + Enabled *bool `json:"enabled,omitempty"` + Action string `json:"action,omitempty"` + Categories []RulesetRuleActionParametersCategories `json:"categories,omitempty"` + Rules []RulesetRuleActionParametersRules `json:"rules,omitempty"` +} + +type RulesetRuleActionParametersCategories struct { + Category string `json:"category"` + Action string `json:"action,omitempty"` + Enabled bool `json:"enabled"` +} + +type RulesetRuleActionParametersRules struct { + ID string `json:"id"` + Action string `json:"action,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + ScoreThreshold int `json:"score_threshold,omitempty"` + SensitivityLevel string `json:"sensitivity_level,omitempty"` +} + +// RulesetRuleActionParametersMatchedData holds the structure for WAF based +// payload logging. +type RulesetRuleActionParametersMatchedData struct { + PublicKey string `json:"public_key,omitempty"` +} + +// RulesetRule contains information about a single Ruleset Rule. +type RulesetRule struct { + ID string `json:"id,omitempty"` + Version string `json:"version,omitempty"` + Action string `json:"action"` + ActionParameters *RulesetRuleActionParameters `json:"action_parameters,omitempty"` + Expression string `json:"expression"` + Description string `json:"description"` + LastUpdated *time.Time `json:"last_updated,omitempty"` + Ref string `json:"ref,omitempty"` + Enabled bool `json:"enabled"` + ScoreThreshold int `json:"score_threshold,omitempty"` + RateLimit *RulesetRuleRateLimit `json:"ratelimit,omitempty"` + ExposedCredentialCheck *RulesetRuleExposedCredentialCheck `json:"exposed_credential_check,omitempty"` +} + +// RulesetRuleRateLimit contains the structure of a HTTP rate limit Ruleset Rule. +type RulesetRuleRateLimit struct { + Characteristics []string `json:"characteristics,omitempty"` + RequestsPerPeriod int `json:"requests_per_period,omitempty"` + Period int `json:"period,omitempty"` + MitigationTimeout int `json:"mitigation_timeout,omitempty"` + + // Should always be sent as "" will trigger the service to use the Ruleset + // expression instead. + MitigationExpression string `json:"mitigation_expression"` +} + +// RulesetRuleExposedCredentialCheck contains the structure of an exposed +// credential check Ruleset Rule. +type RulesetRuleExposedCredentialCheck struct { + UsernameExpression string `json:"username_expression,omitempty"` + PasswordExpression string `json:"password_expression,omitempty"` +} + +// UpdateRulesetRequest is the representation of a Ruleset update. +type UpdateRulesetRequest struct { + Description string `json:"description"` + Rules []RulesetRule `json:"rules"` +} + +// ListRulesetResponse contains all Rulesets. +type ListRulesetResponse struct { + Response + Result []Ruleset `json:"result"` +} + +// GetRulesetResponse contains a single Ruleset. +type GetRulesetResponse struct { + Response + Result Ruleset `json:"result"` +} + +// CreateRulesetResponse contains response data when creating a new Ruleset. +type CreateRulesetResponse struct { + Response + Result Ruleset `json:"result"` +} + +// UpdateRulesetResponse contains response data when updating an existing +// Ruleset. +type UpdateRulesetResponse struct { + Response + Result Ruleset `json:"result"` +} + +// ListZoneRulesets fetches all rulesets for a zone. +// +// API reference: https://api.cloudflare.com/#zone-rulesets-list-zone-rulesets +func (api *API) ListZoneRulesets(ctx context.Context, zoneID string) ([]Ruleset, error) { + return api.listRulesets(ctx, ZoneRouteRoot, zoneID) +} + +// ListAccountRulesets fetches all rulesets for an account. +// +// API reference: https://api.cloudflare.com/#account-rulesets-list-account-rulesets +func (api *API) ListAccountRulesets(ctx context.Context, accountID string) ([]Ruleset, error) { + return api.listRulesets(ctx, AccountRouteRoot, accountID) +} + +// listRulesets lists all Rulesets for a given zone or account depending on the +// identifier type provided. +func (api *API) listRulesets(ctx context.Context, identifierType RouteRoot, identifier string) ([]Ruleset, error) { + uri := fmt.Sprintf("/%s/%s/rulesets", identifierType, identifier) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []Ruleset{}, err + } + + result := ListRulesetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return []Ruleset{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// GetZoneRuleset fetches a single ruleset for a zone. +// +// API reference: https://api.cloudflare.com/#zone-rulesets-get-a-zone-ruleset +func (api *API) GetZoneRuleset(ctx context.Context, zoneID, rulesetID string) (Ruleset, error) { + return api.getRuleset(ctx, ZoneRouteRoot, zoneID, rulesetID) +} + +// GetAccountRuleset fetches a single ruleset for an account. +// +// API reference: https://api.cloudflare.com/#account-rulesets-get-an-account-ruleset +func (api *API) GetAccountRuleset(ctx context.Context, accountID, rulesetID string) (Ruleset, error) { + return api.getRuleset(ctx, AccountRouteRoot, accountID, rulesetID) +} + +// getRuleset fetches a single ruleset based on the zone or account, the +// identifer and the ruleset ID. +func (api *API) getRuleset(ctx context.Context, identifierType RouteRoot, identifier, rulesetID string) (Ruleset, error) { + uri := fmt.Sprintf("/%s/%s/rulesets/%s", identifierType, identifier, rulesetID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return Ruleset{}, err + } + + result := GetRulesetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return Ruleset{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// CreateZoneRuleset creates a new ruleset for a zone. +// +// API reference: https://api.cloudflare.com/#zone-rulesets-create-zone-ruleset +func (api *API) CreateZoneRuleset(ctx context.Context, zoneID string, ruleset Ruleset) (Ruleset, error) { + return api.createRuleset(ctx, ZoneRouteRoot, zoneID, ruleset) +} + +// CreateAccountRuleset creates a new ruleset for an account. +// +// API reference: https://api.cloudflare.com/#account-rulesets-create-account-ruleset +func (api *API) CreateAccountRuleset(ctx context.Context, accountID string, ruleset Ruleset) (Ruleset, error) { + return api.createRuleset(ctx, AccountRouteRoot, accountID, ruleset) +} + +func (api *API) createRuleset(ctx context.Context, identifierType RouteRoot, identifier string, ruleset Ruleset) (Ruleset, error) { + uri := fmt.Sprintf("/%s/%s/rulesets", identifierType, identifier) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, ruleset) + + if err != nil { + return Ruleset{}, err + } + + result := CreateRulesetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return Ruleset{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// DeleteZoneRuleset deletes a single ruleset for a zone. +// +// API reference: https://api.cloudflare.com/#zone-rulesets-delete-zone-ruleset +func (api *API) DeleteZoneRuleset(ctx context.Context, zoneID, rulesetID string) error { + return api.deleteRuleset(ctx, ZoneRouteRoot, zoneID, rulesetID) +} + +// DeleteAccountRuleset deletes a single ruleset for an account. +// +// API reference: https://api.cloudflare.com/#account-rulesets-delete-account-ruleset +func (api *API) DeleteAccountRuleset(ctx context.Context, accountID, rulesetID string) error { + return api.deleteRuleset(ctx, AccountRouteRoot, accountID, rulesetID) +} + +// deleteRuleset removes a ruleset based on the ruleset ID. +func (api *API) deleteRuleset(ctx context.Context, identifierType RouteRoot, identifier, rulesetID string) error { + uri := fmt.Sprintf("/%s/%s/rulesets/%s", identifierType, identifier, rulesetID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + + if err != nil { + return err + } + + // The API is not implementing the standard response blob but returns an + // empty response (204) in case of a success. So we are checking for the + // response body size here. + if len(res) > 0 { + return errors.Wrap(errors.New(string(res)), errMakeRequestError) + } + + return nil +} + +// UpdateZoneRuleset updates a single ruleset for a zone. +// +// API reference: https://api.cloudflare.com/#zone-rulesets-update-a-zone-ruleset +func (api *API) UpdateZoneRuleset(ctx context.Context, zoneID, rulesetID, description string, rules []RulesetRule) (Ruleset, error) { + return api.updateRuleset(ctx, ZoneRouteRoot, zoneID, rulesetID, description, rules) +} + +// UpdateAccountRuleset updates a single ruleset for an account. +// +// API reference: https://api.cloudflare.com/#account-rulesets-update-account-ruleset +func (api *API) UpdateAccountRuleset(ctx context.Context, accountID, rulesetID, description string, rules []RulesetRule) (Ruleset, error) { + return api.updateRuleset(ctx, AccountRouteRoot, accountID, rulesetID, description, rules) +} + +// updateRuleset updates a ruleset based on the ruleset ID. +func (api *API) updateRuleset(ctx context.Context, identifierType RouteRoot, identifier, rulesetID, description string, rules []RulesetRule) (Ruleset, error) { + uri := fmt.Sprintf("/%s/%s/rulesets/%s", identifierType, identifier, rulesetID) + payload := UpdateRulesetRequest{Description: description, Rules: rules} + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, payload) + if err != nil { + return Ruleset{}, err + } + + result := UpdateRulesetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return Ruleset{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// GetZoneRulesetPhase returns a ruleset phase for a zone. +// +// API reference: TBA +func (api *API) GetZoneRulesetPhase(ctx context.Context, zoneID, rulesetPhase string) (Ruleset, error) { + return api.getRulesetPhase(ctx, ZoneRouteRoot, zoneID, rulesetPhase) +} + +// GetAccountRulesetPhase returns a ruleset phase for an account. +// +// API reference: TBA +func (api *API) GetAccountRulesetPhase(ctx context.Context, accountID, rulesetPhase string) (Ruleset, error) { + return api.getRulesetPhase(ctx, AccountRouteRoot, accountID, rulesetPhase) +} + +// getRulesetPhase returns a ruleset phase based on the zone or account and the +// identifer. +func (api *API) getRulesetPhase(ctx context.Context, identifierType RouteRoot, identifier, rulesetPhase string) (Ruleset, error) { + uri := fmt.Sprintf("/%s/%s/rulesets/phases/%s/entrypoint", identifierType, identifier, rulesetPhase) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return Ruleset{}, err + } + + result := GetRulesetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return Ruleset{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// UpdateZoneRulesetPhase updates a ruleset phase for a zone. +// +// API reference: TBA +func (api *API) UpdateZoneRulesetPhase(ctx context.Context, zoneID, rulesetPhase string, ruleset Ruleset) (Ruleset, error) { + return api.updateRulesetPhase(ctx, ZoneRouteRoot, zoneID, rulesetPhase, ruleset) +} + +// UpdateAccountRulesetPhase updates a ruleset phase for an account. +// +// API reference: TBA +func (api *API) UpdateAccountRulesetPhase(ctx context.Context, accountID, rulesetPhase string, ruleset Ruleset) (Ruleset, error) { + return api.updateRulesetPhase(ctx, AccountRouteRoot, accountID, rulesetPhase, ruleset) +} + +// updateRulesetPhase updates a ruleset phase based on the zone or account, the +// identifer and the rules. +func (api *API) updateRulesetPhase(ctx context.Context, identifierType RouteRoot, identifier, rulesetPhase string, ruleset Ruleset) (Ruleset, error) { + uri := fmt.Sprintf("/%s/%s/rulesets/phases/%s/entrypoint", identifierType, identifier, rulesetPhase) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, ruleset) + if err != nil { + return Ruleset{}, err + } + + result := GetRulesetResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return Ruleset{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/secondary_dns_primaries.go b/vendor/github.com/cloudflare/cloudflare-go/secondary_dns_primaries.go new file mode 100644 index 0000000000000..dc0b57a49ddd5 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/secondary_dns_primaries.go @@ -0,0 +1,165 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +const ( + errSecondaryDNSInvalidPrimaryID = "secondary DNS primary ID is required" + errSecondaryDNSInvalidPrimaryIP = "secondary DNS primary IP invalid" + errSecondaryDNSInvalidPrimaryPort = "secondary DNS primary port invalid" +) + +// SecondaryDNSPrimary is the representation of the DNS Primary. +type SecondaryDNSPrimary struct { + ID string `json:"id,omitempty"` + IP string `json:"ip"` + Port int `json:"port"` + IxfrEnable bool `json:"ixfr_enable"` + TsigID string `json:"tsig_id"` + Name string `json:"name"` +} + +// SecondaryDNSPrimaryDetailResponse is the API representation of a single +// secondary DNS primary response. +type SecondaryDNSPrimaryDetailResponse struct { + Response + Result SecondaryDNSPrimary `json:"result"` +} + +// SecondaryDNSPrimaryListResponse is the API representation of all secondary DNS +// primaries. +type SecondaryDNSPrimaryListResponse struct { + Response + Result []SecondaryDNSPrimary `json:"result"` +} + +// GetSecondaryDNSPrimary returns a single secondary DNS primary. +// +// API reference: https://api.cloudflare.com/#secondary-dns-primary--primary-details +func (api *API) GetSecondaryDNSPrimary(ctx context.Context, accountID, primaryID string) (SecondaryDNSPrimary, error) { + uri := fmt.Sprintf("/accounts/%s/secondary_dns/primaries/%s", accountID, primaryID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return SecondaryDNSPrimary{}, err + } + + var r SecondaryDNSPrimaryDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return SecondaryDNSPrimary{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListSecondaryDNSPrimaries returns all secondary DNS primaries for an account. +// +// API reference: https://api.cloudflare.com/#secondary-dns-primary--list-primaries +func (api *API) ListSecondaryDNSPrimaries(ctx context.Context, accountID string) ([]SecondaryDNSPrimary, error) { + uri := fmt.Sprintf("/accounts/%s/secondary_dns/primaries", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []SecondaryDNSPrimary{}, err + } + + var r SecondaryDNSPrimaryListResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []SecondaryDNSPrimary{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// CreateSecondaryDNSPrimary creates a secondary DNS primary. +// +// API reference: https://api.cloudflare.com/#secondary-dns-primary--create-primary +func (api *API) CreateSecondaryDNSPrimary(ctx context.Context, accountID string, primary SecondaryDNSPrimary) (SecondaryDNSPrimary, error) { + if err := validateRequiredSecondaryDNSPrimaries(primary); err != nil { + return SecondaryDNSPrimary{}, err + } + + uri := fmt.Sprintf("/accounts/%s/secondary_dns/primaries", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, SecondaryDNSPrimary{ + IP: primary.IP, + Port: primary.Port, + IxfrEnable: primary.IxfrEnable, + TsigID: primary.TsigID, + Name: primary.Name, + }) + if err != nil { + return SecondaryDNSPrimary{}, err + } + + var r SecondaryDNSPrimaryDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return SecondaryDNSPrimary{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateSecondaryDNSPrimary creates a secondary DNS primary. +// +// API reference: https://api.cloudflare.com/#secondary-dns-primary--update-primary +func (api *API) UpdateSecondaryDNSPrimary(ctx context.Context, accountID string, primary SecondaryDNSPrimary) (SecondaryDNSPrimary, error) { + if primary.ID == "" { + return SecondaryDNSPrimary{}, errors.New(errSecondaryDNSInvalidPrimaryID) + } + + if err := validateRequiredSecondaryDNSPrimaries(primary); err != nil { + return SecondaryDNSPrimary{}, err + } + + uri := fmt.Sprintf("/accounts/%s/secondary_dns/primaries/%s", accountID, primary.ID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, SecondaryDNSPrimary{ + IP: primary.IP, + Port: primary.Port, + IxfrEnable: primary.IxfrEnable, + TsigID: primary.TsigID, + Name: primary.Name, + }) + if err != nil { + return SecondaryDNSPrimary{}, err + } + + var r SecondaryDNSPrimaryDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return SecondaryDNSPrimary{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeleteSecondaryDNSPrimary deletes a secondary DNS primary. +// +// API reference: https://api.cloudflare.com/#secondary-dns-primary--delete-primary +func (api *API) DeleteSecondaryDNSPrimary(ctx context.Context, accountID, primaryID string) error { + uri := fmt.Sprintf("/zones/%s/secondary_dns/primaries/%s", accountID, primaryID) + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + + if err != nil { + return err + } + + return nil +} + +func validateRequiredSecondaryDNSPrimaries(p SecondaryDNSPrimary) error { + if p.IP == "" { + return errors.New(errSecondaryDNSInvalidPrimaryIP) + } + + if p.Port == 0 { + return errors.New(errSecondaryDNSInvalidPrimaryPort) + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/secondary_dns_tsig.go b/vendor/github.com/cloudflare/cloudflare-go/secondary_dns_tsig.go new file mode 100644 index 0000000000000..255c409b03cbd --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/secondary_dns_tsig.go @@ -0,0 +1,132 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +const ( + errSecondaryDNSTSIGMissingID = "secondary DNS TSIG ID is required" +) + +// SecondaryDNSTSIG contains the structure for a secondary DNS TSIG. +type SecondaryDNSTSIG struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` + Secret string `json:"secret"` + Algo string `json:"algo"` +} + +// SecondaryDNSTSIGDetailResponse is the API response for a single secondary +// DNS TSIG. +type SecondaryDNSTSIGDetailResponse struct { + Response + Result SecondaryDNSTSIG `json:"result"` +} + +// SecondaryDNSTSIGListResponse is the API response for all secondary DNS TSIGs. +type SecondaryDNSTSIGListResponse struct { + Response + Result []SecondaryDNSTSIG `json:"result"` +} + +// GetSecondaryDNSTSIG gets a single account level TSIG for a secondary DNS +// configuration. +// +// API reference: https://api.cloudflare.com/#secondary-dns-tsig--tsig-details +func (api *API) GetSecondaryDNSTSIG(ctx context.Context, accountID, tsigID string) (SecondaryDNSTSIG, error) { + uri := fmt.Sprintf("/accounts/%s/secondary_dns/tsigs/%s", accountID, tsigID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return SecondaryDNSTSIG{}, err + } + + var r SecondaryDNSTSIGDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return SecondaryDNSTSIG{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListSecondaryDNSTSIGs gets all account level TSIG for a secondary DNS +// configuration. +// +// API reference: https://api.cloudflare.com/#secondary-dns-tsig--list-tsigs +func (api *API) ListSecondaryDNSTSIGs(ctx context.Context, accountID string) ([]SecondaryDNSTSIG, error) { + uri := fmt.Sprintf("/accounts/%s/secondary_dns/tsigs", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []SecondaryDNSTSIG{}, err + } + + var r SecondaryDNSTSIGListResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []SecondaryDNSTSIG{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// CreateSecondaryDNSTSIG creates a secondary DNS TSIG at the account level. +// +// API reference: https://api.cloudflare.com/#secondary-dns-tsig--create-tsig +func (api *API) CreateSecondaryDNSTSIG(ctx context.Context, accountID string, tsig SecondaryDNSTSIG) (SecondaryDNSTSIG, error) { + uri := fmt.Sprintf("/accounts/%s/secondary_dns/tsigs", accountID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, tsig) + + if err != nil { + return SecondaryDNSTSIG{}, err + } + + result := SecondaryDNSTSIGDetailResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return SecondaryDNSTSIG{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// UpdateSecondaryDNSTSIG updates an existing secondary DNS TSIG at +// the account level. +// +// API reference: https://api.cloudflare.com/#secondary-dns-tsig--update-tsig +func (api *API) UpdateSecondaryDNSTSIG(ctx context.Context, accountID string, tsig SecondaryDNSTSIG) (SecondaryDNSTSIG, error) { + if tsig.ID == "" { + return SecondaryDNSTSIG{}, errors.New(errSecondaryDNSTSIGMissingID) + } + + uri := fmt.Sprintf("/accounts/%s/secondary_dns/tsigs/%s", accountID, tsig.ID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, tsig) + + if err != nil { + return SecondaryDNSTSIG{}, err + } + + result := SecondaryDNSTSIGDetailResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return SecondaryDNSTSIG{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// DeleteSecondaryDNSTSIG deletes a secondary DNS TSIG. +// +// API reference: https://api.cloudflare.com/#secondary-dns-tsig--delete-tsig +func (api *API) DeleteSecondaryDNSTSIG(ctx context.Context, accountID, tsigID string) error { + uri := fmt.Sprintf("/accounts/%s/secondary_dns/tsigs/%s", accountID, tsigID) + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/secondary_dns_zone.go b/vendor/github.com/cloudflare/cloudflare-go/secondary_dns_zone.go new file mode 100644 index 0000000000000..034defc9f8fd8 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/secondary_dns_zone.go @@ -0,0 +1,172 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +const ( + errSecondaryDNSInvalidAutoRefreshValue = "secondary DNS auto refresh value is invalid" + errSecondaryDNSInvalidZoneName = "secondary DNS zone name is invalid" + errSecondaryDNSInvalidPrimaries = "secondary DNS primaries value is invalid" +) + +// SecondaryDNSZone contains the high level structure of a secondary DNS zone. +type SecondaryDNSZone struct { + ID string `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Primaries []string `json:"primaries,omitempty"` + AutoRefreshSeconds int `json:"auto_refresh_seconds,omitempty"` + SoaSerial int `json:"soa_serial,omitempty"` + CreatedTime time.Time `json:"created_time,omitempty"` + CheckedTime time.Time `json:"checked_time,omitempty"` + ModifiedTime time.Time `json:"modified_time,omitempty"` +} + +// SecondaryDNSZoneDetailResponse is the API response for a single secondary +// DNS zone. +type SecondaryDNSZoneDetailResponse struct { + Response + Result SecondaryDNSZone `json:"result"` +} + +// SecondaryDNSZoneAXFRResponse is the API response for a single secondary +// DNS AXFR response. +type SecondaryDNSZoneAXFRResponse struct { + Response + Result string `json:"result"` +} + +// GetSecondaryDNSZone returns the secondary DNS zone configuration for a +// single zone. +// +// API reference: https://api.cloudflare.com/#secondary-dns-secondary-zone-configuration-details +func (api *API) GetSecondaryDNSZone(ctx context.Context, zoneID string) (SecondaryDNSZone, error) { + uri := fmt.Sprintf("/zones/%s/secondary_dns", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return SecondaryDNSZone{}, err + } + + var r SecondaryDNSZoneDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return SecondaryDNSZone{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// CreateSecondaryDNSZone creates a secondary DNS zone. +// +// API reference: https://api.cloudflare.com/#secondary-dns-create-secondary-zone-configuration +func (api *API) CreateSecondaryDNSZone(ctx context.Context, zoneID string, zone SecondaryDNSZone) (SecondaryDNSZone, error) { + if err := validateRequiredSecondaryDNSZoneValues(zone); err != nil { + return SecondaryDNSZone{}, err + } + + uri := fmt.Sprintf("/zones/%s/secondary_dns", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, + SecondaryDNSZone{ + Name: zone.Name, + AutoRefreshSeconds: zone.AutoRefreshSeconds, + Primaries: zone.Primaries, + }, + ) + + if err != nil { + return SecondaryDNSZone{}, err + } + + result := SecondaryDNSZoneDetailResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return SecondaryDNSZone{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// UpdateSecondaryDNSZone updates an existing secondary DNS zone. +// +// API reference: https://api.cloudflare.com/#secondary-dns-update-secondary-zone-configuration +func (api *API) UpdateSecondaryDNSZone(ctx context.Context, zoneID string, zone SecondaryDNSZone) (SecondaryDNSZone, error) { + if err := validateRequiredSecondaryDNSZoneValues(zone); err != nil { + return SecondaryDNSZone{}, err + } + + uri := fmt.Sprintf("/zones/%s/secondary_dns", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, + SecondaryDNSZone{ + Name: zone.Name, + AutoRefreshSeconds: zone.AutoRefreshSeconds, + Primaries: zone.Primaries, + }, + ) + + if err != nil { + return SecondaryDNSZone{}, err + } + + result := SecondaryDNSZoneDetailResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return SecondaryDNSZone{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result, nil +} + +// DeleteSecondaryDNSZone deletes a secondary DNS zone. +// +// API reference: https://api.cloudflare.com/#secondary-dns-delete-secondary-zone-configuration +func (api *API) DeleteSecondaryDNSZone(ctx context.Context, zoneID string) error { + uri := fmt.Sprintf("/zones/%s/secondary_dns", zoneID) + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + + if err != nil { + return err + } + + return nil +} + +// ForceSecondaryDNSZoneAXFR requests an immediate AXFR request. +// +// API reference: https://api.cloudflare.com/#secondary-dns-update-secondary-zone-configuration +func (api *API) ForceSecondaryDNSZoneAXFR(ctx context.Context, zoneID string) error { + uri := fmt.Sprintf("/zones/%s/secondary_dns/force_axfr", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, nil) + + if err != nil { + return err + } + + result := SecondaryDNSZoneAXFRResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return errors.Wrap(err, errUnmarshalError) + } + + return nil +} + +// validateRequiredSecondaryDNSZoneValues ensures that the payload matches the +// API requirements for required fields. +func validateRequiredSecondaryDNSZoneValues(zone SecondaryDNSZone) error { + if zone.Name == "" { + return errors.New(errSecondaryDNSInvalidZoneName) + } + + if zone.AutoRefreshSeconds == 0 || zone.AutoRefreshSeconds < 0 { + return errors.New(errSecondaryDNSInvalidAutoRefreshValue) + } + + if len(zone.Primaries) == 0 { + return errors.New(errSecondaryDNSInvalidPrimaries) + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/spectrum.go b/vendor/github.com/cloudflare/cloudflare-go/spectrum.go new file mode 100644 index 0000000000000..7e5247a930353 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/spectrum.go @@ -0,0 +1,368 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net" + "net/http" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" +) + +// ProxyProtocol implements json.Unmarshaler in order to support deserializing of the deprecated boolean +// value for `proxy_protocol` +type ProxyProtocol string + +// UnmarshalJSON handles deserializing of both the deprecated boolean value and the current string value +// for the `proxy_protocol` field. +func (p *ProxyProtocol) UnmarshalJSON(data []byte) error { + var raw interface{} + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + switch pp := raw.(type) { + case string: + *p = ProxyProtocol(pp) + case bool: + if pp { + *p = "v1" + } else { + *p = "off" + } + default: + return fmt.Errorf("invalid type for proxy_protocol field: %T", pp) + } + return nil +} + +// SpectrumApplicationOriginPort defines a union of a single port or range of ports +type SpectrumApplicationOriginPort struct { + Port, Start, End uint16 +} + +// ErrOriginPortInvalid is a common error for failing to parse a single port or port range +var ErrOriginPortInvalid = errors.New("invalid origin port") + +func (p *SpectrumApplicationOriginPort) parse(s string) error { + switch split := strings.Split(s, "-"); len(split) { + case 1: + i, err := strconv.ParseUint(split[0], 10, 16) + if err != nil { + return err + } + p.Port = uint16(i) + case 2: + start, err := strconv.ParseUint(split[0], 10, 16) + if err != nil { + return err + } + end, err := strconv.ParseUint(split[1], 10, 16) + if err != nil { + return err + } + if start >= end { + return ErrOriginPortInvalid + } + p.Start = uint16(start) + p.End = uint16(end) + default: + return ErrOriginPortInvalid + } + return nil +} + +// UnmarshalJSON converts a byte slice into a single port or port range +func (p *SpectrumApplicationOriginPort) UnmarshalJSON(b []byte) error { + var port interface{} + if err := json.Unmarshal(b, &port); err != nil { + return err + } + + switch i := port.(type) { + case float64: + p.Port = uint16(i) + case string: + if err := p.parse(i); err != nil { + return err + } + } + + return nil +} + +// MarshalJSON converts a single port or port range to a suitable byte slice +func (p *SpectrumApplicationOriginPort) MarshalJSON() ([]byte, error) { + if p.End > 0 { + return json.Marshal(fmt.Sprintf("%d-%d", p.Start, p.End)) + } + return json.Marshal(p.Port) +} + +// SpectrumApplication defines a single Spectrum Application. +type SpectrumApplication struct { + ID string `json:"id,omitempty"` + Protocol string `json:"protocol,omitempty"` + IPv4 bool `json:"ipv4,omitempty"` + DNS SpectrumApplicationDNS `json:"dns,omitempty"` + OriginDirect []string `json:"origin_direct,omitempty"` + OriginPort *SpectrumApplicationOriginPort `json:"origin_port,omitempty"` + OriginDNS *SpectrumApplicationOriginDNS `json:"origin_dns,omitempty"` + IPFirewall bool `json:"ip_firewall,omitempty"` + ProxyProtocol ProxyProtocol `json:"proxy_protocol,omitempty"` + TLS string `json:"tls,omitempty"` + TrafficType string `json:"traffic_type,omitempty"` + EdgeIPs *SpectrumApplicationEdgeIPs `json:"edge_ips,omitempty"` + ArgoSmartRouting bool `json:"argo_smart_routing,omitempty"` + CreatedOn *time.Time `json:"created_on,omitempty"` + ModifiedOn *time.Time `json:"modified_on,omitempty"` +} + +// UnmarshalJSON handles setting the `ProxyProtocol` field based on the value of the deprecated `spp` field. +func (a *SpectrumApplication) UnmarshalJSON(data []byte) error { + var body map[string]interface{} + if err := json.Unmarshal(data, &body); err != nil { + return err + } + + var app spectrumApplicationRaw + if err := json.Unmarshal(data, &app); err != nil { + return err + } + + if spp, ok := body["spp"]; ok && spp.(bool) { + app.ProxyProtocol = "simple" + } + + *a = SpectrumApplication(app) + return nil +} + +// spectrumApplicationRaw is used to inspect an application body to support the deprecated boolean value for `spp` +type spectrumApplicationRaw SpectrumApplication + +// SpectrumApplicationDNS holds the external DNS configuration for a Spectrum +// Application. +type SpectrumApplicationDNS struct { + Type string `json:"type"` + Name string `json:"name"` +} + +// SpectrumApplicationOriginDNS holds the origin DNS configuration for a Spectrum +// Application. +type SpectrumApplicationOriginDNS struct { + Name string `json:"name"` +} + +// SpectrumApplicationDetailResponse is the structure of the detailed response +// from the API. +type SpectrumApplicationDetailResponse struct { + Response + Result SpectrumApplication `json:"result"` +} + +// SpectrumApplicationsDetailResponse is the structure of the detailed response +// from the API. +type SpectrumApplicationsDetailResponse struct { + Response + Result []SpectrumApplication `json:"result"` +} + +// SpectrumApplicationEdgeIPs represents configuration for Bring-Your-Own-IP +// https://developers.cloudflare.com/spectrum/getting-started/byoip/ +type SpectrumApplicationEdgeIPs struct { + Type SpectrumApplicationEdgeType `json:"type"` + Connectivity *SpectrumApplicationConnectivity `json:"connectivity,omitempty"` + IPs []net.IP `json:"ips,omitempty"` +} + +// SpectrumApplicationEdgeType for possible Edge configurations +type SpectrumApplicationEdgeType string + +const ( + // SpectrumEdgeTypeDynamic IP config + SpectrumEdgeTypeDynamic SpectrumApplicationEdgeType = "dynamic" + // SpectrumEdgeTypeStatic IP config + SpectrumEdgeTypeStatic SpectrumApplicationEdgeType = "static" +) + +// UnmarshalJSON function for SpectrumApplicationEdgeType enum +func (t *SpectrumApplicationEdgeType) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + newEdgeType := SpectrumApplicationEdgeType(strings.ToLower(s)) + switch newEdgeType { + case SpectrumEdgeTypeDynamic, SpectrumEdgeTypeStatic: + *t = newEdgeType + return nil + } + + return errors.New(errUnmarshalError) +} + +func (t SpectrumApplicationEdgeType) String() string { + return string(t) +} + +// SpectrumApplicationConnectivity specifies IP address type on the edge configuration +type SpectrumApplicationConnectivity string + +const ( + // SpectrumConnectivityAll specifies IPv4/6 edge IP + SpectrumConnectivityAll SpectrumApplicationConnectivity = "all" + // SpectrumConnectivityIPv4 specifies IPv4 edge IP + SpectrumConnectivityIPv4 SpectrumApplicationConnectivity = "ipv4" + // SpectrumConnectivityIPv6 specifies IPv6 edge IP + SpectrumConnectivityIPv6 SpectrumApplicationConnectivity = "ipv6" + // SpectrumConnectivityStatic specifies static edge IP configuration + SpectrumConnectivityStatic SpectrumApplicationConnectivity = "static" +) + +func (c SpectrumApplicationConnectivity) String() string { + return string(c) +} + +// UnmarshalJSON function for SpectrumApplicationConnectivity enum +func (c *SpectrumApplicationConnectivity) UnmarshalJSON(b []byte) error { + var s string + err := json.Unmarshal(b, &s) + if err != nil { + return err + } + + newConnectivity := SpectrumApplicationConnectivity(strings.ToLower(s)) + if newConnectivity.Dynamic() { + *c = newConnectivity + return nil + } + + return errors.New(errUnmarshalError) +} + +// Dynamic checks if address family is specified as dynamic config +func (c SpectrumApplicationConnectivity) Dynamic() bool { + switch c { + case SpectrumConnectivityAll, SpectrumConnectivityIPv4, SpectrumConnectivityIPv6: + return true + } + return false +} + +// Static checks if address family is specified as static config +func (c SpectrumApplicationConnectivity) Static() bool { + return c == SpectrumConnectivityStatic +} + +// SpectrumApplications fetches all of the Spectrum applications for a zone. +// +// API reference: https://developers.cloudflare.com/spectrum/api-reference/#list-spectrum-applications +func (api *API) SpectrumApplications(ctx context.Context, zoneID string) ([]SpectrumApplication, error) { + uri := fmt.Sprintf("/zones/%s/spectrum/apps", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []SpectrumApplication{}, err + } + + var spectrumApplications SpectrumApplicationsDetailResponse + err = json.Unmarshal(res, &spectrumApplications) + if err != nil { + return []SpectrumApplication{}, errors.Wrap(err, errUnmarshalError) + } + + return spectrumApplications.Result, nil +} + +// SpectrumApplication fetches a single Spectrum application based on the ID. +// +// API reference: https://developers.cloudflare.com/spectrum/api-reference/#list-spectrum-applications +func (api *API) SpectrumApplication(ctx context.Context, zoneID string, applicationID string) (SpectrumApplication, error) { + uri := fmt.Sprintf( + "/zones/%s/spectrum/apps/%s", + zoneID, + applicationID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return SpectrumApplication{}, err + } + + var spectrumApplication SpectrumApplicationDetailResponse + err = json.Unmarshal(res, &spectrumApplication) + if err != nil { + return SpectrumApplication{}, errors.Wrap(err, errUnmarshalError) + } + + return spectrumApplication.Result, nil +} + +// CreateSpectrumApplication creates a new Spectrum application. +// +// API reference: https://developers.cloudflare.com/spectrum/api-reference/#create-a-spectrum-application +func (api *API) CreateSpectrumApplication(ctx context.Context, zoneID string, appDetails SpectrumApplication) (SpectrumApplication, error) { + uri := fmt.Sprintf("/zones/%s/spectrum/apps", zoneID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, appDetails) + if err != nil { + return SpectrumApplication{}, err + } + + var spectrumApplication SpectrumApplicationDetailResponse + err = json.Unmarshal(res, &spectrumApplication) + if err != nil { + return SpectrumApplication{}, errors.Wrap(err, errUnmarshalError) + } + + return spectrumApplication.Result, nil +} + +// UpdateSpectrumApplication updates an existing Spectrum application. +// +// API reference: https://developers.cloudflare.com/spectrum/api-reference/#update-a-spectrum-application +func (api *API) UpdateSpectrumApplication(ctx context.Context, zoneID, appID string, appDetails SpectrumApplication) (SpectrumApplication, error) { + uri := fmt.Sprintf( + "/zones/%s/spectrum/apps/%s", + zoneID, + appID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, appDetails) + if err != nil { + return SpectrumApplication{}, err + } + + var spectrumApplication SpectrumApplicationDetailResponse + err = json.Unmarshal(res, &spectrumApplication) + if err != nil { + return SpectrumApplication{}, errors.Wrap(err, errUnmarshalError) + } + + return spectrumApplication.Result, nil +} + +// DeleteSpectrumApplication removes a Spectrum application based on the ID. +// +// API reference: https://developers.cloudflare.com/spectrum/api-reference/#delete-a-spectrum-application +func (api *API) DeleteSpectrumApplication(ctx context.Context, zoneID string, applicationID string) error { + uri := fmt.Sprintf( + "/zones/%s/spectrum/apps/%s", + zoneID, + applicationID, + ) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/split_tunnel.go b/vendor/github.com/cloudflare/cloudflare-go/split_tunnel.go new file mode 100644 index 0000000000000..04a34bedcc3a2 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/split_tunnel.go @@ -0,0 +1,66 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +// SplitTunnelResponse represents the response from the get split +// tunnel endpoints. +type SplitTunnelResponse struct { + Response + Result []SplitTunnel `json:"result"` +} + +// SplitTunnel represents the individual tunnel struct. +type SplitTunnel struct { + Address string `json:"address,omitempty"` + Host string `json:"host,omitempty"` + Description string `json:"description,omitempty"` +} + +// ListSplitTunnel returns all include or exclude split tunnel within an account. +// +// API reference for include: https://api.cloudflare.com/#device-policy-get-split-tunnel-include-list +// API reference for exclude: https://api.cloudflare.com/#device-policy-get-split-tunnel-exclude-list +func (api *API) ListSplitTunnels(ctx context.Context, accountID string, mode string) ([]SplitTunnel, error) { + uri := fmt.Sprintf("/%s/%s/devices/policy/%s", AccountRouteRoot, accountID, mode) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []SplitTunnel{}, err + } + + var splitTunnelResponse SplitTunnelResponse + err = json.Unmarshal(res, &splitTunnelResponse) + if err != nil { + return []SplitTunnel{}, errors.Wrap(err, errUnmarshalError) + } + + return splitTunnelResponse.Result, nil +} + +// UpdateSplitTunnel updates the existing split tunnel policy. +// +// API reference for include: https://api.cloudflare.com/#device-policy-set-split-tunnel-include-list +// API reference for exclude: https://api.cloudflare.com/#device-policy-set-split-tunnel-exclude-list +func (api *API) UpdateSplitTunnel(ctx context.Context, accountID string, mode string, tunnels []SplitTunnel) ([]SplitTunnel, error) { + uri := fmt.Sprintf("/%s/%s/devices/policy/%s", AccountRouteRoot, accountID, mode) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, tunnels) + if err != nil { + return []SplitTunnel{}, err + } + + var splitTunnelResponse SplitTunnelResponse + err = json.Unmarshal(res, &splitTunnelResponse) + if err != nil { + return []SplitTunnel{}, errors.Wrap(err, errUnmarshalError) + } + + return splitTunnelResponse.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/ssl.go b/vendor/github.com/cloudflare/cloudflare-go/ssl.go new file mode 100644 index 0000000000000..23ae2638571dc --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/ssl.go @@ -0,0 +1,160 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// ZoneCustomSSL represents custom SSL certificate metadata. +type ZoneCustomSSL struct { + ID string `json:"id"` + Hosts []string `json:"hosts"` + Issuer string `json:"issuer"` + Signature string `json:"signature"` + Status string `json:"status"` + BundleMethod string `json:"bundle_method"` + GeoRestrictions ZoneCustomSSLGeoRestrictions `json:"geo_restrictions"` + ZoneID string `json:"zone_id"` + UploadedOn time.Time `json:"uploaded_on"` + ModifiedOn time.Time `json:"modified_on"` + ExpiresOn time.Time `json:"expires_on"` + Priority int `json:"priority"` + KeylessServer KeylessSSL `json:"keyless_server"` +} + +// ZoneCustomSSLGeoRestrictions represents the parameter to create or update +// geographic restrictions on a custom ssl certificate. +type ZoneCustomSSLGeoRestrictions struct { + Label string `json:"label"` +} + +// zoneCustomSSLResponse represents the response from the zone SSL details endpoint. +type zoneCustomSSLResponse struct { + Response + Result ZoneCustomSSL `json:"result"` +} + +// zoneCustomSSLsResponse represents the response from the zone SSL list endpoint. +type zoneCustomSSLsResponse struct { + Response + Result []ZoneCustomSSL `json:"result"` +} + +// ZoneCustomSSLOptions represents the parameters to create or update an existing +// custom SSL configuration. +type ZoneCustomSSLOptions struct { + Certificate string `json:"certificate"` + PrivateKey string `json:"private_key"` + BundleMethod string `json:"bundle_method,omitempty"` + GeoRestrictions *ZoneCustomSSLGeoRestrictions `json:"geo_restrictions,omitempty"` + Type string `json:"type,omitempty"` +} + +// ZoneCustomSSLPriority represents a certificate's ID and priority. It is a +// subset of ZoneCustomSSL used for patch requests. +type ZoneCustomSSLPriority struct { + ID string `json:"ID"` + Priority int `json:"priority"` +} + +// CreateSSL allows you to add a custom SSL certificate to the given zone. +// +// API reference: https://api.cloudflare.com/#custom-ssl-for-a-zone-create-ssl-configuration +func (api *API) CreateSSL(ctx context.Context, zoneID string, options ZoneCustomSSLOptions) (ZoneCustomSSL, error) { + uri := fmt.Sprintf("/zones/%s/custom_certificates", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, options) + if err != nil { + return ZoneCustomSSL{}, err + } + var r zoneCustomSSLResponse + if err := json.Unmarshal(res, &r); err != nil { + return ZoneCustomSSL{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListSSL lists the custom certificates for the given zone. +// +// API reference: https://api.cloudflare.com/#custom-ssl-for-a-zone-list-ssl-configurations +func (api *API) ListSSL(ctx context.Context, zoneID string) ([]ZoneCustomSSL, error) { + uri := fmt.Sprintf("/zones/%s/custom_certificates", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + var r zoneCustomSSLsResponse + if err := json.Unmarshal(res, &r); err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// SSLDetails returns the configuration details for a custom SSL certificate. +// +// API reference: https://api.cloudflare.com/#custom-ssl-for-a-zone-ssl-configuration-details +func (api *API) SSLDetails(ctx context.Context, zoneID, certificateID string) (ZoneCustomSSL, error) { + uri := fmt.Sprintf("/zones/%s/custom_certificates/%s", zoneID, certificateID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return ZoneCustomSSL{}, err + } + var r zoneCustomSSLResponse + if err := json.Unmarshal(res, &r); err != nil { + return ZoneCustomSSL{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateSSL updates (replaces) a custom SSL certificate. +// +// API reference: https://api.cloudflare.com/#custom-ssl-for-a-zone-update-ssl-configuration +func (api *API) UpdateSSL(ctx context.Context, zoneID, certificateID string, options ZoneCustomSSLOptions) (ZoneCustomSSL, error) { + uri := fmt.Sprintf("/zones/%s/custom_certificates/%s", zoneID, certificateID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, options) + if err != nil { + return ZoneCustomSSL{}, err + } + var r zoneCustomSSLResponse + if err := json.Unmarshal(res, &r); err != nil { + return ZoneCustomSSL{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ReprioritizeSSL allows you to change the priority (which is served for a given +// request) of custom SSL certificates associated with the given zone. +// +// API reference: https://api.cloudflare.com/#custom-ssl-for-a-zone-re-prioritize-ssl-certificates +func (api *API) ReprioritizeSSL(ctx context.Context, zoneID string, p []ZoneCustomSSLPriority) ([]ZoneCustomSSL, error) { + uri := fmt.Sprintf("/zones/%s/custom_certificates/prioritize", zoneID) + params := struct { + Certificates []ZoneCustomSSLPriority `json:"certificates"` + }{ + Certificates: p, + } + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params) + if err != nil { + return nil, err + } + var r zoneCustomSSLsResponse + if err := json.Unmarshal(res, &r); err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeleteSSL deletes a custom SSL certificate from the given zone. +// +// API reference: https://api.cloudflare.com/#custom-ssl-for-a-zone-delete-an-ssl-certificate +func (api *API) DeleteSSL(ctx context.Context, zoneID, certificateID string) error { + uri := fmt.Sprintf("/zones/%s/custom_certificates/%s", zoneID, certificateID) + if _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil); err != nil { + return err + } + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/teams_accounts.go b/vendor/github.com/cloudflare/cloudflare-go/teams_accounts.go new file mode 100644 index 0000000000000..504fca03f9dc7 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/teams_accounts.go @@ -0,0 +1,133 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +type TeamsAccount struct { + GatewayTag string `json:"gateway_tag"` // Internal teams ID + ProviderName string `json:"provider_name"` // Auth provider + ID string `json:"id"` // cloudflare account ID +} + +// TeamsAccountResponse is the API response, containing information on teams +// account. +type TeamsAccountResponse struct { + Response + Result TeamsAccount `json:"result"` +} + +// TeamsConfigResponse is the API response, containing information on teams +// account config. +type TeamsConfigResponse struct { + Response + Result TeamsConfiguration `json:"result"` +} + +// TeamsConfiguration data model. +type TeamsConfiguration struct { + Settings TeamsAccountSettings `json:"settings"` + CreatedAt time.Time `json:"created_at,omitempty"` + UpdatedAt time.Time `json:"updated_at,omitempty"` +} + +type TeamsAccountSettings struct { + Antivirus *TeamsAntivirus `json:"antivirus,omitempty"` + TLSDecrypt *TeamsTLSDecrypt `json:"tls_decrypt,omitempty"` + ActivityLog *TeamsActivityLog `json:"activity_log,omitempty"` + BlockPage *TeamsBlockPage `json:"block_page,omitempty"` + FIPS *TeamsFIPS `json:"fips,omitempty"` +} + +type TeamsAntivirus struct { + EnabledDownloadPhase bool `json:"enabled_download_phase"` + EnabledUploadPhase bool `json:"enabled_upload_phase"` + FailClosed bool `json:"fail_closed"` +} + +type TeamsFIPS struct { + TLS bool `json:"tls"` +} + +type TeamsTLSDecrypt struct { + Enabled bool `json:"enabled"` +} + +type TeamsActivityLog struct { + Enabled bool `json:"enabled"` +} + +type TeamsBlockPage struct { + Enabled *bool `json:"enabled,omitempty"` + FooterText string `json:"footer_text,omitempty"` + HeaderText string `json:"header_text,omitempty"` + LogoPath string `json:"logo_path,omitempty"` + BackgroundColor string `json:"background_color,omitempty"` + Name string `json:"name,omitempty"` +} + +// TeamsAccount returns teams account information with internal and external ID. +// +// API reference: TBA +func (api *API) TeamsAccount(ctx context.Context, accountID string) (TeamsAccount, error) { + uri := fmt.Sprintf("/accounts/%s/gateway", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return TeamsAccount{}, err + } + + var teamsAccountResponse TeamsAccountResponse + err = json.Unmarshal(res, &teamsAccountResponse) + if err != nil { + return TeamsAccount{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsAccountResponse.Result, nil +} + +// TeamsAccountConfiguration returns teams account configuration. +// +// API reference: TBA +func (api *API) TeamsAccountConfiguration(ctx context.Context, accountID string) (TeamsConfiguration, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/configuration", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return TeamsConfiguration{}, err + } + + var teamsConfigResponse TeamsConfigResponse + err = json.Unmarshal(res, &teamsConfigResponse) + if err != nil { + return TeamsConfiguration{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsConfigResponse.Result, nil +} + +// TeamsAccountUpdateConfiguration updates a teams account configuration. +// +// API reference: TBA +func (api *API) TeamsAccountUpdateConfiguration(ctx context.Context, accountID string, config TeamsConfiguration) (TeamsConfiguration, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/configuration", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, config) + if err != nil { + return TeamsConfiguration{}, err + } + + var teamsConfigResponse TeamsConfigResponse + err = json.Unmarshal(res, &teamsConfigResponse) + if err != nil { + return TeamsConfiguration{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsConfigResponse.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/teams_list.go b/vendor/github.com/cloudflare/cloudflare-go/teams_list.go new file mode 100644 index 0000000000000..32b3d25bdb772 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/teams_list.go @@ -0,0 +1,221 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// TeamsList represents a Teams List. +type TeamsList struct { + ID string `json:"id,omitempty"` + Name string `json:"name"` + Type string `json:"type"` + Description string `json:"description,omitempty"` + Items []TeamsListItem `json:"items,omitempty"` + Count uint64 `json:"count,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +// TeamsListItem represents a single list item. +type TeamsListItem struct { + Value string `json:"value"` + CreatedAt *time.Time `json:"created_at,omitempty"` +} + +// PatchTeamsList represents a patch request for appending/removing list items. +type PatchTeamsList struct { + ID string `json:"id"` + Append []TeamsListItem `json:"append"` + Remove []string `json:"remove"` +} + +// TeamsListListResponse represents the response from the list +// teams lists endpoint. +type TeamsListListResponse struct { + Result []TeamsList `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// TeamsListItemsListResponse represents the response from the list +// teams list items endpoint. +type TeamsListItemsListResponse struct { + Result []TeamsListItem `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// TeamsListDetailResponse is the API response, containing a single +// teams list. +type TeamsListDetailResponse struct { + Response + Result TeamsList `json:"result"` +} + +// TeamsLists returns all lists within an account. +// +// API reference: https://api.cloudflare.com/#teams-lists-list-teams-lists +func (api *API) TeamsLists(ctx context.Context, accountID string) ([]TeamsList, ResultInfo, error) { + uri := fmt.Sprintf("/%s/%s/gateway/lists", AccountRouteRoot, accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []TeamsList{}, ResultInfo{}, err + } + + var teamsListListResponse TeamsListListResponse + err = json.Unmarshal(res, &teamsListListResponse) + if err != nil { + return []TeamsList{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsListListResponse.Result, teamsListListResponse.ResultInfo, nil +} + +// TeamsList returns a single list based on the list ID. +// +// API reference: https://api.cloudflare.com/#teams-lists-teams-list-details +func (api *API) TeamsList(ctx context.Context, accountID, listID string) (TeamsList, error) { + uri := fmt.Sprintf( + "/%s/%s/gateway/lists/%s", + AccountRouteRoot, + accountID, + listID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return TeamsList{}, err + } + + var teamsListDetailResponse TeamsListDetailResponse + err = json.Unmarshal(res, &teamsListDetailResponse) + if err != nil { + return TeamsList{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsListDetailResponse.Result, nil +} + +// TeamsListItems returns all list items for a list. +// +// API reference: https://api.cloudflare.com/#teams-lists-teams-list-items +func (api *API) TeamsListItems(ctx context.Context, accountID, listID string) ([]TeamsListItem, ResultInfo, error) { + uri := fmt.Sprintf("/%s/%s/gateway/lists/%s/items", AccountRouteRoot, accountID, listID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []TeamsListItem{}, ResultInfo{}, err + } + + var teamsListItemsListResponse TeamsListItemsListResponse + err = json.Unmarshal(res, &teamsListItemsListResponse) + if err != nil { + return []TeamsListItem{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsListItemsListResponse.Result, teamsListItemsListResponse.ResultInfo, nil +} + +// CreateTeamsList creates a new teams list. +// +// API reference: https://api.cloudflare.com/#teams-lists-create-teams-list +func (api *API) CreateTeamsList(ctx context.Context, accountID string, teamsList TeamsList) (TeamsList, error) { + uri := fmt.Sprintf("/%s/%s/gateway/lists", AccountRouteRoot, accountID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, teamsList) + if err != nil { + return TeamsList{}, err + } + + var teamsListDetailResponse TeamsListDetailResponse + err = json.Unmarshal(res, &teamsListDetailResponse) + if err != nil { + return TeamsList{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsListDetailResponse.Result, nil +} + +// UpdateTeamsList updates an existing teams list. +// +// API reference: https://api.cloudflare.com/#teams-lists-update-teams-list +func (api *API) UpdateTeamsList(ctx context.Context, accountID string, teamsList TeamsList) (TeamsList, error) { + if teamsList.ID == "" { + return TeamsList{}, errors.Errorf("teams list ID cannot be empty") + } + + uri := fmt.Sprintf( + "/%s/%s/gateway/lists/%s", + AccountRouteRoot, + accountID, + teamsList.ID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, teamsList) + if err != nil { + return TeamsList{}, err + } + + var teamsListDetailResponse TeamsListDetailResponse + err = json.Unmarshal(res, &teamsListDetailResponse) + if err != nil { + return TeamsList{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsListDetailResponse.Result, nil +} + +// PatchTeamsList updates the items in an existing teams list. +// +// API reference: https://api.cloudflare.com/#teams-lists-patch-teams-list +func (api *API) PatchTeamsList(ctx context.Context, accountID string, listPatch PatchTeamsList) (TeamsList, error) { + if listPatch.ID == "" { + return TeamsList{}, errors.Errorf("teams list ID cannot be empty") + } + + uri := fmt.Sprintf( + "/%s/%s/gateway/lists/%s", + AccountRouteRoot, + accountID, + listPatch.ID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, listPatch) + if err != nil { + return TeamsList{}, err + } + + var teamsListDetailResponse TeamsListDetailResponse + err = json.Unmarshal(res, &teamsListDetailResponse) + if err != nil { + return TeamsList{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsListDetailResponse.Result, nil +} + +// DeleteTeamsList deletes a teams list. +// +// API reference: https://api.cloudflare.com/#teams-lists-delete-teams-list +func (api *API) DeleteTeamsList(ctx context.Context, accountID, teamsListID string) error { + uri := fmt.Sprintf( + "/%s/%s/gateway/lists/%s", + AccountRouteRoot, + accountID, + teamsListID, + ) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/teams_locations.go b/vendor/github.com/cloudflare/cloudflare-go/teams_locations.go new file mode 100644 index 0000000000000..b6b95a49f1622 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/teams_locations.go @@ -0,0 +1,155 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +type TeamsLocationsListResponse struct { + Response + ResultInfo `json:"result_info"` + Result []TeamsLocation `json:"result"` +} + +type TeamsLocationDetailResponse struct { + Response + Result TeamsLocation `json:"result"` +} + +type TeamsLocationNetwork struct { + ID string `json:"id"` + Network string `json:"network"` +} + +type TeamsLocation struct { + ID string `json:"id"` + Name string `json:"name"` + Networks []TeamsLocationNetwork `json:"networks"` + PolicyIDs []string `json:"policy_ids"` + Ip string `json:"ip,omitempty"` + Subdomain string `json:"doh_subdomain"` + AnonymizedLogsEnabled bool `json:"anonymized_logs_enabled"` + IPv4Destination string `json:"ipv4_destination"` + ClientDefault bool `json:"client_default"` + + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +// TeamsLocations returns all locations within an account. +// +// API reference: https://api.cloudflare.com/#teams-locations-list-teams-locations +func (api *API) TeamsLocations(ctx context.Context, accountID string) ([]TeamsLocation, ResultInfo, error) { + uri := fmt.Sprintf("/%s/%s/gateway/locations", AccountRouteRoot, accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []TeamsLocation{}, ResultInfo{}, err + } + + var teamsLocationsListResponse TeamsLocationsListResponse + err = json.Unmarshal(res, &teamsLocationsListResponse) + if err != nil { + return []TeamsLocation{}, ResultInfo{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsLocationsListResponse.Result, teamsLocationsListResponse.ResultInfo, nil +} + +// TeamsLocation returns a single location based on the ID. +// +// API reference: https://api.cloudflare.com/#teams-locations-teams-location-details +func (api *API) TeamsLocation(ctx context.Context, accountID, locationID string) (TeamsLocation, error) { + uri := fmt.Sprintf( + "/%s/%s/gateway/locations/%s", + AccountRouteRoot, + accountID, + locationID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return TeamsLocation{}, err + } + + var teamsLocationDetailResponse TeamsLocationDetailResponse + err = json.Unmarshal(res, &teamsLocationDetailResponse) + if err != nil { + return TeamsLocation{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsLocationDetailResponse.Result, nil +} + +// CreateTeamsLocation creates a new teams location. +// +// API reference: https://api.cloudflare.com/#teams-locations-create-teams-location +func (api *API) CreateTeamsLocation(ctx context.Context, accountID string, teamsLocation TeamsLocation) (TeamsLocation, error) { + uri := fmt.Sprintf("/%s/%s/gateway/locations", AccountRouteRoot, accountID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, teamsLocation) + if err != nil { + return TeamsLocation{}, err + } + + var teamsLocationDetailResponse TeamsLocationDetailResponse + err = json.Unmarshal(res, &teamsLocationDetailResponse) + if err != nil { + return TeamsLocation{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsLocationDetailResponse.Result, nil +} + +// UpdateTeamsLocation updates an existing teams location. +// +// API reference: https://api.cloudflare.com/#teams-locations-update-teams-location +func (api *API) UpdateTeamsLocation(ctx context.Context, accountID string, teamsLocation TeamsLocation) (TeamsLocation, error) { + if teamsLocation.ID == "" { + return TeamsLocation{}, errors.Errorf("teams location ID cannot be empty") + } + + uri := fmt.Sprintf( + "/%s/%s/gateway/locations/%s", + AccountRouteRoot, + accountID, + teamsLocation.ID, + ) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, teamsLocation) + if err != nil { + return TeamsLocation{}, err + } + + var teamsLocationDetailResponse TeamsLocationDetailResponse + err = json.Unmarshal(res, &teamsLocationDetailResponse) + if err != nil { + return TeamsLocation{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsLocationDetailResponse.Result, nil +} + +// DeleteTeamsLocation deletes a teams location. +// +// API reference: https://api.cloudflare.com/#teams-locations-delete-teams-location +func (api *API) DeleteTeamsLocation(ctx context.Context, accountID, teamsLocationID string) error { + uri := fmt.Sprintf( + "/%s/%s/gateway/locations/%s", + AccountRouteRoot, + accountID, + teamsLocationID, + ) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/teams_rules.go b/vendor/github.com/cloudflare/cloudflare-go/teams_rules.go new file mode 100644 index 0000000000000..177cf2d6cb5bc --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/teams_rules.go @@ -0,0 +1,239 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +type TeamsRuleSettings struct { + // Enable block page on rules with action block + BlockPageEnabled bool `json:"block_page_enabled"` + + // show this string at block page caused by this rule + BlockReason string `json:"block_reason"` + + // list of ipv4 or ipv6 ips to override with, when action is set to dns override + OverrideIPs []string `json:"override_ips"` + + // host name to override with when action is set to dns override. Can not be used with OverrideIPs + OverrideHost string `json:"override_host"` + + // settings for l4(network) level overrides + L4Override *TeamsL4OverrideSettings `json:"l4override"` + + // settings for browser isolation actions + BISOAdminControls *TeamsBISOAdminControlSettings `json:"biso_admin_controls"` +} + +// TeamsL4OverrideSettings used in l4 filter type rule with action set to override +type TeamsL4OverrideSettings struct { + IP string `json:"ip,omitempty"` + Port int `json:"port,omitempty"` +} + +type TeamsBISOAdminControlSettings struct { + DisablePrinting bool `json:"dp"` + DisableCopyPaste bool `json:"dcp"` +} + +type TeamsFilterType string + +type TeamsGatewayAction string + +const ( + HttpFilter TeamsFilterType = "http" + DnsFilter TeamsFilterType = "dns" + L4Filter TeamsFilterType = "l4" +) + +const ( + Allow TeamsGatewayAction = "allow" + Block TeamsGatewayAction = "block" + SafeSearch TeamsGatewayAction = "safesearch" + YTRestricted TeamsGatewayAction = "ytrestricted" + On TeamsGatewayAction = "on" + Off TeamsGatewayAction = "off" + Scan TeamsGatewayAction = "scan" + NoScan TeamsGatewayAction = "noscan" + Isolate TeamsGatewayAction = "isolate" + NoIsolate TeamsGatewayAction = "noisolate" + Override TeamsGatewayAction = "override" + L4Override TeamsGatewayAction = "l4_override" +) + +func TeamsRulesActionValues() []string { + return []string{ + string(Allow), + string(Block), + string(SafeSearch), + string(YTRestricted), + string(On), + string(Off), + string(Scan), + string(NoScan), + string(Isolate), + string(NoIsolate), + string(Override), + string(L4Override), + } +} + +// TeamsRule represents an Teams wirefilter rule. +type TeamsRule struct { + ID string `json:"id,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` + DeletedAt *time.Time `json:"deleted_at,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + Precedence uint64 `json:"precedence"` + Enabled bool `json:"enabled"` + Action TeamsGatewayAction `json:"action"` + Filters []TeamsFilterType `json:"filters"` + Traffic string `json:"traffic"` + Identity string `json:"identity"` + Version uint64 `json:"version"` + RuleSettings TeamsRuleSettings `json:"rule_settings,omitempty"` +} + +// TeamsRuleResponse is the API response, containing a single rule. +type TeamsRuleResponse struct { + Response + Result TeamsRule `json:"result"` +} + +// TeamsRuleResponse is the API response, containing an array of rules. +type TeamsRulesResponse struct { + Response + Result []TeamsRule `json:"result"` +} + +// TeamsRulePatchRequest is used to patch an existing rule. +type TeamsRulePatchRequest struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Precedence uint64 `json:"precedence"` + Enabled bool `json:"enabled"` + Action TeamsGatewayAction `json:"action"` + RuleSettings TeamsRuleSettings `json:"rule_settings,omitempty"` +} + +// TeamsRules returns all rules within an account. +// +// API reference: https://api.cloudflare.com/#teams-rules-properties +func (api *API) TeamsRules(ctx context.Context, accountID string) ([]TeamsRule, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/rules", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []TeamsRule{}, err + } + + var teamsRulesResponse TeamsRulesResponse + err = json.Unmarshal(res, &teamsRulesResponse) + if err != nil { + return []TeamsRule{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsRulesResponse.Result, nil +} + +// TeamsRule returns the rule with rule ID in the URL. +// +// API reference: https://api.cloudflare.com/#teams-rules-properties +func (api *API) TeamsRule(ctx context.Context, accountID string, ruleId string) (TeamsRule, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/rules/%s", accountID, ruleId) + + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return TeamsRule{}, err + } + + var teamsRuleResponse TeamsRuleResponse + err = json.Unmarshal(res, &teamsRuleResponse) + if err != nil { + return TeamsRule{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsRuleResponse.Result, nil +} + +// TeamsCreateRule creates a rule with wirefilter expression. +// +// API reference: https://api.cloudflare.com/#teams-rules-properties +func (api *API) TeamsCreateRule(ctx context.Context, accountID string, rule TeamsRule) (TeamsRule, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/rules", accountID) + + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, rule) + if err != nil { + return TeamsRule{}, err + } + + var teamsRuleResponse TeamsRuleResponse + err = json.Unmarshal(res, &teamsRuleResponse) + if err != nil { + return TeamsRule{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsRuleResponse.Result, nil +} + +// TeamsUpdateRule updates a rule with wirefilter expression. +// +// API reference: https://api.cloudflare.com/#teams-rules-properties +func (api *API) TeamsUpdateRule(ctx context.Context, accountID string, ruleId string, rule TeamsRule) (TeamsRule, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/rules/%s", accountID, ruleId) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, rule) + if err != nil { + return TeamsRule{}, err + } + + var teamsRuleResponse TeamsRuleResponse + err = json.Unmarshal(res, &teamsRuleResponse) + if err != nil { + return TeamsRule{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsRuleResponse.Result, nil +} + +// TeamsPatchRule patches a rule associated values. +// +// API reference: https://api.cloudflare.com/#teams-rules-properties +func (api *API) TeamsPatchRule(ctx context.Context, accountID string, ruleId string, rule TeamsRulePatchRequest) (TeamsRule, error) { + uri := fmt.Sprintf("/accounts/%s/gateway/rules/%s", accountID, ruleId) + + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, rule) + if err != nil { + return TeamsRule{}, err + } + + var teamsRuleResponse TeamsRuleResponse + err = json.Unmarshal(res, &teamsRuleResponse) + if err != nil { + return TeamsRule{}, errors.Wrap(err, errUnmarshalError) + } + + return teamsRuleResponse.Result, nil +} + +// TeamsDeleteRule deletes a rule. +// +// API reference: https://api.cloudflare.com/#teams-rules-properties +func (api *API) TeamsDeleteRule(ctx context.Context, accountID string, ruleId string) error { + uri := fmt.Sprintf("/accounts/%s/gateway/rules/%s", accountID, ruleId) + + _, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/universal_ssl.go b/vendor/github.com/cloudflare/cloudflare-go/universal_ssl.go new file mode 100644 index 0000000000000..7e233c43da43c --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/universal_ssl.go @@ -0,0 +1,116 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +// UniversalSSLSetting represents a universal ssl setting's properties. +type UniversalSSLSetting struct { + Enabled bool `json:"enabled"` +} + +type universalSSLSettingResponse struct { + Response + Result UniversalSSLSetting `json:"result"` +} + +// UniversalSSLVerificationDetails represents a universal ssl verification's properties. +type UniversalSSLVerificationDetails struct { + CertificateStatus string `json:"certificate_status"` + VerificationType string `json:"verification_type"` + ValidationMethod string `json:"validation_method"` + CertPackUUID string `json:"cert_pack_uuid"` + VerificationStatus bool `json:"verification_status"` + BrandCheck bool `json:"brand_check"` + VerificationInfo UniversalSSLVerificationInfo `json:"verification_info"` +} + +// UniversalSSLVerificationInfo represents DCV record. +type UniversalSSLVerificationInfo struct { + RecordName string `json:"record_name"` + RecordTarget string `json:"record_target"` +} + +type universalSSLVerificationResponse struct { + Response + Result []UniversalSSLVerificationDetails `json:"result"` +} + +type UniversalSSLCertificatePackValidationMethodSetting struct { + ValidationMethod string `json:"validation_method"` +} + +type universalSSLCertificatePackValidationMethodSettingResponse struct { + Response + Result UniversalSSLCertificatePackValidationMethodSetting `json:"result"` +} + +// UniversalSSLSettingDetails returns the details for a universal ssl setting +// +// API reference: https://api.cloudflare.com/#universal-ssl-settings-for-a-zone-universal-ssl-settings-details +func (api *API) UniversalSSLSettingDetails(ctx context.Context, zoneID string) (UniversalSSLSetting, error) { + uri := fmt.Sprintf("/zones/%s/ssl/universal/settings", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return UniversalSSLSetting{}, err + } + var r universalSSLSettingResponse + if err := json.Unmarshal(res, &r); err != nil { + return UniversalSSLSetting{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// EditUniversalSSLSetting edits the universal ssl setting for a zone +// +// API reference: https://api.cloudflare.com/#universal-ssl-settings-for-a-zone-edit-universal-ssl-settings +func (api *API) EditUniversalSSLSetting(ctx context.Context, zoneID string, setting UniversalSSLSetting) (UniversalSSLSetting, error) { + uri := fmt.Sprintf("/zones/%s/ssl/universal/settings", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, setting) + if err != nil { + return UniversalSSLSetting{}, err + } + var r universalSSLSettingResponse + if err := json.Unmarshal(res, &r); err != nil { + return UniversalSSLSetting{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil + +} + +// UniversalSSLVerificationDetails returns the details for a universal ssl verification +// +// API reference: https://api.cloudflare.com/#ssl-verification-ssl-verification-details +func (api *API) UniversalSSLVerificationDetails(ctx context.Context, zoneID string) ([]UniversalSSLVerificationDetails, error) { + uri := fmt.Sprintf("/zones/%s/ssl/verification", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []UniversalSSLVerificationDetails{}, err + } + var r universalSSLVerificationResponse + if err := json.Unmarshal(res, &r); err != nil { + return []UniversalSSLVerificationDetails{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateUniversalSSLCertificatePackValidationMethod changes the validation method for a certificate pack +// +// API reference: https://api.cloudflare.com/#ssl-verification-ssl-verification-details +func (api *API) UpdateUniversalSSLCertificatePackValidationMethod(ctx context.Context, zoneID string, certPackUUID string, setting UniversalSSLCertificatePackValidationMethodSetting) (UniversalSSLCertificatePackValidationMethodSetting, error) { + uri := fmt.Sprintf("/zones/%s/ssl/verification/%s", zoneID, certPackUUID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, setting) + if err != nil { + return UniversalSSLCertificatePackValidationMethodSetting{}, err + } + var r universalSSLCertificatePackValidationMethodSettingResponse + if err := json.Unmarshal(res, &r); err != nil { + return UniversalSSLCertificatePackValidationMethodSetting{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/user.go b/vendor/github.com/cloudflare/cloudflare-go/user.go new file mode 100644 index 0000000000000..50b7f2071cff5 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/user.go @@ -0,0 +1,115 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// User describes a user account. +type User struct { + ID string `json:"id,omitempty"` + Email string `json:"email,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + Username string `json:"username,omitempty"` + Telephone string `json:"telephone,omitempty"` + Country string `json:"country,omitempty"` + Zipcode string `json:"zipcode,omitempty"` + CreatedOn *time.Time `json:"created_on,omitempty"` + ModifiedOn *time.Time `json:"modified_on,omitempty"` + APIKey string `json:"api_key,omitempty"` + TwoFA bool `json:"two_factor_authentication_enabled,omitempty"` + Betas []string `json:"betas,omitempty"` + Accounts []Account `json:"organizations,omitempty"` +} + +// UserResponse wraps a response containing User accounts. +type UserResponse struct { + Response + Result User `json:"result"` +} + +// userBillingProfileResponse wraps a response containing Billing Profile information. +type userBillingProfileResponse struct { + Response + Result UserBillingProfile +} + +// UserBillingProfile contains Billing Profile information. +type UserBillingProfile struct { + ID string `json:"id,omitempty"` + FirstName string `json:"first_name,omitempty"` + LastName string `json:"last_name,omitempty"` + Address string `json:"address,omitempty"` + Address2 string `json:"address2,omitempty"` + Company string `json:"company,omitempty"` + City string `json:"city,omitempty"` + State string `json:"state,omitempty"` + ZipCode string `json:"zipcode,omitempty"` + Country string `json:"country,omitempty"` + Telephone string `json:"telephone,omitempty"` + CardNumber string `json:"card_number,omitempty"` + CardExpiryYear int `json:"card_expiry_year,omitempty"` + CardExpiryMonth int `json:"card_expiry_month,omitempty"` + VAT string `json:"vat,omitempty"` + CreatedOn *time.Time `json:"created_on,omitempty"` + EditedOn *time.Time `json:"edited_on,omitempty"` +} + +// UserDetails provides information about the logged-in user. +// +// API reference: https://api.cloudflare.com/#user-user-details +func (api *API) UserDetails(ctx context.Context) (User, error) { + var r UserResponse + res, err := api.makeRequestContext(ctx, http.MethodGet, "/user", nil) + if err != nil { + return User{}, err + } + + err = json.Unmarshal(res, &r) + if err != nil { + return User{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// UpdateUser updates the properties of the given user. +// +// API reference: https://api.cloudflare.com/#user-update-user +func (api *API) UpdateUser(ctx context.Context, user *User) (User, error) { + var r UserResponse + res, err := api.makeRequestContext(ctx, http.MethodPatch, "/user", user) + if err != nil { + return User{}, err + } + + err = json.Unmarshal(res, &r) + if err != nil { + return User{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// UserBillingProfile returns the billing profile of the user. +// +// API reference: https://api.cloudflare.com/#user-billing-profile +func (api *API) UserBillingProfile(ctx context.Context) (UserBillingProfile, error) { + var r userBillingProfileResponse + res, err := api.makeRequestContext(ctx, http.MethodGet, "/user/billing/profile", nil) + if err != nil { + return UserBillingProfile{}, err + } + + err = json.Unmarshal(res, &r) + if err != nil { + return UserBillingProfile{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/user_agent.go b/vendor/github.com/cloudflare/cloudflare-go/user_agent.go new file mode 100644 index 0000000000000..bb48ef8158ba5 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/user_agent.go @@ -0,0 +1,151 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + + "github.com/pkg/errors" +) + +// UserAgentRule represents a User-Agent Block. These rules can be used to +// challenge, block or whitelist specific User-Agents for a given zone. +type UserAgentRule struct { + ID string `json:"id"` + Description string `json:"description"` + Mode string `json:"mode"` + Configuration UserAgentRuleConfig `json:"configuration"` + Paused bool `json:"paused"` +} + +// UserAgentRuleConfig represents a Zone Lockdown config, which comprises +// a Target ("ip" or "ip_range") and a Value (an IP address or IP+mask, +// respectively.) +type UserAgentRuleConfig ZoneLockdownConfig + +// UserAgentRuleResponse represents a response from the Zone Lockdown endpoint. +type UserAgentRuleResponse struct { + Result UserAgentRule `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// UserAgentRuleListResponse represents a response from the List Zone Lockdown endpoint. +type UserAgentRuleListResponse struct { + Result []UserAgentRule `json:"result"` + Response + ResultInfo `json:"result_info"` +} + +// CreateUserAgentRule creates a User-Agent Block rule for the given zone ID. +// +// API reference: https://api.cloudflare.com/#user-agent-blocking-rules-create-a-useragent-rule +func (api *API) CreateUserAgentRule(ctx context.Context, zoneID string, ld UserAgentRule) (*UserAgentRuleResponse, error) { + switch ld.Mode { + case "block", "challenge", "js_challenge", "whitelist": + break + default: + return nil, errors.New(`the User-Agent Block rule mode must be one of "block", "challenge", "js_challenge", "whitelist"`) + } + + uri := fmt.Sprintf("/zones/%s/firewall/ua_rules", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, ld) + if err != nil { + return nil, err + } + + response := &UserAgentRuleResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// UpdateUserAgentRule updates a User-Agent Block rule (based on the ID) for the given zone ID. +// +// API reference: https://api.cloudflare.com/#user-agent-blocking-rules-update-useragent-rule +func (api *API) UpdateUserAgentRule(ctx context.Context, zoneID string, id string, ld UserAgentRule) (*UserAgentRuleResponse, error) { + uri := fmt.Sprintf("/zones/%s/firewall/ua_rules/%s", zoneID, id) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, ld) + if err != nil { + return nil, err + } + + response := &UserAgentRuleResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// DeleteUserAgentRule deletes a User-Agent Block rule (based on the ID) for the given zone ID. +// +// API reference: https://api.cloudflare.com/#user-agent-blocking-rules-delete-useragent-rule +func (api *API) DeleteUserAgentRule(ctx context.Context, zoneID string, id string) (*UserAgentRuleResponse, error) { + uri := fmt.Sprintf("/zones/%s/firewall/ua_rules/%s", zoneID, id) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return nil, err + } + + response := &UserAgentRuleResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// UserAgentRule retrieves a User-Agent Block rule (based on the ID) for the given zone ID. +// +// API reference: https://api.cloudflare.com/#user-agent-blocking-rules-useragent-rule-details +func (api *API) UserAgentRule(ctx context.Context, zoneID string, id string) (*UserAgentRuleResponse, error) { + uri := fmt.Sprintf("/zones/%s/firewall/ua_rules/%s", zoneID, id) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + response := &UserAgentRuleResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// ListUserAgentRules retrieves a list of User-Agent Block rules for a given zone ID by page number. +// +// API reference: https://api.cloudflare.com/#user-agent-blocking-rules-list-useragent-rules +func (api *API) ListUserAgentRules(ctx context.Context, zoneID string, page int) (*UserAgentRuleListResponse, error) { + v := url.Values{} + if page <= 0 { + page = 1 + } + + v.Set("page", strconv.Itoa(page)) + v.Set("per_page", strconv.Itoa(100)) + + uri := fmt.Sprintf("/zones/%s/firewall/ua_rules?%s", zoneID, v.Encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + response := &UserAgentRuleListResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/virtualdns.go b/vendor/github.com/cloudflare/cloudflare-go/virtualdns.go new file mode 100644 index 0000000000000..08058447cca6b --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/virtualdns.go @@ -0,0 +1,197 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "github.com/pkg/errors" +) + +// VirtualDNS represents a Virtual DNS configuration. +type VirtualDNS struct { + ID string `json:"id"` + Name string `json:"name"` + OriginIPs []string `json:"origin_ips"` + VirtualDNSIPs []string `json:"virtual_dns_ips"` + MinimumCacheTTL uint `json:"minimum_cache_ttl"` + MaximumCacheTTL uint `json:"maximum_cache_ttl"` + DeprecateAnyRequests bool `json:"deprecate_any_requests"` + ModifiedOn string `json:"modified_on"` +} + +// VirtualDNSAnalyticsMetrics represents a group of aggregated Virtual DNS metrics. +type VirtualDNSAnalyticsMetrics 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"` +} + +// VirtualDNSAnalytics represents a set of aggregated Virtual DNS metrics. +// TODO: Add the queried data and not only the aggregated values. +type VirtualDNSAnalytics struct { + Totals VirtualDNSAnalyticsMetrics `json:"totals"` + Min VirtualDNSAnalyticsMetrics `json:"min"` + Max VirtualDNSAnalyticsMetrics `json:"max"` +} + +// VirtualDNSUserAnalyticsOptions represents range and dimension selection on analytics endpoint +type VirtualDNSUserAnalyticsOptions struct { + Metrics []string + Since *time.Time + Until *time.Time +} + +// VirtualDNSResponse represents a Virtual DNS response. +type VirtualDNSResponse struct { + Response + Result *VirtualDNS `json:"result"` +} + +// VirtualDNSListResponse represents an array of Virtual DNS responses. +type VirtualDNSListResponse struct { + Response + Result []*VirtualDNS `json:"result"` +} + +// VirtualDNSAnalyticsResponse represents a Virtual DNS analytics response. +type VirtualDNSAnalyticsResponse struct { + Response + Result VirtualDNSAnalytics `json:"result"` +} + +// CreateVirtualDNS creates a new Virtual DNS cluster. +// +// API reference: https://api.cloudflare.com/#virtual-dns-users--create-a-virtual-dns-cluster +func (api *API) CreateVirtualDNS(ctx context.Context, v *VirtualDNS) (*VirtualDNS, error) { + uri := fmt.Sprintf("%s/virtual_dns", api.userBaseURL("/user")) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, v) + if err != nil { + return nil, err + } + + response := &VirtualDNSResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response.Result, nil +} + +// VirtualDNS fetches a single virtual DNS cluster. +// +// API reference: https://api.cloudflare.com/#virtual-dns-users--get-a-virtual-dns-cluster +func (api *API) VirtualDNS(ctx context.Context, virtualDNSID string) (*VirtualDNS, error) { + uri := fmt.Sprintf("%s/virtual_dns/%s", api.userBaseURL("/user"), virtualDNSID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + response := &VirtualDNSResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response.Result, nil +} + +// ListVirtualDNS lists the virtual DNS clusters associated with an account. +// +// API reference: https://api.cloudflare.com/#virtual-dns-users--get-virtual-dns-clusters +func (api *API) ListVirtualDNS(ctx context.Context) ([]*VirtualDNS, error) { + uri := fmt.Sprintf("%s/virtual_dns", api.userBaseURL("/user")) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + response := &VirtualDNSListResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response.Result, nil +} + +// UpdateVirtualDNS updates a Virtual DNS cluster. +// +// API reference: https://api.cloudflare.com/#virtual-dns-users--modify-a-virtual-dns-cluster +func (api *API) UpdateVirtualDNS(ctx context.Context, virtualDNSID string, vv VirtualDNS) error { + uri := fmt.Sprintf("%s/virtual_dns/%s", api.userBaseURL("/user"), virtualDNSID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, vv) + if err != nil { + return err + } + + response := &VirtualDNSResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + + return nil +} + +// DeleteVirtualDNS deletes a Virtual DNS cluster. Note that this cannot be +// undone, and will stop all traffic to that cluster. +// +// API reference: https://api.cloudflare.com/#virtual-dns-users--delete-a-virtual-dns-cluster +func (api *API) DeleteVirtualDNS(ctx context.Context, virtualDNSID string) error { + uri := fmt.Sprintf("%s/virtual_dns/%s", api.userBaseURL("/user"), virtualDNSID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + + response := &VirtualDNSResponse{} + 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 VirtualDNSUserAnalyticsOptions) 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() +} + +// VirtualDNSUserAnalytics retrieves analytics report for a specified dimension and time range +func (api *API) VirtualDNSUserAnalytics(ctx context.Context, virtualDNSID string, o VirtualDNSUserAnalyticsOptions) (VirtualDNSAnalytics, error) { + uri := fmt.Sprintf("%s/virtual_dns/%s/dns_analytics/report?%s", api.userBaseURL("/user"), virtualDNSID, o.encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return VirtualDNSAnalytics{}, err + } + + response := VirtualDNSAnalyticsResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return VirtualDNSAnalytics{}, errors.Wrap(err, errUnmarshalError) + } + + return response.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/waf.go b/vendor/github.com/cloudflare/cloudflare-go/waf.go new file mode 100644 index 0000000000000..596ef37f279ba --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/waf.go @@ -0,0 +1,353 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + + "github.com/pkg/errors" +) + +// WAFPackage represents a WAF package configuration. +type WAFPackage struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + ZoneID string `json:"zone_id"` + DetectionMode string `json:"detection_mode"` + Sensitivity string `json:"sensitivity"` + ActionMode string `json:"action_mode"` +} + +// WAFPackagesResponse represents the response from the WAF packages endpoint. +type WAFPackagesResponse struct { + Response + Result []WAFPackage `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// WAFPackageResponse represents the response from the WAF package endpoint. +type WAFPackageResponse struct { + Response + Result WAFPackage `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// WAFPackageOptions represents options to edit a WAF package. +type WAFPackageOptions struct { + Sensitivity string `json:"sensitivity,omitempty"` + ActionMode string `json:"action_mode,omitempty"` +} + +// WAFGroup represents a WAF rule group. +type WAFGroup struct { + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + RulesCount int `json:"rules_count"` + ModifiedRulesCount int `json:"modified_rules_count"` + PackageID string `json:"package_id"` + Mode string `json:"mode"` + AllowedModes []string `json:"allowed_modes"` +} + +// WAFGroupsResponse represents the response from the WAF groups endpoint. +type WAFGroupsResponse struct { + Response + Result []WAFGroup `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// WAFGroupResponse represents the response from the WAF group endpoint. +type WAFGroupResponse struct { + Response + Result WAFGroup `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// WAFRule represents a WAF rule. +type WAFRule struct { + ID string `json:"id"` + Description string `json:"description"` + Priority string `json:"priority"` + PackageID string `json:"package_id"` + Group struct { + ID string `json:"id"` + Name string `json:"name"` + } `json:"group"` + Mode string `json:"mode"` + DefaultMode string `json:"default_mode"` + AllowedModes []string `json:"allowed_modes"` +} + +// WAFRulesResponse represents the response from the WAF rules endpoint. +type WAFRulesResponse struct { + Response + Result []WAFRule `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// WAFRuleResponse represents the response from the WAF rule endpoint. +type WAFRuleResponse struct { + Response + Result WAFRule `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// WAFRuleOptions is a subset of WAFRule, for editable options. +type WAFRuleOptions struct { + Mode string `json:"mode"` +} + +// ListWAFPackages returns a slice of the WAF packages for the given zone. +// +// API Reference: https://api.cloudflare.com/#waf-rule-packages-list-firewall-packages +func (api *API) ListWAFPackages(ctx context.Context, zoneID string) ([]WAFPackage, error) { + // Construct a query string + v := url.Values{} + // Request as many WAF packages as possible per page - API max is 100 + v.Set("per_page", "100") + + var packages []WAFPackage + var res []byte + var err error + page := 1 + + // Loop over makeRequest until what we've fetched all records + for { + v.Set("page", strconv.Itoa(page)) + uri := fmt.Sprintf("/zones/%s/firewall/waf/packages?%s", zoneID, v.Encode()) + res, err = api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []WAFPackage{}, err + } + + var p WAFPackagesResponse + err = json.Unmarshal(res, &p) + if err != nil { + return []WAFPackage{}, errors.Wrap(err, errUnmarshalError) + } + + if !p.Success { + // TODO: Provide an actual error message instead of always returning nil + return []WAFPackage{}, err + } + + packages = append(packages, p.Result...) + if p.ResultInfo.Page >= p.ResultInfo.TotalPages { + break + } + + // Loop around and fetch the next page + page++ + } + + return packages, nil +} + +// WAFPackage returns a WAF package for the given zone. +// +// API Reference: https://api.cloudflare.com/#waf-rule-packages-firewall-package-details +func (api *API) WAFPackage(ctx context.Context, zoneID, packageID string) (WAFPackage, error) { + uri := fmt.Sprintf("/zones/%s/firewall/waf/packages/%s", zoneID, packageID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WAFPackage{}, err + } + + var r WAFPackageResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WAFPackage{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// UpdateWAFPackage lets you update the a WAF Package. +// +// API Reference: https://api.cloudflare.com/#waf-rule-packages-edit-firewall-package +func (api *API) UpdateWAFPackage(ctx context.Context, zoneID, packageID string, opts WAFPackageOptions) (WAFPackage, error) { + uri := fmt.Sprintf("/zones/%s/firewall/waf/packages/%s", zoneID, packageID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, opts) + if err != nil { + return WAFPackage{}, err + } + + var r WAFPackageResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WAFPackage{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListWAFGroups returns a slice of the WAF groups for the given WAF package. +// +// API Reference: https://api.cloudflare.com/#waf-rule-groups-list-rule-groups +func (api *API) ListWAFGroups(ctx context.Context, zoneID, packageID string) ([]WAFGroup, error) { + // Construct a query string + v := url.Values{} + // Request as many WAF groups as possible per page - API max is 100 + v.Set("per_page", "100") + + var groups []WAFGroup + var res []byte + var err error + page := 1 + + // Loop over makeRequest until what we've fetched all records + for { + v.Set("page", strconv.Itoa(page)) + uri := fmt.Sprintf("/zones/%s/firewall/waf/packages/%s/groups?%s", zoneID, packageID, v.Encode()) + res, err = api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []WAFGroup{}, err + } + + var r WAFGroupsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []WAFGroup{}, errors.Wrap(err, errUnmarshalError) + } + + if !r.Success { + // TODO: Provide an actual error message instead of always returning nil + return []WAFGroup{}, err + } + + groups = append(groups, r.Result...) + if r.ResultInfo.Page >= r.ResultInfo.TotalPages { + break + } + + // Loop around and fetch the next page + page++ + } + return groups, nil +} + +// WAFGroup returns a WAF rule group from the given WAF package. +// +// API Reference: https://api.cloudflare.com/#waf-rule-groups-rule-group-details +func (api *API) WAFGroup(ctx context.Context, zoneID, packageID, groupID string) (WAFGroup, error) { + uri := fmt.Sprintf("/zones/%s/firewall/waf/packages/%s/groups/%s", zoneID, packageID, groupID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WAFGroup{}, err + } + + var r WAFGroupResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WAFGroup{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// UpdateWAFGroup lets you update the mode of a WAF Group. +// +// API Reference: https://api.cloudflare.com/#waf-rule-groups-edit-rule-group +func (api *API) UpdateWAFGroup(ctx context.Context, zoneID, packageID, groupID, mode string) (WAFGroup, error) { + opts := WAFRuleOptions{Mode: mode} + uri := fmt.Sprintf("/zones/%s/firewall/waf/packages/%s/groups/%s", zoneID, packageID, groupID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, opts) + if err != nil { + return WAFGroup{}, err + } + + var r WAFGroupResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WAFGroup{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ListWAFRules returns a slice of the WAF rules for the given WAF package. +// +// API Reference: https://api.cloudflare.com/#waf-rules-list-rules +func (api *API) ListWAFRules(ctx context.Context, zoneID, packageID string) ([]WAFRule, error) { + // Construct a query string + v := url.Values{} + // Request as many WAF rules as possible per page - API max is 100 + v.Set("per_page", "100") + + var rules []WAFRule + var res []byte + var err error + page := 1 + + // Loop over makeRequest until what we've fetched all records + for { + v.Set("page", strconv.Itoa(page)) + uri := fmt.Sprintf("/zones/%s/firewall/waf/packages/%s/rules?%s", zoneID, packageID, v.Encode()) + res, err = api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []WAFRule{}, err + } + + var r WAFRulesResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []WAFRule{}, errors.Wrap(err, errUnmarshalError) + } + + if !r.Success { + // TODO: Provide an actual error message instead of always returning nil + return []WAFRule{}, err + } + + rules = append(rules, r.Result...) + if r.ResultInfo.Page >= r.ResultInfo.TotalPages { + break + } + + // Loop around and fetch the next page + page++ + } + + return rules, nil +} + +// WAFRule returns a WAF rule from the given WAF package. +// +// API Reference: https://api.cloudflare.com/#waf-rules-rule-details +func (api *API) WAFRule(ctx context.Context, zoneID, packageID, ruleID string) (WAFRule, error) { + uri := fmt.Sprintf("/zones/%s/firewall/waf/packages/%s/rules/%s", zoneID, packageID, ruleID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WAFRule{}, err + } + + var r WAFRuleResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WAFRule{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// UpdateWAFRule lets you update the mode of a WAF Rule. +// +// API Reference: https://api.cloudflare.com/#waf-rules-edit-rule +func (api *API) UpdateWAFRule(ctx context.Context, zoneID, packageID, ruleID, mode string) (WAFRule, error) { + opts := WAFRuleOptions{Mode: mode} + uri := fmt.Sprintf("/zones/%s/firewall/waf/packages/%s/rules/%s", zoneID, packageID, ruleID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, opts) + if err != nil { + return WAFRule{}, err + } + + var r WAFRuleResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WAFRule{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/waf_overrides.go b/vendor/github.com/cloudflare/cloudflare-go/waf_overrides.go new file mode 100644 index 0000000000000..d061425c25f8d --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/waf_overrides.go @@ -0,0 +1,139 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +// WAFOverridesResponse represents the response form the WAF overrides endpoint. +type WAFOverridesResponse struct { + Response + Result []WAFOverride `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// WAFOverrideResponse represents the response form the WAF override endpoint. +type WAFOverrideResponse struct { + Response + Result WAFOverride `json:"result"` + ResultInfo ResultInfo `json:"result_info"` +} + +// WAFOverride represents a WAF override. +type WAFOverride struct { + ID string `json:"id,omitempty"` + Description string `json:"description"` + URLs []string `json:"urls"` + Priority int `json:"priority"` + Groups map[string]string `json:"groups"` + RewriteAction map[string]string `json:"rewrite_action"` + Rules map[string]string `json:"rules"` + Paused bool `json:"paused"` +} + +// ListWAFOverrides returns a slice of the WAF overrides. +// +// API Reference: https://api.cloudflare.com/#waf-overrides-list-uri-controlled-waf-configurations +func (api *API) ListWAFOverrides(ctx context.Context, zoneID string) ([]WAFOverride, error) { + var overrides []WAFOverride + var res []byte + var err error + + uri := fmt.Sprintf("/zones/%s/firewall/waf/overrides", zoneID) + res, err = api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []WAFOverride{}, err + } + + var r WAFOverridesResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []WAFOverride{}, errors.Wrap(err, errUnmarshalError) + } + + if !r.Success { + // TODO: Provide an actual error message instead of always returning nil + return []WAFOverride{}, err + } + + for ri := range r.Result { + overrides = append(overrides, r.Result[ri]) + } + return overrides, nil +} + +// WAFOverride returns a WAF override from the given override ID. +// +// API Reference: https://api.cloudflare.com/#waf-overrides-uri-controlled-waf-configuration-details +func (api *API) WAFOverride(ctx context.Context, zoneID, overrideID string) (WAFOverride, error) { + uri := fmt.Sprintf("/zones/%s/firewall/waf/overrides/%s", zoneID, overrideID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WAFOverride{}, err + } + + var r WAFOverrideResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WAFOverride{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// CreateWAFOverride creates a new WAF override. +// +// API reference: https://api.cloudflare.com/#waf-overrides-create-a-uri-controlled-waf-configuration +func (api *API) CreateWAFOverride(ctx context.Context, zoneID string, override WAFOverride) (WAFOverride, error) { + uri := fmt.Sprintf("/zones/%s/firewall/waf/overrides", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, override) + if err != nil { + return WAFOverride{}, err + } + var r WAFOverrideResponse + if err := json.Unmarshal(res, &r); err != nil { + return WAFOverride{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateWAFOverride updates an existing WAF override. +// +// API reference: https://api.cloudflare.com/#waf-overrides-update-uri-controlled-waf-configuration +func (api *API) UpdateWAFOverride(ctx context.Context, zoneID, overrideID string, override WAFOverride) (WAFOverride, error) { + uri := fmt.Sprintf("/zones/%s/firewall/waf/overrides/%s", zoneID, overrideID) + + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, override) + if err != nil { + return WAFOverride{}, err + } + + var r WAFOverrideResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WAFOverride{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// DeleteWAFOverride deletes a WAF override for a zone. +// +// API reference: https://api.cloudflare.com/#waf-overrides-delete-lockdown-rule +func (api *API) DeleteWAFOverride(ctx context.Context, zoneID, overrideID string) error { + uri := fmt.Sprintf("/zones/%s/firewall/waf/overrides/%s", zoneID, overrideID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + var r WAFOverrideResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/waiting_room.go b/vendor/github.com/cloudflare/cloudflare-go/waiting_room.go new file mode 100644 index 0000000000000..7964dbf7b50f9 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/waiting_room.go @@ -0,0 +1,146 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// WaitingRoom describes a WaitingRoom object. +type WaitingRoom struct { + ID string `json:"id,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty"` + Name string `json:"name"` + Description string `json:"description,omitempty"` + Suspended bool `json:"suspended"` + Host string `json:"host"` + Path string `json:"path"` + QueueAll bool `json:"queue_all"` + NewUsersPerMinute int `json:"new_users_per_minute"` + TotalActiveUsers int `json:"total_active_users"` + SessionDuration int `json:"session_duration"` + DisableSessionRenewal bool `json:"disable_session_renewal"` + CustomPageHTML string `json:"custom_page_html,omitempty"` + JsonResponseEnabled bool `json:"json_response_enabled"` +} + +// WaitingRoomDetailResponse is the API response, containing a single WaitingRoom. +type WaitingRoomDetailResponse struct { + Response + Result WaitingRoom `json:"result"` +} + +// WaitingRoomsResponse is the API response, containing an array of WaitingRooms. +type WaitingRoomsResponse struct { + Response + Result []WaitingRoom `json:"result"` +} + +// CreateWaitingRoom creates a new Waiting Room for a zone. +// +// API reference: https://api.cloudflare.com/#waiting-room-create-waiting-room +func (api *API) CreateWaitingRoom(ctx context.Context, zoneID string, waitingRoom WaitingRoom) (*WaitingRoom, error) { + uri := fmt.Sprintf("/zones/%s/waiting_rooms", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, waitingRoom) + if err != nil { + return nil, err + } + var r WaitingRoomDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return &r.Result, nil +} + +// ListWaitingRooms returns all Waiting Room for a zone. +// +// API reference: https://api.cloudflare.com/#waiting-room-list-waiting-rooms +func (api *API) ListWaitingRooms(ctx context.Context, zoneID string) ([]WaitingRoom, error) { + uri := fmt.Sprintf("/zones/%s/waiting_rooms", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []WaitingRoom{}, err + } + var r WaitingRoomsResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []WaitingRoom{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// WaitingRoom fetches detail about one Waiting room for a zone. +// +// API reference: https://api.cloudflare.com/#waiting-room-waiting-room-details +func (api *API) WaitingRoom(ctx context.Context, zoneID, waitingRoomID string) (WaitingRoom, error) { + uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s", zoneID, waitingRoomID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WaitingRoom{}, err + } + var r WaitingRoomDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WaitingRoom{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ChangeWaitingRoom lets you change individual settings for a Waiting room. This is +// in contrast to UpdateWaitingRoom which replaces the entire Waiting room. +// +// API reference: https://api.cloudflare.com/#waiting-room-update-waiting-room +func (api *API) ChangeWaitingRoom(ctx context.Context, zoneID, waitingRoomID string, waitingRoom WaitingRoom) (WaitingRoom, error) { + uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s", zoneID, waitingRoomID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, waitingRoom) + if err != nil { + return WaitingRoom{}, err + } + var r WaitingRoomDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WaitingRoom{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateWaitingRoom lets you replace a Waiting Room. This is in contrast to +// ChangeWaitingRoom which lets you change individual settings. +// +// API reference: https://api.cloudflare.com/#waiting-room-update-waiting-room +func (api *API) UpdateWaitingRoom(ctx context.Context, zoneID string, waitingRoom WaitingRoom) (WaitingRoom, error) { + uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s", zoneID, waitingRoom.ID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, waitingRoom) + if err != nil { + return WaitingRoom{}, err + } + var r WaitingRoomDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WaitingRoom{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// DeleteWaitingRoom deletes a Waiting Room for a zone. +// +// API reference: https://api.cloudflare.com/#waiting-room-delete-waiting-room +func (api *API) DeleteWaitingRoom(ctx context.Context, zoneID, waitingRoomID string) error { + uri := fmt.Sprintf("/zones/%s/waiting_rooms/%s", zoneID, waitingRoomID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return err + } + var r WaitingRoomDetailResponse + err = json.Unmarshal(res, &r) + if err != nil { + return errors.Wrap(err, errUnmarshalError) + } + return nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/workers.go b/vendor/github.com/cloudflare/cloudflare-go/workers.go new file mode 100644 index 0000000000000..5710b3457d7b6 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/workers.go @@ -0,0 +1,750 @@ +package cloudflare + +import ( + "bytes" + "context" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "math/rand" + "mime/multipart" + "net/http" + "net/textproto" + "time" + + "github.com/pkg/errors" +) + +// WorkerRequestParams provides parameters for worker requests for both enterprise and standard requests +type WorkerRequestParams struct { + ZoneID string + ScriptName string +} + +// WorkerScriptParams provides a worker script and the associated bindings +type WorkerScriptParams struct { + Script string + + // Bindings should be a map where the keys are the binding name, and the + // values are the binding content + Bindings map[string]WorkerBinding +} + +// WorkerRoute is used to map traffic matching a URL pattern to a workers +// +// API reference: https://api.cloudflare.com/#worker-routes-properties +type WorkerRoute struct { + ID string `json:"id,omitempty"` + Pattern string `json:"pattern"` + Enabled bool `json:"enabled"` // this is deprecated: https://api.cloudflare.com/#worker-filters-deprecated--properties + Script string `json:"script,omitempty"` +} + +// WorkerRoutesResponse embeds Response struct and slice of WorkerRoutes +type WorkerRoutesResponse struct { + Response + Routes []WorkerRoute `json:"result"` +} + +// WorkerRouteResponse embeds Response struct and a single WorkerRoute +type WorkerRouteResponse struct { + Response + WorkerRoute `json:"result"` +} + +// WorkerScript Cloudflare Worker struct with metadata +type WorkerScript struct { + WorkerMetaData + Script string `json:"script"` +} + +// WorkerMetaData contains worker script information such as size, creation & modification dates +type WorkerMetaData struct { + ID string `json:"id,omitempty"` + ETAG string `json:"etag,omitempty"` + Size int `json:"size,omitempty"` + CreatedOn time.Time `json:"created_on,omitempty"` + ModifiedOn time.Time `json:"modified_on,omitempty"` +} + +// WorkerListResponse wrapper struct for API response to worker script list API call +type WorkerListResponse struct { + Response + WorkerList []WorkerMetaData `json:"result"` +} + +// WorkerScriptResponse wrapper struct for API response to worker script calls +type WorkerScriptResponse struct { + Response + WorkerScript `json:"result"` +} + +// WorkerBindingType represents a particular type of binding +type WorkerBindingType string + +func (b WorkerBindingType) String() string { + return string(b) +} + +const ( + // WorkerInheritBindingType is the type for inherited bindings + WorkerInheritBindingType WorkerBindingType = "inherit" + // WorkerKvNamespaceBindingType is the type for KV Namespace bindings + WorkerKvNamespaceBindingType WorkerBindingType = "kv_namespace" + // WorkerWebAssemblyBindingType is the type for Web Assembly module bindings + WorkerWebAssemblyBindingType WorkerBindingType = "wasm_module" + // WorkerSecretTextBindingType is the type for secret text bindings + WorkerSecretTextBindingType WorkerBindingType = "secret_text" + // WorkerPlainTextBindingType is the type for plain text bindings + WorkerPlainTextBindingType WorkerBindingType = "plain_text" +) + +// WorkerBindingListItem a struct representing an individual binding in a list of bindings +type WorkerBindingListItem struct { + Name string `json:"name"` + Binding WorkerBinding +} + +// WorkerBindingListResponse wrapper struct for API response to worker binding list API call +type WorkerBindingListResponse struct { + Response + BindingList []WorkerBindingListItem +} + +// Workers supports multiple types of bindings, e.g. KV namespaces or WebAssembly modules, and each type +// of binding will be represented differently in the upload request body. At a high-level, every binding +// will specify metadata, which is a JSON object with the properties "name" and "type". Some types of bindings +// will also have additional metadata properties. For example, KV bindings also specify the KV namespace. +// In addition to the metadata, some binding types may need to include additional data as part of the +// multipart form. For example, WebAssembly bindings will include the contents of the WebAssembly module. + +// WorkerBinding is the generic interface implemented by all of +// the various binding types +type WorkerBinding interface { + Type() WorkerBindingType + + // serialize is responsible for returning the binding metadata as well as an optionally + // returning a function that can modify the multipart form body. For example, this is used + // by WebAssembly bindings to add a new part containing the WebAssembly module contents. + serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) +} + +// workerBindingMeta is the metadata portion of the binding +type workerBindingMeta = map[string]interface{} + +// workerBindingBodyWriter allows for a binding to add additional parts to the multipart body +type workerBindingBodyWriter func(*multipart.Writer) error + +// WorkerInheritBinding will just persist whatever binding content was previously uploaded +type WorkerInheritBinding struct { + // Optional parameter that allows for renaming a binding without changing + // its contents. If `OldName` is empty, the binding name will not be changed. + OldName string +} + +// Type returns the type of the binding +func (b WorkerInheritBinding) Type() WorkerBindingType { + return WorkerInheritBindingType +} + +func (b WorkerInheritBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + meta := workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + } + + if b.OldName != "" { + meta["old_name"] = b.OldName + } + + return meta, nil, nil +} + +// WorkerKvNamespaceBinding is a binding to a Workers KV Namespace +// +// https://developers.cloudflare.com/workers/archive/api/resource-bindings/kv-namespaces/ +type WorkerKvNamespaceBinding struct { + NamespaceID string +} + +// Type returns the type of the binding +func (b WorkerKvNamespaceBinding) Type() WorkerBindingType { + return WorkerKvNamespaceBindingType +} + +func (b WorkerKvNamespaceBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + if b.NamespaceID == "" { + return nil, nil, errors.Errorf(`NamespaceID for binding "%s" cannot be empty`, bindingName) + } + + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "namespace_id": b.NamespaceID, + }, nil, nil +} + +// WorkerWebAssemblyBinding is a binding to a WebAssembly module +// +// https://developers.cloudflare.com/workers/archive/api/resource-bindings/webassembly-modules/ +type WorkerWebAssemblyBinding struct { + Module io.Reader +} + +// Type returns the type of the binding +func (b WorkerWebAssemblyBinding) Type() WorkerBindingType { + return WorkerWebAssemblyBindingType +} + +func (b WorkerWebAssemblyBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + partName := getRandomPartName() + + bodyWriter := func(mpw *multipart.Writer) error { + var hdr = textproto.MIMEHeader{} + hdr.Set("content-disposition", fmt.Sprintf(`form-data; name="%s"`, partName)) + hdr.Set("content-type", "application/wasm") + pw, err := mpw.CreatePart(hdr) + if err != nil { + return err + } + _, err = io.Copy(pw, b.Module) + return err + } + + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "part": partName, + }, bodyWriter, nil +} + +// WorkerPlainTextBinding is a binding to plain text +// +// https://developers.cloudflare.com/workers/tooling/api/scripts/#add-a-plain-text-binding +type WorkerPlainTextBinding struct { + Text string +} + +// Type returns the type of the binding +func (b WorkerPlainTextBinding) Type() WorkerBindingType { + return WorkerPlainTextBindingType +} + +func (b WorkerPlainTextBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + if b.Text == "" { + return nil, nil, errors.Errorf(`Text for binding "%s" cannot be empty`, bindingName) + } + + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "text": b.Text, + }, nil, nil +} + +// WorkerSecretTextBinding is a binding to secret text +// +// https://developers.cloudflare.com/workers/tooling/api/scripts/#add-a-secret-text-binding +type WorkerSecretTextBinding struct { + Text string +} + +// Type returns the type of the binding +func (b WorkerSecretTextBinding) Type() WorkerBindingType { + return WorkerSecretTextBindingType +} + +func (b WorkerSecretTextBinding) serialize(bindingName string) (workerBindingMeta, workerBindingBodyWriter, error) { + if b.Text == "" { + return nil, nil, errors.Errorf(`Text for binding "%s" cannot be empty`, bindingName) + } + + return workerBindingMeta{ + "name": bindingName, + "type": b.Type(), + "text": b.Text, + }, nil, nil +} + +// Each binding that adds a part to the multipart form body will need +// a unique part name so we just generate a random 128bit hex string +func getRandomPartName() string { + randBytes := make([]byte, 16) + rand.Read(randBytes) + return hex.EncodeToString(randBytes) +} + +// DeleteWorker deletes worker for a zone. +// +// API reference: https://api.cloudflare.com/#worker-script-delete-worker +func (api *API) DeleteWorker(ctx context.Context, requestParams *WorkerRequestParams) (WorkerScriptResponse, error) { + // if ScriptName is provided we will treat as org request + if requestParams.ScriptName != "" { + return api.deleteWorkerWithName(ctx, requestParams.ScriptName) + } + uri := fmt.Sprintf("/zones/%s/workers/script", requestParams.ZoneID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + var r WorkerScriptResponse + if err != nil { + return r, err + } + err = json.Unmarshal(res, &r) + if err != nil { + return r, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// DeleteWorkerWithName deletes worker for a zone. +// Sccount must be specified as api option https://godoc.org/github.com/cloudflare/cloudflare-go#UsingAccount +// +// API reference: https://developers.cloudflare.com/workers/tooling/api/scripts/ +func (api *API) deleteWorkerWithName(ctx context.Context, scriptName string) (WorkerScriptResponse, error) { + if api.AccountID == "" { + return WorkerScriptResponse{}, errors.New("account ID required") + } + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", api.AccountID, scriptName) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + var r WorkerScriptResponse + if err != nil { + return r, err + } + err = json.Unmarshal(res, &r) + if err != nil { + return r, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// DownloadWorker fetch raw script content for your worker returns []byte containing worker code js +// +// API reference: https://api.cloudflare.com/#worker-script-download-worker +func (api *API) DownloadWorker(ctx context.Context, requestParams *WorkerRequestParams) (WorkerScriptResponse, error) { + if requestParams.ScriptName != "" { + return api.downloadWorkerWithName(ctx, requestParams.ScriptName) + } + uri := fmt.Sprintf("/zones/%s/workers/script", requestParams.ZoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + var r WorkerScriptResponse + if err != nil { + return r, err + } + r.Script = string(res) + r.Success = true + return r, nil +} + +// DownloadWorkerWithName fetch raw script content for your worker returns string containing worker code js +// +// API reference: https://developers.cloudflare.com/workers/tooling/api/scripts/ +func (api *API) downloadWorkerWithName(ctx context.Context, scriptName string) (WorkerScriptResponse, error) { + if api.AccountID == "" { + return WorkerScriptResponse{}, errors.New("account ID required") + } + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", api.AccountID, scriptName) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + var r WorkerScriptResponse + if err != nil { + return r, err + } + r.Script = string(res) + r.Success = true + return r, nil +} + +// ListWorkerBindings returns all the bindings for a particular worker +func (api *API) ListWorkerBindings(ctx context.Context, requestParams *WorkerRequestParams) (WorkerBindingListResponse, error) { + if requestParams.ScriptName == "" { + return WorkerBindingListResponse{}, errors.New("ScriptName is required") + } + if api.AccountID == "" { + return WorkerBindingListResponse{}, errors.New("account ID required") + } + + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/bindings", api.AccountID, requestParams.ScriptName) + + var jsonRes struct { + Response + Bindings []workerBindingMeta `json:"result"` + } + var r WorkerBindingListResponse + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return r, err + } + err = json.Unmarshal(res, &jsonRes) + if err != nil { + return r, errors.Wrap(err, errUnmarshalError) + } + + r = WorkerBindingListResponse{ + Response: jsonRes.Response, + BindingList: make([]WorkerBindingListItem, 0, len(jsonRes.Bindings)), + } + for _, jsonBinding := range jsonRes.Bindings { + name, ok := jsonBinding["name"].(string) + if !ok { + return r, errors.Errorf("Binding missing name %v", jsonBinding) + } + bType, ok := jsonBinding["type"].(string) + if !ok { + return r, errors.Errorf("Binding missing type %v", jsonBinding) + } + bindingListItem := WorkerBindingListItem{ + Name: name, + } + + switch WorkerBindingType(bType) { + case WorkerKvNamespaceBindingType: + namespaceID := jsonBinding["namespace_id"].(string) + bindingListItem.Binding = WorkerKvNamespaceBinding{ + NamespaceID: namespaceID, + } + case WorkerWebAssemblyBindingType: + bindingListItem.Binding = WorkerWebAssemblyBinding{ + Module: &bindingContentReader{ + api: api, + requestParams: requestParams, + bindingName: name, + }, + } + case WorkerPlainTextBindingType: + text := jsonBinding["text"].(string) + bindingListItem.Binding = WorkerPlainTextBinding{ + Text: text, + } + case WorkerSecretTextBindingType: + bindingListItem.Binding = WorkerSecretTextBinding{} + default: + bindingListItem.Binding = WorkerInheritBinding{} + } + r.BindingList = append(r.BindingList, bindingListItem) + } + + return r, nil +} + +// bindingContentReader is an io.Reader that will lazily load the +// raw bytes for a binding from the API when the Read() method +// is first called. This is only useful for binding types +// that store raw bytes, like WebAssembly modules +type bindingContentReader struct { + api *API + requestParams *WorkerRequestParams + bindingName string + content []byte + position int +} + +func (b *bindingContentReader) Read(p []byte) (n int, err error) { + // Lazily load the content when Read() is first called + if b.content == nil { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/bindings/%s/content", b.api.AccountID, b.requestParams.ScriptName, b.bindingName) + res, err := b.api.makeRequest(http.MethodGet, uri, nil) + if err != nil { + return 0, err + } + b.content = res + } + + if b.position >= len(b.content) { + return 0, io.EOF + } + + bytesRemaining := len(b.content) - b.position + bytesToProcess := 0 + if len(p) < bytesRemaining { + bytesToProcess = len(p) + } else { + bytesToProcess = bytesRemaining + } + + for i := 0; i < bytesToProcess; i++ { + p[i] = b.content[b.position] + b.position = b.position + 1 + } + + return bytesToProcess, nil +} + +// ListWorkerScripts returns list of worker scripts for given account. +// +// API reference: https://developers.cloudflare.com/workers/tooling/api/scripts/ +func (api *API) ListWorkerScripts(ctx context.Context) (WorkerListResponse, error) { + if api.AccountID == "" { + return WorkerListResponse{}, errors.New("account ID required") + } + uri := fmt.Sprintf("/accounts/%s/workers/scripts", api.AccountID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WorkerListResponse{}, err + } + var r WorkerListResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerListResponse{}, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// UploadWorker push raw script content for your worker. +// +// API reference: https://api.cloudflare.com/#worker-script-upload-worker +func (api *API) UploadWorker(ctx context.Context, requestParams *WorkerRequestParams, data string) (WorkerScriptResponse, error) { + if requestParams.ScriptName != "" { + return api.uploadWorkerWithName(ctx, requestParams.ScriptName, "application/javascript", []byte(data)) + } + return api.uploadWorkerForZone(ctx, requestParams.ZoneID, "application/javascript", []byte(data)) +} + +// UploadWorkerWithBindings push raw script content and bindings for your worker +// +// API reference: https://api.cloudflare.com/#worker-script-upload-worker +func (api *API) UploadWorkerWithBindings(ctx context.Context, requestParams *WorkerRequestParams, data *WorkerScriptParams) (WorkerScriptResponse, error) { + contentType, body, err := formatMultipartBody(data) + if err != nil { + return WorkerScriptResponse{}, err + } + if requestParams.ScriptName != "" { + return api.uploadWorkerWithName(ctx, requestParams.ScriptName, contentType, body) + } + return api.uploadWorkerForZone(ctx, requestParams.ZoneID, contentType, body) +} + +func (api *API) uploadWorkerForZone(ctx context.Context, zoneID, contentType string, body []byte) (WorkerScriptResponse, error) { + uri := fmt.Sprintf("/zones/%s/workers/script", zoneID) + headers := make(http.Header) + headers.Set("Content-Type", contentType) + res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPut, uri, body, headers) + var r WorkerScriptResponse + if err != nil { + return r, err + } + err = json.Unmarshal(res, &r) + if err != nil { + return r, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +func (api *API) uploadWorkerWithName(ctx context.Context, scriptName, contentType string, body []byte) (WorkerScriptResponse, error) { + if api.AccountID == "" { + return WorkerScriptResponse{}, errors.New("account ID required") + } + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s", api.AccountID, scriptName) + headers := make(http.Header) + headers.Set("Content-Type", contentType) + res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPut, uri, body, headers) + var r WorkerScriptResponse + if err != nil { + return r, err + } + err = json.Unmarshal(res, &r) + if err != nil { + return r, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// Returns content-type, body, error +func formatMultipartBody(params *WorkerScriptParams) (string, []byte, error) { + var buf = &bytes.Buffer{} + var mpw = multipart.NewWriter(buf) + defer mpw.Close() + + // Write metadata part + scriptPartName := "script" + meta := struct { + BodyPart string `json:"body_part"` + Bindings []workerBindingMeta `json:"bindings"` + }{ + BodyPart: scriptPartName, + Bindings: make([]workerBindingMeta, 0, len(params.Bindings)), + } + + bodyWriters := make([]workerBindingBodyWriter, 0, len(params.Bindings)) + for name, b := range params.Bindings { + bindingMeta, bodyWriter, err := b.serialize(name) + if err != nil { + return "", nil, err + } + + meta.Bindings = append(meta.Bindings, bindingMeta) + bodyWriters = append(bodyWriters, bodyWriter) + } + + var hdr = textproto.MIMEHeader{} + hdr.Set("content-disposition", fmt.Sprintf(`form-data; name="%s"`, "metadata")) + hdr.Set("content-type", "application/json") + pw, err := mpw.CreatePart(hdr) + if err != nil { + return "", nil, err + } + metaJSON, err := json.Marshal(meta) + if err != nil { + return "", nil, err + } + _, err = pw.Write(metaJSON) + if err != nil { + return "", nil, err + } + + // Write script part + hdr = textproto.MIMEHeader{} + hdr.Set("content-disposition", fmt.Sprintf(`form-data; name="%s"`, scriptPartName)) + hdr.Set("content-type", "application/javascript") + pw, err = mpw.CreatePart(hdr) + if err != nil { + return "", nil, err + } + _, err = pw.Write([]byte(params.Script)) + if err != nil { + return "", nil, err + } + + // Write other bindings with parts + for _, w := range bodyWriters { + if w != nil { + err = w(mpw) + if err != nil { + return "", nil, err + } + } + } + + mpw.Close() + + return mpw.FormDataContentType(), buf.Bytes(), nil +} + +// CreateWorkerRoute creates worker route for a zone +// +// API reference: https://api.cloudflare.com/#worker-filters-create-filter, https://api.cloudflare.com/#worker-routes-create-route +func (api *API) CreateWorkerRoute(ctx context.Context, zoneID string, route WorkerRoute) (WorkerRouteResponse, error) { + pathComponent, err := getRouteEndpoint(api, route) + if err != nil { + return WorkerRouteResponse{}, err + } + + uri := fmt.Sprintf("/zones/%s/workers/%s", zoneID, pathComponent) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, route) + if err != nil { + return WorkerRouteResponse{}, err + } + var r WorkerRouteResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// DeleteWorkerRoute deletes worker route for a zone +// +// API reference: https://api.cloudflare.com/#worker-routes-delete-route +func (api *API) DeleteWorkerRoute(ctx context.Context, zoneID string, routeID string) (WorkerRouteResponse, error) { + uri := fmt.Sprintf("/zones/%s/workers/routes/%s", zoneID, routeID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return WorkerRouteResponse{}, err + } + var r WorkerRouteResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// ListWorkerRoutes returns list of worker routes +// +// API reference: https://api.cloudflare.com/#worker-filters-list-filters, https://api.cloudflare.com/#worker-routes-list-routes +func (api *API) ListWorkerRoutes(ctx context.Context, zoneID string) (WorkerRoutesResponse, error) { + pathComponent := "filters" + // Unfortunately we don't have a good signal of whether the user is wanting + // to use the deprecated filters endpoint (https://api.cloudflare.com/#worker-filters-list-filters) + // or the multi-script routes endpoint (https://api.cloudflare.com/#worker-script-list-workers) + // + // The filters endpoint does not support API tokens, so if an API token is specified we need to use + // the routes endpoint. Otherwise, since the multi-script API endpoints that operate on a script + // require an AccountID, we assume that anyone specifying an AccountID is using the routes endpoint. + // This is likely too presumptuous. In the next major version, we should just remove the deprecated + // filter endpoints entirely to avoid this ambiguity. + if api.AccountID != "" || api.APIToken != "" { + pathComponent = "routes" + } + uri := fmt.Sprintf("/zones/%s/workers/%s", zoneID, pathComponent) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WorkerRoutesResponse{}, err + } + var r WorkerRoutesResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerRoutesResponse{}, errors.Wrap(err, errUnmarshalError) + } + for i := range r.Routes { + route := &r.Routes[i] + // The Enabled flag will not be set in the multi-script API response + // so we manually set it to true if the script name is not empty + // in case any multi-script customers rely on the Enabled field + if route.Script != "" { + route.Enabled = true + } + } + return r, nil +} + +// GetWorkerRoute returns a worker route. +// +// API reference: https://api.cloudflare.com/#worker-routes-get-route +func (api *API) GetWorkerRoute(ctx context.Context, zoneID string, routeID string) (WorkerRouteResponse, error) { + uri := fmt.Sprintf("/zones/%s/workers/routes/%s", zoneID, routeID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WorkerRouteResponse{}, err + } + var r WorkerRouteResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// UpdateWorkerRoute updates worker route for a zone. +// +// API reference: https://api.cloudflare.com/#worker-filters-update-filter, https://api.cloudflare.com/#worker-routes-update-route +func (api *API) UpdateWorkerRoute(ctx context.Context, zoneID string, routeID string, route WorkerRoute) (WorkerRouteResponse, error) { + pathComponent, err := getRouteEndpoint(api, route) + if err != nil { + return WorkerRouteResponse{}, err + } + uri := fmt.Sprintf("/zones/%s/workers/%s/%s", zoneID, pathComponent, routeID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, route) + if err != nil { + return WorkerRouteResponse{}, err + } + var r WorkerRouteResponse + err = json.Unmarshal(res, &r) + if err != nil { + return WorkerRouteResponse{}, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +func getRouteEndpoint(api *API, route WorkerRoute) (string, error) { + if route.Script != "" && route.Enabled { + return "", errors.New("Only `Script` or `Enabled` may be specified for a WorkerRoute, not both") + } + + // For backwards-compatibility, fallback to the deprecated filter + // endpoint if Enabled == true + // https://api.cloudflare.com/#worker-filters-deprecated--properties + if route.Enabled { + return "filters", nil + } + + return "routes", nil +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/workers_cron_triggers.go b/vendor/github.com/cloudflare/cloudflare-go/workers_cron_triggers.go new file mode 100644 index 0000000000000..09427bb74edef --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/workers_cron_triggers.go @@ -0,0 +1,75 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/pkg/errors" +) + +// WorkerCronTriggerResponse represents the response from the Worker cron trigger +// API endpoint. +type WorkerCronTriggerResponse struct { + Response + Result WorkerCronTriggerSchedules `json:"result"` +} + +// WorkerCronTriggerSchedules contains the schedule of Worker cron triggers. +type WorkerCronTriggerSchedules struct { + Schedules []WorkerCronTrigger `json:"schedules"` +} + +// WorkerCronTrigger holds an individual cron schedule for a worker. +type WorkerCronTrigger struct { + Cron string `json:"cron"` + CreatedOn *time.Time `json:"created_on,omitempty"` + ModifiedOn *time.Time `json:"modified_on,omitempty"` +} + +// ListWorkerCronTriggers fetches all available cron triggers for a single Worker +// script. +// +// API reference: https://api.cloudflare.com/#worker-cron-trigger-get-cron-triggers +func (api *API) ListWorkerCronTriggers(ctx context.Context, scriptName string) ([]WorkerCronTrigger, error) { + if err := api.checkAccountID(); err != nil { + return []WorkerCronTrigger{}, err + } + + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/schedules", api.AccountID, scriptName) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []WorkerCronTrigger{}, err + } + + result := WorkerCronTriggerResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return []WorkerCronTrigger{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result.Schedules, err +} + +// UpdateWorkerCronTriggers updates a single schedule for a Worker cron trigger. +// +// API reference: https://api.cloudflare.com/#worker-cron-trigger-update-cron-triggers +func (api *API) UpdateWorkerCronTriggers(ctx context.Context, scriptName string, crons []WorkerCronTrigger) ([]WorkerCronTrigger, error) { + if err := api.checkAccountID(); err != nil { + return []WorkerCronTrigger{}, err + } + + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/schedules", api.AccountID, scriptName) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, crons) + if err != nil { + return []WorkerCronTrigger{}, err + } + + result := WorkerCronTriggerResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return []WorkerCronTrigger{}, errors.Wrap(err, errUnmarshalError) + } + + return result.Result.Schedules, err +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/workers_kv.go b/vendor/github.com/cloudflare/cloudflare-go/workers_kv.go new file mode 100644 index 0000000000000..ed5fbd0ed51c2 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/workers_kv.go @@ -0,0 +1,307 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + + "github.com/pkg/errors" +) + +// WorkersKVNamespaceRequest provides parameters for creating and updating storage namespaces +type WorkersKVNamespaceRequest struct { + Title string `json:"title"` +} + +// WorkersKVPair is used in an array in the request to the bulk KV api +type WorkersKVPair struct { + Key string `json:"key"` + Value string `json:"value"` + Expiration int `json:"expiration,omitempty"` + ExpirationTTL int `json:"expiration_ttl,omitempty"` + Metadata interface{} `json:"metadata,omitempty"` + Base64 bool `json:"base64,omitempty"` +} + +// WorkersKVBulkWriteRequest is the request to the bulk KV api +type WorkersKVBulkWriteRequest []*WorkersKVPair + +// WorkersKVNamespaceResponse is the response received when creating storage namespaces +type WorkersKVNamespaceResponse struct { + Response + Result WorkersKVNamespace `json:"result"` +} + +// WorkersKVNamespace contains the unique identifier and title of a storage namespace +type WorkersKVNamespace struct { + ID string `json:"id"` + Title string `json:"title"` +} + +// ListWorkersKVNamespacesResponse contains a slice of storage namespaces associated with an +// account, pagination information, and an embedded response struct +type ListWorkersKVNamespacesResponse struct { + Response + Result []WorkersKVNamespace `json:"result"` + ResultInfo `json:"result_info"` +} + +// StorageKey is a key name used to identify a storage value +type StorageKey struct { + Name string `json:"name"` + Expiration int `json:"expiration"` + Metadata interface{} `json:"metadata"` +} + +// ListWorkersKVsOptions contains optional parameters for listing a namespace's keys +type ListWorkersKVsOptions struct { + Limit *int + Cursor *string + Prefix *string +} + +// ListStorageKeysResponse contains a slice of keys belonging to a storage namespace, +// pagination information, and an embedded response struct +type ListStorageKeysResponse struct { + Response + Result []StorageKey `json:"result"` + ResultInfo `json:"result_info"` +} + +// CreateWorkersKVNamespace creates a namespace under the given title. +// A 400 is returned if the account already owns a namespace with this title. +// A namespace must be explicitly deleted to be replaced. +// +// API reference: https://api.cloudflare.com/#workers-kv-namespace-create-a-namespace +func (api *API) CreateWorkersKVNamespace(ctx context.Context, req *WorkersKVNamespaceRequest) (WorkersKVNamespaceResponse, error) { + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces", api.AccountID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, req) + if err != nil { + return WorkersKVNamespaceResponse{}, err + } + + result := WorkersKVNamespaceResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + + return result, err +} + +// ListWorkersKVNamespaces lists storage namespaces +// +// API reference: https://api.cloudflare.com/#workers-kv-namespace-list-namespaces +func (api *API) ListWorkersKVNamespaces(ctx context.Context) ([]WorkersKVNamespace, error) { + v := url.Values{} + v.Set("per_page", "100") + + var namespaces []WorkersKVNamespace + page := 1 + + for { + v.Set("page", strconv.Itoa(page)) + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces?%s", api.AccountID, v.Encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []WorkersKVNamespace{}, err + } + + var p ListWorkersKVNamespacesResponse + if err := json.Unmarshal(res, &p); err != nil { + return []WorkersKVNamespace{}, errors.Wrap(err, errUnmarshalError) + } + + if !p.Success { + return []WorkersKVNamespace{}, errors.New(errRequestNotSuccessful) + } + + namespaces = append(namespaces, p.Result...) + if p.ResultInfo.Page >= p.ResultInfo.TotalPages { + break + } + + page++ + } + + return namespaces, nil +} + +// DeleteWorkersKVNamespace deletes the namespace corresponding to the given ID +// +// API reference: https://api.cloudflare.com/#workers-kv-namespace-remove-a-namespace +func (api *API) DeleteWorkersKVNamespace(ctx context.Context, namespaceID string) (Response, error) { + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s", api.AccountID, namespaceID) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return Response{}, err + } + + result := Response{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + + return result, err +} + +// UpdateWorkersKVNamespace modifies a namespace's title +// +// API reference: https://api.cloudflare.com/#workers-kv-namespace-rename-a-namespace +func (api *API) UpdateWorkersKVNamespace(ctx context.Context, namespaceID string, req *WorkersKVNamespaceRequest) (Response, error) { + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s", api.AccountID, namespaceID) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, req) + if err != nil { + return Response{}, err + } + + result := Response{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + + return result, err +} + +// WriteWorkersKV writes a value identified by a key. +// +// API reference: https://api.cloudflare.com/#workers-kv-namespace-write-key-value-pair +func (api *API) WriteWorkersKV(ctx context.Context, namespaceID, key string, value []byte) (Response, error) { + key = url.PathEscape(key) + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", api.AccountID, namespaceID, key) + res, err := api.makeRequestWithHeaders( + http.MethodPut, uri, value, http.Header{"Content-Type": []string{"application/octet-stream"}}, + ) + if err != nil { + return Response{}, err + } + + result := Response{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + + return result, err +} + +// WriteWorkersKVBulk writes multiple KVs at once. +// +// API reference: https://api.cloudflare.com/#workers-kv-namespace-write-multiple-key-value-pairs +func (api *API) WriteWorkersKVBulk(ctx context.Context, namespaceID string, kvs WorkersKVBulkWriteRequest) (Response, error) { + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/bulk", api.AccountID, namespaceID) + res, err := api.makeRequestWithHeaders( + http.MethodPut, uri, kvs, http.Header{"Content-Type": []string{"application/json"}}, + ) + if err != nil { + return Response{}, err + } + + result := Response{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + + return result, err +} + +// ReadWorkersKV returns the value associated with the given key in the given namespace +// +// API reference: https://api.cloudflare.com/#workers-kv-namespace-read-key-value-pair +func (api API) ReadWorkersKV(ctx context.Context, namespaceID, key string) ([]byte, error) { + key = url.PathEscape(key) + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", api.AccountID, namespaceID, key) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + return res, nil +} + +// DeleteWorkersKV deletes a key and value for a provided storage namespace +// +// API reference: https://api.cloudflare.com/#workers-kv-namespace-delete-key-value-pair +func (api API) DeleteWorkersKV(ctx context.Context, namespaceID, key string) (Response, error) { + key = url.PathEscape(key) + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", api.AccountID, namespaceID, key) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return Response{}, err + } + + result := Response{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + return result, err +} + +// DeleteWorkersKVBulk deletes multiple KVs at once. +// +// API reference: https://api.cloudflare.com/#workers-kv-namespace-delete-multiple-key-value-pairs +func (api *API) DeleteWorkersKVBulk(ctx context.Context, namespaceID string, keys []string) (Response, error) { + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/bulk", api.AccountID, namespaceID) + res, err := api.makeRequestWithHeaders( + http.MethodDelete, uri, keys, http.Header{"Content-Type": []string{"application/json"}}, + ) + if err != nil { + return Response{}, err + } + + result := Response{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + + return result, err +} + +// ListWorkersKVs lists a namespace's keys +// +// API Reference: https://api.cloudflare.com/#workers-kv-namespace-list-a-namespace-s-keys +func (api API) ListWorkersKVs(ctx context.Context, namespaceID string) (ListStorageKeysResponse, error) { + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/keys", api.AccountID, namespaceID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return ListStorageKeysResponse{}, err + } + + result := ListStorageKeysResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + return result, err +} + +// encode encodes non-nil fields into URL encoded form. +func (o ListWorkersKVsOptions) encode() string { + v := url.Values{} + if o.Limit != nil { + v.Set("limit", strconv.Itoa(*o.Limit)) + } + if o.Cursor != nil { + v.Set("cursor", *o.Cursor) + } + if o.Prefix != nil { + v.Set("prefix", *o.Prefix) + } + return v.Encode() +} + +// ListWorkersKVsWithOptions lists a namespace's keys with optional parameters +// +// API Reference: https://api.cloudflare.com/#workers-kv-namespace-list-a-namespace-s-keys +func (api API) ListWorkersKVsWithOptions(ctx context.Context, namespaceID string, o ListWorkersKVsOptions) (ListStorageKeysResponse, error) { + uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/keys?%s", api.AccountID, namespaceID, o.encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return ListStorageKeysResponse{}, err + } + + result := ListStorageKeysResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + return result, err +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/workers_secrets.go b/vendor/github.com/cloudflare/cloudflare-go/workers_secrets.go new file mode 100644 index 0000000000000..973d3fc5ee220 --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/workers_secrets.go @@ -0,0 +1,86 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + + "github.com/pkg/errors" +) + +// WorkersPutSecretRequest provides parameters for creating and updating secrets +type WorkersPutSecretRequest struct { + Name string `json:"name"` + Text string `json:"text"` + Type WorkerBindingType `json:"type"` +} + +// WorkersSecret contains the name and type of the secret +type WorkersSecret struct { + Name string `json:"name"` + Type string `json:"secret_text"` +} + +// WorkersPutSecretResponse is the response received when creating or updating a secret +type WorkersPutSecretResponse struct { + Response + Result WorkersSecret `json:"result"` +} + +// WorkersListSecretsResponse is the response received when listing secrets +type WorkersListSecretsResponse struct { + Response + Result []WorkersSecret `json:"result"` +} + +// SetWorkersSecret creates or updates a secret +// API reference: https://api.cloudflare.com/ +func (api *API) SetWorkersSecret(ctx context.Context, script string, req *WorkersPutSecretRequest) (WorkersPutSecretResponse, error) { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/secrets", api.AccountID, script) + res, err := api.makeRequestContext(ctx, http.MethodPut, uri, req) + if err != nil { + return WorkersPutSecretResponse{}, err + } + + result := WorkersPutSecretResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + + return result, err +} + +// DeleteWorkersSecret deletes a secret +// API reference: https://api.cloudflare.com/ +func (api *API) DeleteWorkersSecret(ctx context.Context, script, secretName string) (Response, error) { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/secrets/%s", api.AccountID, script, secretName) + res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil) + if err != nil { + return Response{}, err + } + + result := Response{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + + return result, err +} + +// ListWorkersSecrets lists secrets for a given worker +// API reference: https://api.cloudflare.com/ +func (api *API) ListWorkersSecrets(ctx context.Context, script string) (WorkersListSecretsResponse, error) { + uri := fmt.Sprintf("/accounts/%s/workers/scripts/%s/secrets", api.AccountID, script) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return WorkersListSecretsResponse{}, err + } + + result := WorkersListSecretsResponse{} + if err := json.Unmarshal(res, &result); err != nil { + return result, errors.Wrap(err, errUnmarshalError) + } + + return result, err +} diff --git a/vendor/github.com/cloudflare/cloudflare-go/zone.go b/vendor/github.com/cloudflare/cloudflare-go/zone.go new file mode 100644 index 0000000000000..083e75ee7ce6c --- /dev/null +++ b/vendor/github.com/cloudflare/cloudflare-go/zone.go @@ -0,0 +1,1026 @@ +package cloudflare + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "sync" + "time" + + "github.com/pkg/errors" + "golang.org/x/net/idna" +) + +// Owner describes the resource owner. +type Owner struct { + ID string `json:"id"` + Email string `json:"email"` + Name string `json:"name"` + OwnerType string `json:"type"` +} + +// Zone describes a Cloudflare zone. +type Zone struct { + ID string `json:"id"` + Name string `json:"name"` + // DevMode contains the time in seconds until development expires (if + // positive) or since it expired (if negative). It will be 0 if never used. + DevMode int `json:"development_mode"` + OriginalNS []string `json:"original_name_servers"` + OriginalRegistrar string `json:"original_registrar"` + OriginalDNSHost string `json:"original_dnshost"` + CreatedOn time.Time `json:"created_on"` + ModifiedOn time.Time `json:"modified_on"` + NameServers []string `json:"name_servers"` + Owner Owner `json:"owner"` + Permissions []string `json:"permissions"` + Plan ZonePlan `json:"plan"` + PlanPending ZonePlan `json:"plan_pending,omitempty"` + Status string `json:"status"` + Paused bool `json:"paused"` + Type string `json:"type"` + Host struct { + Name string + Website string + } `json:"host"` + VanityNS []string `json:"vanity_name_servers"` + Betas []string `json:"betas"` + DeactReason string `json:"deactivation_reason"` + Meta ZoneMeta `json:"meta"` + Account Account `json:"account"` + VerificationKey string `json:"verification_key"` +} + +// ZoneMeta describes metadata about a zone. +type ZoneMeta struct { + // custom_certificate_quota is broken - sometimes it's a string, sometimes a number! + // CustCertQuota int `json:"custom_certificate_quota"` + PageRuleQuota int `json:"page_rule_quota"` + WildcardProxiable bool `json:"wildcard_proxiable"` + PhishingDetected bool `json:"phishing_detected"` +} + +// ZonePlan contains the plan information for a zone. +type ZonePlan struct { + ZonePlanCommon + IsSubscribed bool `json:"is_subscribed"` + CanSubscribe bool `json:"can_subscribe"` + LegacyID string `json:"legacy_id"` + LegacyDiscount bool `json:"legacy_discount"` + ExternallyManaged bool `json:"externally_managed"` +} + +// ZoneRatePlan contains the plan information for a zone. +type ZoneRatePlan struct { + ZonePlanCommon + Components []zoneRatePlanComponents `json:"components,omitempty"` +} + +// ZonePlanCommon contains fields used by various Plan endpoints +type ZonePlanCommon struct { + ID string `json:"id"` + Name string `json:"name,omitempty"` + Price int `json:"price,omitempty"` + Currency string `json:"currency,omitempty"` + Frequency string `json:"frequency,omitempty"` +} + +type zoneRatePlanComponents struct { + Name string `json:"name"` + Default int `json:"Default"` + UnitPrice int `json:"unit_price"` +} + +// ZoneID contains only the zone ID. +type ZoneID struct { + ID string `json:"id"` +} + +// ZoneResponse represents the response from the Zone endpoint containing a single zone. +type ZoneResponse struct { + Response + Result Zone `json:"result"` +} + +// ZonesResponse represents the response from the Zone endpoint containing an array of zones. +type ZonesResponse struct { + Response + Result []Zone `json:"result"` + ResultInfo `json:"result_info"` +} + +// ZoneIDResponse represents the response from the Zone endpoint, containing only a zone ID. +type ZoneIDResponse struct { + Response + Result ZoneID `json:"result"` +} + +// AvailableZoneRatePlansResponse represents the response from the Available Rate Plans endpoint. +type AvailableZoneRatePlansResponse struct { + Response + Result []ZoneRatePlan `json:"result"` + ResultInfo `json:"result_info"` +} + +// AvailableZonePlansResponse represents the response from the Available Plans endpoint. +type AvailableZonePlansResponse struct { + Response + Result []ZonePlan `json:"result"` + ResultInfo +} + +// ZoneRatePlanResponse represents the response from the Plan Details endpoint. +type ZoneRatePlanResponse struct { + Response + Result ZoneRatePlan `json:"result"` +} + +// ZoneSetting contains settings for a zone. +type ZoneSetting struct { + ID string `json:"id"` + Editable bool `json:"editable"` + ModifiedOn string `json:"modified_on,omitempty"` + Value interface{} `json:"value"` + TimeRemaining int `json:"time_remaining"` +} + +// ZoneSettingResponse represents the response from the Zone Setting endpoint. +type ZoneSettingResponse struct { + Response + Result []ZoneSetting `json:"result"` +} + +// ZoneSettingSingleResponse represents the response from the Zone Setting endpoint for the specified setting. +type ZoneSettingSingleResponse struct { + Response + Result ZoneSetting `json:"result"` +} + +// ZoneSSLSetting contains ssl setting for a zone. +type ZoneSSLSetting struct { + ID string `json:"id"` + Editable bool `json:"editable"` + ModifiedOn string `json:"modified_on"` + Value string `json:"value"` + CertificateStatus string `json:"certificate_status"` +} + +// ZoneSSLSettingResponse represents the response from the Zone SSL Setting +// endpoint. +type ZoneSSLSettingResponse struct { + Response + Result ZoneSSLSetting `json:"result"` +} + +// ZoneAnalyticsData contains totals and timeseries analytics data for a zone. +type ZoneAnalyticsData struct { + Totals ZoneAnalytics `json:"totals"` + Timeseries []ZoneAnalytics `json:"timeseries"` +} + +// zoneAnalyticsDataResponse represents the response from the Zone Analytics Dashboard endpoint. +type zoneAnalyticsDataResponse struct { + Response + Result ZoneAnalyticsData `json:"result"` +} + +// ZoneAnalyticsColocation contains analytics data by datacenter. +type ZoneAnalyticsColocation struct { + ColocationID string `json:"colo_id"` + Timeseries []ZoneAnalytics `json:"timeseries"` +} + +// zoneAnalyticsColocationResponse represents the response from the Zone Analytics By Co-location endpoint. +type zoneAnalyticsColocationResponse struct { + Response + Result []ZoneAnalyticsColocation `json:"result"` +} + +// ZoneAnalytics contains analytics data for a zone. +type ZoneAnalytics struct { + Since time.Time `json:"since"` + Until time.Time `json:"until"` + Requests struct { + All int `json:"all"` + Cached int `json:"cached"` + Uncached int `json:"uncached"` + ContentType map[string]int `json:"content_type"` + Country map[string]int `json:"country"` + SSL struct { + Encrypted int `json:"encrypted"` + Unencrypted int `json:"unencrypted"` + } `json:"ssl"` + HTTPStatus map[string]int `json:"http_status"` + } `json:"requests"` + Bandwidth struct { + All int `json:"all"` + Cached int `json:"cached"` + Uncached int `json:"uncached"` + ContentType map[string]int `json:"content_type"` + Country map[string]int `json:"country"` + SSL struct { + Encrypted int `json:"encrypted"` + Unencrypted int `json:"unencrypted"` + } `json:"ssl"` + } `json:"bandwidth"` + Threats struct { + All int `json:"all"` + Country map[string]int `json:"country"` + Type map[string]int `json:"type"` + } `json:"threats"` + Pageviews struct { + All int `json:"all"` + SearchEngines map[string]int `json:"search_engines"` + } `json:"pageviews"` + Uniques struct { + All int `json:"all"` + } +} + +// ZoneAnalyticsOptions represents the optional parameters in Zone Analytics +// endpoint requests. +type ZoneAnalyticsOptions struct { + Since *time.Time + Until *time.Time + Continuous *bool +} + +// PurgeCacheRequest represents the request format made to the purge endpoint. +type PurgeCacheRequest struct { + Everything bool `json:"purge_everything,omitempty"` + // Purge by filepath (exact match). Limit of 30 + Files []string `json:"files,omitempty"` + // Purge by Tag (Enterprise only): + // https://support.cloudflare.com/hc/en-us/articles/206596608-How-to-Purge-Cache-Using-Cache-Tags-Enterprise-only- + Tags []string `json:"tags,omitempty"` + // Purge by hostname - e.g. "assets.example.com" + Hosts []string `json:"hosts,omitempty"` + // Purge by prefix - e.g. "example.com/css" + Prefixes []string `json:"prefixes,omitempty"` +} + +// PurgeCacheResponse represents the response from the purge endpoint. +type PurgeCacheResponse struct { + Response + Result struct { + ID string `json:"id"` + } `json:"result"` +} + +// newZone describes a new zone. +type newZone struct { + Name string `json:"name"` + JumpStart bool `json:"jump_start"` + Type string `json:"type"` + // We use a pointer to get a nil type when the field is empty. + // This allows us to completely omit this with json.Marshal(). + Account *Account `json:"organization,omitempty"` +} + +// FallbackOrigin describes a fallback origin +type FallbackOrigin struct { + Value string `json:"value"` + ID string `json:"id,omitempty"` +} + +// FallbackOriginResponse represents the response from the fallback_origin endpoint +type FallbackOriginResponse struct { + Response + Result FallbackOrigin `json:"result"` +} + +// zoneSubscriptionRatePlanPayload is used to build the JSON payload for +// setting a particular rate plan on an existing zone. +type zoneSubscriptionRatePlanPayload struct { + RatePlan struct { + ID string `json:"id"` + } `json:"rate_plan"` +} + +// CreateZone creates a zone on an account. +// +// Setting jumpstart to true will attempt to automatically scan for existing +// DNS records. Setting this to false will create the zone with no DNS records. +// +// If account is non-empty, it must have at least the ID field populated. +// This will add the new zone to the specified multi-user account. +// +// API reference: https://api.cloudflare.com/#zone-create-a-zone +func (api *API) CreateZone(ctx context.Context, name string, jumpstart bool, account Account, zoneType string) (Zone, error) { + var newzone newZone + newzone.Name = name + newzone.JumpStart = jumpstart + if account.ID != "" { + newzone.Account = &account + } + + if zoneType == "partial" { + newzone.Type = "partial" + } else { + newzone.Type = "full" + } + + res, err := api.makeRequestContext(ctx, http.MethodPost, "/zones", newzone) + if err != nil { + return Zone{}, err + } + + var r ZoneResponse + err = json.Unmarshal(res, &r) + if err != nil { + return Zone{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ZoneActivationCheck initiates another zone activation check for newly-created zones. +// +// API reference: https://api.cloudflare.com/#zone-initiate-another-zone-activation-check +func (api *API) ZoneActivationCheck(ctx context.Context, zoneID string) (Response, error) { + res, err := api.makeRequestContext(ctx, http.MethodPut, "/zones/"+zoneID+"/activation_check", nil) + if err != nil { + return Response{}, err + } + var r Response + err = json.Unmarshal(res, &r) + if err != nil { + return Response{}, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// ListZones lists zones on an account. Optionally takes a list of zone names +// to filter against. +// +// API reference: https://api.cloudflare.com/#zone-list-zones +func (api *API) ListZones(ctx context.Context, z ...string) ([]Zone, error) { + var zones []Zone + if len(z) > 0 { + var ( + v = url.Values{} + r ZonesResponse + ) + for _, zone := range z { + v.Set("name", normalizeZoneName(zone)) + res, err := api.makeRequestContext(ctx, http.MethodGet, "/zones?"+v.Encode(), nil) + if err != nil { + return []Zone{}, err + } + err = json.Unmarshal(res, &r) + if err != nil { + return []Zone{}, errors.Wrap(err, errUnmarshalError) + } + if !r.Success { + // TODO: Provide an actual error message instead of always returning nil + return []Zone{}, err + } + zones = append(zones, r.Result...) + } + } else { + res, err := api.ListZonesContext(ctx) + if err != nil { + return nil, err + } + + zones = res.Result + } + + return zones, nil +} + +const listZonesPerPage = 50 + +// listZonesFetch fetches one page of zones. +// This is placed as a separate function to prevent any possibility of unintended capturing. +func (api *API) listZonesFetch(ctx context.Context, wg *sync.WaitGroup, errc chan error, + path string, pageSize int, buf []Zone) { + defer wg.Done() + + // recordError sends the error to errc in a non-blocking manner + recordError := func(err error) { + select { + case errc <- err: + default: + } + } + + res, err := api.makeRequestContext(ctx, http.MethodGet, path, nil) + if err != nil { + recordError(err) + return + } + + var r ZonesResponse + err = json.Unmarshal(res, &r) + if err != nil { + recordError(err) + return + } + + if len(r.Result) != pageSize { + recordError(errors.New(errResultInfo)) + return + } + + copy(buf, r.Result) +} + +// ListZonesContext lists all zones on an account automatically handling the +// pagination. Optionally takes a list of ReqOptions. +func (api *API) ListZonesContext(ctx context.Context, opts ...ReqOption) (r ZonesResponse, err error) { + opt := reqOption{ + params: url.Values{}, + } + for _, of := range opts { + of(&opt) + } + + if opt.params.Get("page") != "" || opt.params.Get("per_page") != "" { + return ZonesResponse{}, errors.New(errManualPagination) + } + + opt.params.Add("per_page", strconv.Itoa(listZonesPerPage)) + + res, err := api.makeRequestContext(ctx, http.MethodGet, "/zones?"+opt.params.Encode(), nil) + if err != nil { + return ZonesResponse{}, err + } + err = json.Unmarshal(res, &r) + if err != nil { + return ZonesResponse{}, errors.Wrap(err, errUnmarshalError) + } + + // avoid overhead in most common cases where the total #zones <= 50 + if r.TotalPages < 2 { + return r, nil + } + + // parameters of pagination + var ( + totalPageCount = r.TotalPages + totalCount = r.Total + + // zones is a large slice to prevent resizing during concurrent writes. + zones = make([]Zone, totalCount) + ) + + // Copy the first page into zones. + copy(zones, r.Result) + + var wg sync.WaitGroup + wg.Add(totalPageCount - 1) // all pages except the first one. + errc := make(chan error, 1) // getting the first error + + // Creating all the workers. + for pageNum := 2; pageNum <= totalPageCount; pageNum++ { + // Note: URL.Values is just a map[string], so this would override the existing 'page' + opt.params.Set("page", strconv.Itoa(pageNum)) + + // start is the first index in the zone buffer + start := listZonesPerPage * (pageNum - 1) + + pageSize := listZonesPerPage + if pageNum == totalPageCount { + // The size of the last page (which would be <= 50). + pageSize = totalCount - start + } + + go api.listZonesFetch(ctx, &wg, errc, "/zones?"+opt.params.Encode(), pageSize, zones[start:]) + } + + wg.Wait() + + select { + case err := <-errc: // if there were any errors + return ZonesResponse{}, err + default: // if there were no errors, the receive statement should block + r.Result = zones + return r, nil + } +} + +// ZoneDetails fetches information about a zone. +// +// API reference: https://api.cloudflare.com/#zone-zone-details +func (api *API) ZoneDetails(ctx context.Context, zoneID string) (Zone, error) { + res, err := api.makeRequestContext(ctx, http.MethodGet, "/zones/"+zoneID, nil) + if err != nil { + return Zone{}, err + } + var r ZoneResponse + err = json.Unmarshal(res, &r) + if err != nil { + return Zone{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ZoneOptions is a subset of Zone, for editable options. +type ZoneOptions struct { + Paused *bool `json:"paused,omitempty"` + VanityNS []string `json:"vanity_name_servers,omitempty"` + Plan *ZonePlan `json:"plan,omitempty"` + Type string `json:"type,omitempty"` +} + +// ZoneSetPaused pauses Cloudflare service for the entire zone, sending all +// traffic direct to the origin. +func (api *API) ZoneSetPaused(ctx context.Context, zoneID string, paused bool) (Zone, error) { + zoneopts := ZoneOptions{Paused: &paused} + zone, err := api.EditZone(ctx, zoneID, zoneopts) + if err != nil { + return Zone{}, err + } + + return zone, nil +} + +// ZoneSetType toggles the type for an existing zone. +// +// Valid values for `type` are "full" and "partial" +// +// API reference: https://api.cloudflare.com/#zone-edit-zone +func (api *API) ZoneSetType(ctx context.Context, zoneID string, zoneType string) (Zone, error) { + zoneopts := ZoneOptions{Type: zoneType} + zone, err := api.EditZone(ctx, zoneID, zoneopts) + if err != nil { + return Zone{}, err + } + + return zone, nil +} + +// ZoneSetVanityNS sets custom nameservers for the zone. +// These names must be within the same zone. +func (api *API) ZoneSetVanityNS(ctx context.Context, zoneID string, ns []string) (Zone, error) { + zoneopts := ZoneOptions{VanityNS: ns} + zone, err := api.EditZone(ctx, zoneID, zoneopts) + if err != nil { + return Zone{}, err + } + + return zone, nil +} + +// ZoneSetPlan sets the rate plan of an existing zone. +// +// Valid values for `planType` are "CF_FREE", "CF_PRO", "CF_BIZ" and +// "CF_ENT". +// +// API reference: https://api.cloudflare.com/#zone-subscription-create-zone-subscription +func (api *API) ZoneSetPlan(ctx context.Context, zoneID string, planType string) error { + zonePayload := zoneSubscriptionRatePlanPayload{} + zonePayload.RatePlan.ID = planType + + uri := fmt.Sprintf("/zones/%s/subscription", zoneID) + + _, err := api.makeRequestContext(ctx, http.MethodPost, uri, zonePayload) + if err != nil { + return err + } + + return nil +} + +// ZoneUpdatePlan updates the rate plan of an existing zone. +// +// Valid values for `planType` are "CF_FREE", "CF_PRO", "CF_BIZ" and +// "CF_ENT". +// +// API reference: https://api.cloudflare.com/#zone-subscription-update-zone-subscription +func (api *API) ZoneUpdatePlan(ctx context.Context, zoneID string, planType string) error { + zonePayload := zoneSubscriptionRatePlanPayload{} + zonePayload.RatePlan.ID = planType + + uri := fmt.Sprintf("/zones/%s/subscription", zoneID) + + _, err := api.makeRequestContext(ctx, http.MethodPut, uri, zonePayload) + if err != nil { + return err + } + + return nil +} + +// EditZone edits the given zone. +// +// This is usually called by ZoneSetPaused, ZoneSetType, or ZoneSetVanityNS. +// +// API reference: https://api.cloudflare.com/#zone-edit-zone-properties +func (api *API) EditZone(ctx context.Context, zoneID string, zoneOpts ZoneOptions) (Zone, error) { + res, err := api.makeRequestContext(ctx, http.MethodPatch, "/zones/"+zoneID, zoneOpts) + if err != nil { + return Zone{}, err + } + var r ZoneResponse + err = json.Unmarshal(res, &r) + if err != nil { + return Zone{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// PurgeEverything purges the cache for the given zone. +// +// Note: this will substantially increase load on the origin server for that +// zone if there is a high cached vs. uncached request ratio. +// +// API reference: https://api.cloudflare.com/#zone-purge-all-files +func (api *API) PurgeEverything(ctx context.Context, zoneID string) (PurgeCacheResponse, error) { + uri := fmt.Sprintf("/zones/%s/purge_cache", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, PurgeCacheRequest{true, nil, nil, nil, nil}) + if err != nil { + return PurgeCacheResponse{}, err + } + var r PurgeCacheResponse + err = json.Unmarshal(res, &r) + if err != nil { + return PurgeCacheResponse{}, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// PurgeCache purges the cache using the given PurgeCacheRequest (zone/url/tag). +// +// API reference: https://api.cloudflare.com/#zone-purge-individual-files-by-url-and-cache-tags +func (api *API) PurgeCache(ctx context.Context, zoneID string, pcr PurgeCacheRequest) (PurgeCacheResponse, error) { + return api.PurgeCacheContext(ctx, zoneID, pcr) +} + +// PurgeCacheContext purges the cache using the given PurgeCacheRequest (zone/url/tag). +// +// API reference: https://api.cloudflare.com/#zone-purge-individual-files-by-url-and-cache-tags +func (api *API) PurgeCacheContext(ctx context.Context, zoneID string, pcr PurgeCacheRequest) (PurgeCacheResponse, error) { + uri := fmt.Sprintf("/zones/%s/purge_cache", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPost, uri, pcr) + if err != nil { + return PurgeCacheResponse{}, err + } + var r PurgeCacheResponse + err = json.Unmarshal(res, &r) + if err != nil { + return PurgeCacheResponse{}, errors.Wrap(err, errUnmarshalError) + } + return r, nil +} + +// DeleteZone deletes the given zone. +// +// API reference: https://api.cloudflare.com/#zone-delete-a-zone +func (api *API) DeleteZone(ctx context.Context, zoneID string) (ZoneID, error) { + res, err := api.makeRequestContext(ctx, http.MethodDelete, "/zones/"+zoneID, nil) + if err != nil { + return ZoneID{}, err + } + var r ZoneIDResponse + err = json.Unmarshal(res, &r) + if err != nil { + return ZoneID{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// AvailableZoneRatePlans returns information about all plans available to the specified zone. +// +// API reference: https://api.cloudflare.com/#zone-plan-available-plans +func (api *API) AvailableZoneRatePlans(ctx context.Context, zoneID string) ([]ZoneRatePlan, error) { + uri := fmt.Sprintf("/zones/%s/available_rate_plans", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []ZoneRatePlan{}, err + } + var r AvailableZoneRatePlansResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []ZoneRatePlan{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// AvailableZonePlans returns information about all plans available to the specified zone. +// +// API reference: https://api.cloudflare.com/#zone-rate-plan-list-available-plans +func (api *API) AvailableZonePlans(ctx context.Context, zoneID string) ([]ZonePlan, error) { + uri := fmt.Sprintf("/zones/%s/available_plans", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return []ZonePlan{}, err + } + var r AvailableZonePlansResponse + err = json.Unmarshal(res, &r) + if err != nil { + return []ZonePlan{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// encode encodes non-nil fields into URL encoded form. +func (o ZoneAnalyticsOptions) encode() string { + v := url.Values{} + if o.Since != nil { + v.Set("since", (*o.Since).Format(time.RFC3339)) + } + if o.Until != nil { + v.Set("until", (*o.Until).Format(time.RFC3339)) + } + if o.Continuous != nil { + v.Set("continuous", fmt.Sprintf("%t", *o.Continuous)) + } + return v.Encode() +} + +// ZoneAnalyticsDashboard returns zone analytics information. +// +// API reference: https://api.cloudflare.com/#zone-analytics-dashboard +func (api *API) ZoneAnalyticsDashboard(ctx context.Context, zoneID string, options ZoneAnalyticsOptions) (ZoneAnalyticsData, error) { + uri := fmt.Sprintf("/zones/%s/analytics/dashboard?%s", zoneID, options.encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return ZoneAnalyticsData{}, err + } + var r zoneAnalyticsDataResponse + err = json.Unmarshal(res, &r) + if err != nil { + return ZoneAnalyticsData{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ZoneAnalyticsByColocation returns zone analytics information by datacenter. +// +// API reference: https://api.cloudflare.com/#zone-analytics-analytics-by-co-locations +func (api *API) ZoneAnalyticsByColocation(ctx context.Context, zoneID string, options ZoneAnalyticsOptions) ([]ZoneAnalyticsColocation, error) { + uri := fmt.Sprintf("/zones/%s/analytics/colos?%s", zoneID, options.encode()) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + var r zoneAnalyticsColocationResponse + err = json.Unmarshal(res, &r) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// ZoneSettings returns all of the settings for a given zone. +// +// API reference: https://api.cloudflare.com/#zone-settings-get-all-zone-settings +func (api *API) ZoneSettings(ctx context.Context, zoneID string) (*ZoneSettingResponse, error) { + uri := fmt.Sprintf("/zones/%s/settings", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return nil, err + } + + response := &ZoneSettingResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// UpdateZoneSettings updates the settings for a given zone. +// +// API reference: https://api.cloudflare.com/#zone-settings-edit-zone-settings-info +func (api *API) UpdateZoneSettings(ctx context.Context, zoneID string, settings []ZoneSetting) (*ZoneSettingResponse, error) { + uri := fmt.Sprintf("/zones/%s/settings", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, struct { + Items []ZoneSetting `json:"items"` + }{settings}) + if err != nil { + return nil, err + } + + response := &ZoneSettingResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// ZoneSSLSettings returns information about SSL setting to the specified zone. +// +// API reference: https://api.cloudflare.com/#zone-settings-get-ssl-setting +func (api *API) ZoneSSLSettings(ctx context.Context, zoneID string) (ZoneSSLSetting, error) { + uri := fmt.Sprintf("/zones/%s/settings/ssl", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return ZoneSSLSetting{}, err + } + var r ZoneSSLSettingResponse + err = json.Unmarshal(res, &r) + if err != nil { + return ZoneSSLSetting{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateZoneSSLSettings update information about SSL setting to the specified zone. +// +// API reference: https://api.cloudflare.com/#zone-settings-change-ssl-setting +func (api *API) UpdateZoneSSLSettings(ctx context.Context, zoneID string, sslValue string) (ZoneSSLSetting, error) { + uri := fmt.Sprintf("/zones/%s/settings/ssl", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, ZoneSSLSetting{Value: sslValue}) + if err != nil { + return ZoneSSLSetting{}, err + } + var r ZoneSSLSettingResponse + err = json.Unmarshal(res, &r) + if err != nil { + return ZoneSSLSetting{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// FallbackOrigin returns information about the fallback origin for the specified zone. +// +// API reference: https://developers.cloudflare.com/ssl/ssl-for-saas/api-calls/#fallback-origin-configuration +func (api *API) FallbackOrigin(ctx context.Context, zoneID string) (FallbackOrigin, error) { + uri := fmt.Sprintf("/zones/%s/fallback_origin", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return FallbackOrigin{}, err + } + + var r FallbackOriginResponse + err = json.Unmarshal(res, &r) + if err != nil { + return FallbackOrigin{}, errors.Wrap(err, errUnmarshalError) + } + + return r.Result, nil +} + +// UpdateFallbackOrigin updates the fallback origin for a given zone. +// +// API reference: https://developers.cloudflare.com/ssl/ssl-for-saas/api-calls/#4-example-patch-to-change-fallback-origin +func (api *API) UpdateFallbackOrigin(ctx context.Context, zoneID string, fbo FallbackOrigin) (*FallbackOriginResponse, error) { + uri := fmt.Sprintf("/zones/%s/fallback_origin", zoneID) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, fbo) + if err != nil { + return nil, err + } + + response := &FallbackOriginResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// normalizeZoneName tries to convert IDNs (international domain names) +// from Punycode to Unicode form. If the given zone name is not represented +// as Punycode, or converting fails (for invalid representations), it +// is returned unchanged. +// +// Because all the zone name comparison is currently done using the API service +// (except for comparison with the empty string), theoretically, we could +// remove this function from the Go library. However, there should be no harm +// calling this function other than gelable performance penalty. +// +// Note: conversion errors are silently discarded. +func normalizeZoneName(name string) string { + if n, err := idna.ToUnicode(name); err == nil { + return n + } + return name +} + +// ZoneSingleSetting returns information about specified setting to the specified zone. +// +// API reference: https://api.cloudflare.com/#zone-settings-get-all-zone-settings +func (api *API) ZoneSingleSetting(ctx context.Context, zoneID, settingName string) (ZoneSetting, error) { + uri := fmt.Sprintf("/zones/%s/settings/%s", zoneID, settingName) + res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil) + if err != nil { + return ZoneSetting{}, err + } + var r ZoneSettingSingleResponse + err = json.Unmarshal(res, &r) + if err != nil { + return ZoneSetting{}, errors.Wrap(err, errUnmarshalError) + } + return r.Result, nil +} + +// UpdateZoneSingleSetting updates the specified setting for a given zone. +// +// API reference: https://api.cloudflare.com/#zone-settings-edit-zone-settings-info +func (api *API) UpdateZoneSingleSetting(ctx context.Context, zoneID, settingName string, setting ZoneSetting) (*ZoneSettingSingleResponse, error) { + uri := fmt.Sprintf("/zones/%s/settings/%s", zoneID, settingName) + res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, setting) + if err != nil { + return nil, err + } + + response := &ZoneSettingSingleResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return nil, errors.Wrap(err, errUnmarshalError) + } + + return response, nil +} + +// ZoneExport returns the text BIND config for the given zone +// +// API reference: https://api.cloudflare.com/#dns-records-for-a-zone-export-dns-records +func (api *API) ZoneExport(ctx context.Context, zoneID string) (string, error) { + res, err := api.makeRequestContext(ctx, http.MethodGet, "/zones/"+zoneID+"/dns_records/export", nil) + if err != nil { + return "", err + } + return string(res), nil +} + +// ZoneDNSSECResponse represents the response from the Zone DNSSEC Setting +type ZoneDNSSECResponse struct { + Response + Result ZoneDNSSEC `json:"result"` +} + +// ZoneDNSSEC represents the response from the Zone DNSSEC Setting result +type ZoneDNSSEC struct { + Status string `json:"status"` + Flags int `json:"flags"` + Algorithm string `json:"algorithm"` + KeyType string `json:"key_type"` + DigestType string `json:"digest_type"` + DigestAlgorithm string `json:"digest_algorithm"` + Digest string `json:"digest"` + DS string `json:"ds"` + KeyTag int `json:"key_tag"` + PublicKey string `json:"public_key"` + ModifiedOn time.Time `json:"modified_on"` +} + +// ZoneDNSSECSetting returns the DNSSEC details of a zone +// +// API reference: https://api.cloudflare.com/#dnssec-dnssec-details +func (api *API) ZoneDNSSECSetting(ctx context.Context, zoneID string) (ZoneDNSSEC, error) { + res, err := api.makeRequestContext(ctx, http.MethodGet, "/zones/"+zoneID+"/dnssec", nil) + if err != nil { + return ZoneDNSSEC{}, err + } + response := ZoneDNSSECResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return ZoneDNSSEC{}, errors.Wrap(err, errUnmarshalError) + } + + return response.Result, nil +} + +// ZoneDNSSECDeleteResponse represents the response from the Zone DNSSEC Delete request +type ZoneDNSSECDeleteResponse struct { + Response + Result string `json:"result"` +} + +// DeleteZoneDNSSEC deletes DNSSEC for zone +// +// API reference: https://api.cloudflare.com/#dnssec-delete-dnssec-records +func (api *API) DeleteZoneDNSSEC(ctx context.Context, zoneID string) (string, error) { + res, err := api.makeRequestContext(ctx, http.MethodDelete, "/zones/"+zoneID+"/dnssec", nil) + if err != nil { + return "", err + } + response := ZoneDNSSECDeleteResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return "", errors.Wrap(err, errUnmarshalError) + } + return response.Result, nil +} + +// ZoneDNSSECUpdateOptions represents the options for DNSSEC update +type ZoneDNSSECUpdateOptions struct { + Status string `json:"status"` +} + +// UpdateZoneDNSSEC updates DNSSEC for a zone +// +// API reference: https://api.cloudflare.com/#dnssec-edit-dnssec-status +func (api *API) UpdateZoneDNSSEC(ctx context.Context, zoneID string, options ZoneDNSSECUpdateOptions) (ZoneDNSSEC, error) { + res, err := api.makeRequestContext(ctx, http.MethodPatch, "/zones/"+zoneID+"/dnssec", options) + if err != nil { + return ZoneDNSSEC{}, err + } + response := ZoneDNSSECResponse{} + err = json.Unmarshal(res, &response) + if err != nil { + return ZoneDNSSEC{}, errors.Wrap(err, errUnmarshalError) + } + return response.Result, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 30a982c437701..3d159f90949cf 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -209,6 +209,9 @@ github.com/cespare/xxhash # github.com/cespare/xxhash/v2 v2.1.2 ## explicit; go 1.11 github.com/cespare/xxhash/v2 +# github.com/cloudflare/cloudflare-go v0.27.0 => github.com/cyriltovena/cloudflare-go v0.27.1-0.20211118103540-ff77400bcb93 +## explicit; go 1.15 +github.com/cloudflare/cloudflare-go # github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 ## explicit; go 1.11 github.com/cncf/udpa/go/udpa/data/orca/v1 @@ -1788,3 +1791,4 @@ sigs.k8s.io/yaml # github.com/gocql/gocql => github.com/grafana/gocql v0.0.0-20200605141915-ba5dc39ece85 # github.com/bradfitz/gomemcache => github.com/themihai/gomemcache v0.0.0-20180902122335-24332e2d58ab # gopkg.in/Graylog2/go-gelf.v2 => github.com/grafana/go-gelf v0.0.0-20211112153804-126646b86de8 +# github.com/cloudflare/cloudflare-go => github.com/cyriltovena/cloudflare-go v0.27.1-0.20211118103540-ff77400bcb93