Skip to content

Commit

Permalink
POR-1703: add support for cloudflare dns (#3559)
Browse files Browse the repository at this point in the history
  • Loading branch information
jose-fully-ported authored Sep 15, 2023
1 parent ab844db commit e855b9f
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 29 deletions.
7 changes: 7 additions & 0 deletions api/server/shared/config/env/envconfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,13 @@ type ServerConf struct {

SegmentClientKey string `env:"SEGMENT_CLIENT_KEY"`

// DnsProvider controls which provider to use for dns (powerdns or cloudflare)
// Setting this to empty string will disable external dns
DnsProvider string `env:"DNS_PROVIDER,default=powerdns"`

// Cloudflare API Key
CloudflareAPIToken string `env:"CLOUDFLARE_API_TOKEN"`

// PowerDNS client API key and the host of the PowerDNS API server
PowerDNSAPIServerURL string `env:"POWER_DNS_API_SERVER_URL"`
PowerDNSAPIKey string `env:"POWER_DNS_API_KEY"`
Expand Down
17 changes: 15 additions & 2 deletions api/server/shared/config/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/porter-dev/porter/internal/billing"
"github.com/porter-dev/porter/internal/features"
"github.com/porter-dev/porter/internal/helm/urlcache"
"github.com/porter-dev/porter/internal/integrations/cloudflare"
"github.com/porter-dev/porter/internal/integrations/dns"
"github.com/porter-dev/porter/internal/integrations/powerdns"
"github.com/porter-dev/porter/internal/notifier"
Expand Down Expand Up @@ -304,8 +305,20 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
res.AnalyticsClient = analytics.InitializeAnalyticsSegmentClient(sc.SegmentClientKey, res.Logger)
res.Logger.Info().Msg("Created analytics client")

if sc.PowerDNSAPIKey != "" && sc.PowerDNSAPIServerURL != "" {
res.DNSClient = &dns.Client{Client: powerdns.NewClient(sc.PowerDNSAPIServerURL, sc.PowerDNSAPIKey, sc.AppRootDomain)}
switch sc.DnsProvider {
case "powerdns":
if sc.PowerDNSAPIKey != "" && sc.PowerDNSAPIServerURL != "" {
res.DNSClient = &dns.Client{Client: powerdns.NewClient(sc.PowerDNSAPIServerURL, sc.PowerDNSAPIKey, sc.AppRootDomain)}
}
case "cloudflare":
if sc.CloudflareAPIToken != "" {
cloudflareClient, err := cloudflare.NewClient(sc.CloudflareAPIToken, sc.AppRootDomain)
if err != nil {
return res, fmt.Errorf("unable to create cloudflare client: %w", err)
}

res.DNSClient = &dns.Client{Client: cloudflareClient}
}
}

res.EnableCAPIProvisioner = sc.EnableCAPIProvisioner
Expand Down
14 changes: 8 additions & 6 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ require (
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.10.0
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.6.0
golang.org/x/net v0.10.0
golang.org/x/crypto v0.12.0
golang.org/x/net v0.14.0
golang.org/x/oauth2 v0.8.0
google.golang.org/api v0.114.0
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc
Expand Down Expand Up @@ -123,6 +123,7 @@ require (
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.0.0-20220517224237-e6f29200ae04 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20220327082430-c57b701bfc08 // indirect
github.com/cloudflare/cloudflare-go v0.76.0 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/elazarl/goproxy v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
Expand All @@ -131,12 +132,13 @@ require (
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.4 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/launchdarkly/ccache v1.1.0 // indirect
github.com/launchdarkly/eventsource v1.6.2 // indirect
Expand Down Expand Up @@ -341,9 +343,9 @@ require (
go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd // indirect
golang.org/x/mod v0.8.0 // indirect
golang.org/x/sync v0.1.0
golang.org/x/sys v0.8.0 // indirect
golang.org/x/term v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/sys v0.11.0 // indirect
golang.org/x/term v0.11.0 // indirect
golang.org/x/text v0.12.0 // indirect
golang.org/x/time v0.3.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
Expand Down
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ github.com/cli/oauth v0.8.0/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/cloudflare-go v0.76.0 h1:0YsPw0KHWJK2fpivzx38X5onurfM0GkanZtS8HAqlj0=
github.com/cloudflare/cloudflare-go v0.76.0/go.mod h1:5ocQT9qQ99QsT1Ii2751490Z5J+W/nv6jOj+lSAe4ug=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
Expand Down Expand Up @@ -733,6 +735,8 @@ github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJA
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
Expand Down Expand Up @@ -957,6 +961,7 @@ github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/S
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.0.0 h1:bkKf0BeBXcSYa7f5Fyi9gMuQ8gNsxeiNpZjR6VxNZeo=
github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
Expand All @@ -965,6 +970,8 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
Expand Down Expand Up @@ -1988,6 +1995,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down Expand Up @@ -2098,6 +2107,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down Expand Up @@ -2264,6 +2275,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
Expand All @@ -2274,6 +2287,8 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand All @@ -2286,6 +2301,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
Expand Down
89 changes: 89 additions & 0 deletions internal/integrations/cloudflare/cloudflare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package cloudflare

import (
"context"
"fmt"

"github.com/cloudflare/cloudflare-go"
"github.com/porter-dev/porter/internal/integrations/dns"
)

// RecordType strongly types cloudflare dns entry types
type RecordType string

const (
// RecordType_A declares an A record type for cloudflare
RecordType_A RecordType = "A"

// RecordType_CNAME declares an CNME record type for cloudflare
RecordType_CNAME = "CNAME"
)

// TTL sets the TTL for Cloudflare DNS records
const TTL = 300

// Client is a struct wrapper around the cloudflare client
type Client struct {
zoneID string

client *cloudflare.API
}

// NewClient creates a new cloudflare API client
func NewClient(apiToken string, runDomain string) (Client, error) {
client, err := cloudflare.NewWithAPIToken(apiToken)
if err != nil {
return Client{}, err
}

zoneID, err := client.ZoneIDByName(runDomain)
if err != nil {
return Client{}, err
}

return Client{client: client, zoneID: zoneID}, nil
}

// CreateCNAMERecord creates a new CNAME record for the nameserver
//
// The method ignores record.RootDomain in favor of the zoneID derived from c.runDomain
func (c Client) CreateCNAMERecord(record dns.Record) error {
proxy := false

cloudflareRecord := cloudflare.CreateDNSRecordParams{
Name: record.Name,
Type: string(RecordType_CNAME),
Content: record.Value,
TTL: TTL,
Proxied: &proxy,
}

_, err := c.client.CreateDNSRecord(context.Background(), cloudflare.ZoneIdentifier(c.zoneID), cloudflareRecord)
if err != nil {
return fmt.Errorf("failed to create CNAME dns record: %w", err)
}

return err
}

// CreateARecord creates a new A record for the nameserver
//
// The method ignores record.RootDomain in favor of the zoneID derived from c.runDomain
func (c Client) CreateARecord(record dns.Record) error {
proxy := false

cloudflareRecord := cloudflare.CreateDNSRecordParams{
Name: record.Name,
Type: string(RecordType_A),
Content: record.Value,
TTL: TTL,
Proxied: &proxy,
}

_, err := c.client.CreateDNSRecord(context.Background(), cloudflare.ZoneIdentifier(c.zoneID), cloudflareRecord)
if err != nil {
return fmt.Errorf("failed to create A dns record: %w", err)
}

return nil
}
40 changes: 32 additions & 8 deletions internal/integrations/dns/dns.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
package dns

import "github.com/porter-dev/porter/internal/integrations/powerdns"
// RecordType strongly types dns record types
type RecordType int

const (
// RecordType_A represents a DNS RecordType_A record
RecordType_A RecordType = iota

// RecordType_CNAME represents a DNS RecordType_CNAME record
RecordType_CNAME
)

// WrappedClient is an interface describing a wrapper
// around a particular dns implementation
type WrappedClient interface {
CreateARecord(record Record) error
CreateCNAMERecord(record Record) error
}

// Client wraps the underlying powerdns client
// providing a stable api around interacting with DNS
type Client struct {
Client *powerdns.Client
Client WrappedClient
}

// CreateARecord creates a new A record
func (c Client) CreateARecord(value, hostname string) error {
return c.Client.CreateARecord(value, hostname)
// Record describes a specific DNS record to create
// and can include implementation-specific attributes
type Record struct {
Type RecordType
Name string
RootDomain string
Value string
}

// CreateCNAMERecord creates a new CNAME record
func (c Client) CreateCNAMERecord(value, hostname string) error {
return c.Client.CreateCNAMERecord(value, hostname)
// CreateRecord creates a new dns record
func (c Client) CreateRecord(record Record) error {
if record.Type == RecordType_A {
return c.Client.CreateARecord(record)
}

return c.Client.CreateCNAMERecord(record)
}
Loading

0 comments on commit e855b9f

Please sign in to comment.