diff --git a/api/server/shared/config/env/envconfs.go b/api/server/shared/config/env/envconfs.go index ad3bff0c97..36532b1b00 100644 --- a/api/server/shared/config/env/envconfs.go +++ b/api/server/shared/config/env/envconfs.go @@ -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"` diff --git a/api/server/shared/config/loader/loader.go b/api/server/shared/config/loader/loader.go index 25a634019c..08b0a186a9 100644 --- a/api/server/shared/config/loader/loader.go +++ b/api/server/shared/config/loader/loader.go @@ -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" @@ -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 diff --git a/go.mod b/go.mod index 4477be8e19..2caa68f96a 100644 --- a/go.mod +++ b/go.mod @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/go.sum b/go.sum index 73f40427a1..e022bf9bcc 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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= @@ -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= @@ -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= @@ -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= @@ -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= @@ -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= @@ -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= @@ -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= diff --git a/internal/integrations/cloudflare/cloudflare.go b/internal/integrations/cloudflare/cloudflare.go new file mode 100644 index 0000000000..60ab9d80c1 --- /dev/null +++ b/internal/integrations/cloudflare/cloudflare.go @@ -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 +} diff --git a/internal/integrations/dns/dns.go b/internal/integrations/dns/dns.go index 081422c875..e4bfeb7128 100644 --- a/internal/integrations/dns/dns.go +++ b/internal/integrations/dns/dns.go @@ -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) } diff --git a/internal/integrations/powerdns/powerdns.go b/internal/integrations/powerdns/powerdns.go index 702cac24b3..ba0325738f 100644 --- a/internal/integrations/powerdns/powerdns.go +++ b/internal/integrations/powerdns/powerdns.go @@ -3,11 +3,13 @@ package powerdns import ( "encoding/json" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "strings" "time" + + "github.com/porter-dev/porter/internal/integrations/dns" ) // Client contains an API client for a PowerDNS server @@ -20,12 +22,12 @@ type Client struct { } // NewClient creates a new bind API client -func NewClient(serverURL, apiKey, runDomain string) *Client { +func NewClient(serverURL, apiKey, runDomain string) Client { httpClient := &http.Client{ Timeout: time.Minute, } - return &Client{apiKey, serverURL, runDomain, httpClient} + return Client{apiKey, serverURL, runDomain, httpClient} } // RecordData represents the data required to create or delete an A/CNAME record @@ -34,6 +36,7 @@ type RecordData struct { RRSets []RR `json:"rrsets"` } +// RR represents a dns resource record collection for PowerDNS type RR struct { Name string `json:"name"` Type string `json:"type"` @@ -42,6 +45,8 @@ type RR struct { Records []Record `json:"records"` } +// Record represents an individual record for a given +// PowerDNS resource record type Record struct { Content string `json:"content"` Disabled bool `json:"disabled"` @@ -51,9 +56,9 @@ type Record struct { } // CreateCNAMERecord creates a new CNAME record for the nameserver -func (c *Client) CreateCNAMERecord(value, hostname string) error { - valueC := canonicalize(value) - hostnameC := canonicalize(hostname) +func (c Client) CreateCNAMERecord(record dns.Record) error { + valueC := canonicalize(record.Value) + hostnameC := canonicalize(fmt.Sprintf("%s.%s", record.Name, record.RootDomain)) return c.sendRequest("PATCH", &RecordData{ RRSets: []RR{{ @@ -73,8 +78,8 @@ func (c *Client) CreateCNAMERecord(value, hostname string) error { } // CreateARecord creates a new A record for the nameserver -func (c *Client) CreateARecord(value, hostname string) error { - hostnameC := canonicalize(hostname) +func (c Client) CreateARecord(record dns.Record) error { + hostnameC := canonicalize(fmt.Sprintf("%s.%s", record.Name, record.RootDomain)) return c.sendRequest("PATCH", &RecordData{ RRSets: []RR{{ @@ -83,7 +88,7 @@ func (c *Client) CreateARecord(value, hostname string) error { ChangeType: "REPLACE", TTL: 300, Records: []Record{{ - Content: value, + Content: record.Value, Disabled: false, Name: hostnameC, Type: "A", @@ -136,7 +141,7 @@ func (c *Client) sendRequest(method string, data *RecordData) error { defer res.Body.Close() if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest { - resBytes, err := ioutil.ReadAll(res.Body) + resBytes, err := io.ReadAll(res.Body) if err != nil { return fmt.Errorf("request failed with status code %d, but could not read body (%s)\n", res.StatusCode, err.Error()) } diff --git a/internal/kubernetes/domain/domain.go b/internal/kubernetes/domain/domain.go index f4301194e3..27a0e1d373 100644 --- a/internal/kubernetes/domain/domain.go +++ b/internal/kubernetes/domain/domain.go @@ -101,11 +101,16 @@ func (c *CreateDNSRecordConfig) NewDNSRecordForEndpoint() *models.DNSRecord { // CreateDomain creates a new record for the vanity domain func (e *DNSRecord) CreateDomain(dnsClient *dns.Client) error { isIPv4 := net.ParseIP(e.Endpoint) != nil - domain := fmt.Sprintf("%s.%s", e.SubdomainPrefix, e.RootDomain) + dnsType := dns.RecordType_CNAME if isIPv4 { - return dnsClient.CreateARecord(e.Endpoint, domain) + dnsType = dns.RecordType_A } - return dnsClient.CreateCNAMERecord(e.Endpoint, domain) + return dnsClient.CreateRecord(dns.Record{ + Type: dnsType, + Value: e.Endpoint, + Name: e.SubdomainPrefix, + RootDomain: e.RootDomain, + }) } diff --git a/zarf/helm/.serverenv b/zarf/helm/.serverenv index 22a7af2148..03b1aa9ed4 100644 --- a/zarf/helm/.serverenv +++ b/zarf/helm/.serverenv @@ -15,6 +15,19 @@ ENABLE_CAPI_PROVISIONER=true NATS_URL=nats:4222 CLUSTER_CONTROL_PLANE_ADDRESS=http://ccp-web:7833 +# Domain we use to generate custom subdomains from +APP_ROOT_DOMAIN=withporter.run + +# Controls which external dns provider to use +# Specific clients may require extra tokens +# Options: cloudflare, powerdns, or empty string +DNS_PROVIDER=powerdns + +# Cloudflare DNS provider +# if set, can be used as an alternative to PowerDNS +# Create a token here: https://dash.cloudflare.com/profile/api-tokens +CLOUDFLARE_API_TOKEN= + # Github Login OAuth. More information found at https://docs.porter.run/enterprise/self-hosted/integrations/github#setting-up-github-repository-integrations GITHUB_LOGIN_ENABLED=false