Skip to content

Commit

Permalink
Support Netcup as DNS provider
Browse files Browse the repository at this point in the history
Add support for https://www.netcup-wiki.de/wiki/CCP_API via
netcup-dns-api library.

See also: aellwein/netcup-dns-api#1

Co-Authored-By: Alex Ellwein <[email protected]>
  • Loading branch information
mrueg and aellwein committed Jan 6, 2023
1 parent 8d39464 commit a7a5686
Show file tree
Hide file tree
Showing 9 changed files with 792 additions and 1 deletion.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchroniz
* [TencentCloud DNSPod](https://cloud.tencent.com/product/cns)
* [Plural](https://www.plural.sh/)
* [Pi-hole](https://pi-hole.net/)
* [Netcup](https://netcup.de)

From this release, ExternalDNS can become aware of the records it is managing (enabled via `--registry=txt`), therefore ExternalDNS can safely manage non-empty hosted zones. We strongly encourage you to use `v0.5` (or greater) with `--registry=txt` enabled and `--txt-owner-id` set to a unique value that doesn't change for the lifetime of your cluster. You might also want to run ExternalDNS in a dry run mode (`--dry-run` flag) to see the changes to be submitted to your DNS Provider API.

Expand Down Expand Up @@ -124,6 +125,7 @@ The following table clarifies the current status of the providers according to t
| TencentCloud | Alpha | @Hyzhou |
| Plural | Alpha | @michaeljguarino |
| Pi-hole | Alpha | @tinyzimmer |
| Netcup | Alpha | @aellwein @mrueg |

## Kubernetes version compatibility

Expand Down Expand Up @@ -196,6 +198,7 @@ The following tutorials are provided:
* [TencentCloud](docs/tutorials/tencentcloud.md)
* [Plural](docs/tutorials/plural.md)
* [Pi-hole](docs/tutorials/pihole.md)
* [Netcup](docs/tutorials/netcup.md)

### Running Locally

Expand Down
208 changes: 208 additions & 0 deletions docs/tutorials/netcup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# Setting up ExternalDNS for Netcup

This tutorial describes how to setup ExternalDNS for usage within a Kubernetes cluster using Netcup.

Make sure to use **master** version of ExternalDNS for this tutorial.

## Creating Netcup Credentials

A secret containing the a Netcup API token and an API Password is needed for this provider. You can get a token for your user [here](https://www.customercontrolpanel.de/daten_aendern.php?sprung=api).

To create the API token secret you can run `kubectl create secret generic netcup-api-key --from-literal=EXTERNAL_DNS_NETCUP_API_KEY=<replace-with-your-access-token>`.

To create the API password secret you can run `kubectl create secret generic netcup-api-password --from-literal=EXTERNAL_DNS_NETCUP_API_PASSWORD=<replace-with-your-access-token>`.

## Deploy ExternalDNS

Connect your `kubectl` client to the cluster you want to test ExternalDNS with.

Besides the API key and password, it is mandatory to provide a customer id as well as a list of DNS zones you want ExternalDNS to manage.

Then apply one of the following manifests file to deploy ExternalDNS.

### Manifest (for clusters without RBAC enabled)

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:latest
args:
- --source=service # ingress is also possible
- --provider=netcup
- --netcup-zones="example.com"
- --netcup-customer-id="123456"
env:
- name: EXTERNAL_DNS_NETCUP_API_KEY
valueFrom:
secretKeyRef:
key: EXTERNAL_DNS_NETCUP_API_KEY
name: netcup-api-key
- name: EXTERNAL_DNS_NETCUP_API_PASSWORD
valueFrom:
secretKeyRef:
key: EXTERNAL_DNS_NETCUP_API_PASSWORD
name: netcup-api-password

```

### Manifest (for clusters with RBAC enabled)

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
rules:
- apiGroups: [""]
resources: ["services","endpoints","pods"]
verbs: ["get","watch","list"]
- apiGroups: ["extensions","networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get","watch","list"]
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: external-dns
subjects:
- kind: ServiceAccount
name: external-dns
namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: external-dns
spec:
strategy:
type: Recreate
selector:
matchLabels:
app: external-dns
template:
metadata:
labels:
app: external-dns
spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:latest
args:
- --source=service # ingress is also possible
- --provider=netcup
- --netcup-zones=example.com
- --netcup-customer-id=123456
env:
- name: EXTERNAL_DNS_NETCUP_API_TOKEN
valueFrom:
secretKeyRef:
key: EXTERNAL_DNS_NETCUP_API_TOKEN
name: netcup-api-token
- name: EXTERNAL_DNS_NETCUP_API_PASSWORD
valueFrom:
secretKeyRef:
key: EXTERNAL_DNS_NETCUP_API_PASSWORD
name: netcup-api-password
```
## Deploying an Nginx Service
Create a service file called 'nginx.yaml' with the following contents:
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx
annotations:
external-dns.alpha.kubernetes.io/hostname: example.com
spec:
selector:
app: nginx
type: LoadBalancer
ports:
- protocol: TCP
port: 80
targetPort: 80
```
Note the annotation on the service; use the same hostname as the Netcup DNS zone created above. The annotation may also be a subdomain
of the DNS zone (e.g. 'www.example.com').
By setting the TTL annotation on the service, you have to pass a valid TTL, which must be 120 or above.
This annotation is optional, if you won't set it, it will be 1 (automatic) which is 300.
ExternalDNS uses this annotation to determine what services should be registered with DNS. Removing the annotation
will cause ExternalDNS to remove the corresponding DNS records.
Create the deployment and service:
```
$ kubectl create -f nginx.yaml
```

Depending where you run your service it can take a little while for your cloud provider to create an external IP for the service.

Once the service has an external IP assigned, ExternalDNS will notice the new service IP address and synchronize
the Netcup DNS records.

## Verifying Netcup DNS records

Check your [Netcup domain overview](https://www.customercontrolpanel.de/domains.php) to view the domains associated with your Netcup account. There you can view the records for each domain.

The records should show the external IP address of the service as the A record for your domain.

## Cleanup

Now that we have verified that ExternalDNS will automatically manage Netcup DNS records, we can delete the tutorial's example:

```
$ kubectl delete -f nginx.yaml
$ kubectl delete -f externaldns.yaml
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/IBM/go-sdk-core/v5 v5.8.0
github.com/IBM/networking-go-sdk v0.32.0
github.com/StackExchange/dnscontrol v0.2.8
github.com/aellwein/netcup-dns-api v1.0.2
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.1
github.com/alecthomas/kingpin v2.2.5+incompatible
github.com/aliyun/alibaba-cloud-sdk-go v1.62.4
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ github.com/Venafi/vcert/v4 v4.14.3/go.mod h1:IL+6LA8QRWZbmcMzIr/vRhf9Aa6XDM2cQO5
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/Yamashou/gqlgenc v0.11.0 h1:y6I7CDrUdY4JBxfwss9168HTP5k/CdExLV5+YPG/3nY=
github.com/Yamashou/gqlgenc v0.11.0/go.mod h1:OeQhghEgvGWvRwzx9XjMeg3FUQOHnTo5/12iuJSJxLg=
github.com/aellwein/netcup-dns-api v1.0.2 h1:xOQlIffnny0GtcuBkx+4Izgkxa42zh5HFqGvK17E7kI=
github.com/aellwein/netcup-dns-api v1.0.2/go.mod h1:YQvT7mPbYBK+h213w0mHoMPt4HiQX9eI761BM4EWNjk=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/ahmetb/gen-crd-api-reference-docs v0.2.1-0.20201224172655-df869c1245d4/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8=
Expand Down
3 changes: 3 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import (
"sigs.k8s.io/external-dns/provider/infoblox"
"sigs.k8s.io/external-dns/provider/inmemory"
"sigs.k8s.io/external-dns/provider/linode"
"sigs.k8s.io/external-dns/provider/netcup"
"sigs.k8s.io/external-dns/provider/ns1"
"sigs.k8s.io/external-dns/provider/oci"
"sigs.k8s.io/external-dns/provider/ovh"
Expand Down Expand Up @@ -354,6 +355,8 @@ func main() {
p, err = plural.NewPluralProvider(cfg.PluralCluster, cfg.PluralProvider)
case "tencentcloud":
p, err = tencentcloud.NewTencentCloudProvider(domainFilter, zoneIDFilter, cfg.TencentCloudConfigFile, cfg.TencentCloudZoneType, cfg.DryRun)
case "netcup":
p, err = netcup.NewNetcupProvider(cfg.NetcupCustomerID, cfg.NetcupAPIKey, cfg.NetcupAPIPassword, cfg.NetcupZones, cfg.DryRun)
default:
log.Fatalf("unknown dns provider: %s", cfg.Provider)
}
Expand Down
16 changes: 15 additions & 1 deletion pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,10 @@ type Config struct {
PiholeTLSInsecureSkipVerify bool
PluralCluster string
PluralProvider string
NetcupCustomerID int
NetcupAPIKey string `secure:"yes"`
NetcupAPIPassword string `secure:"yes"`
NetcupZones []string
}

var defaultConfig = &Config{
Expand Down Expand Up @@ -340,6 +344,10 @@ var defaultConfig = &Config{
PiholeTLSInsecureSkipVerify: false,
PluralCluster: "",
PluralProvider: "",
NetcupCustomerID: 0,
NetcupAPIKey: "",
NetcupAPIPassword: "",
NetcupZones: []string{},
}

// NewConfig returns new Config object
Expand Down Expand Up @@ -429,7 +437,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("exclude-target-net", "Exclude target nets (optional)").StringsVar(&cfg.ExcludeTargetNets)

// Flags related to providers
providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "bluecat", "civo", "cloudflare", "coredns", "designate", "digitalocean", "dnsimple", "dyn", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "infoblox", "inmemory", "linode", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rcodezero", "rdns", "rfc2136", "safedns", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "vinyldns", "vultr"}
providers := []string{"akamai", "alibabacloud", "aws", "aws-sd", "azure", "azure-dns", "azure-private-dns", "bluecat", "civo", "cloudflare", "coredns", "designate", "digitalocean", "dnsimple", "dyn", "exoscale", "gandi", "godaddy", "google", "ibmcloud", "infoblox", "inmemory", "linode", "netcup", "ns1", "oci", "ovh", "pdns", "pihole", "plural", "rcodezero", "rdns", "rfc2136", "safedns", "scaleway", "skydns", "tencentcloud", "transip", "ultradns", "vinyldns", "vultr"}
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: "+strings.Join(providers, ", ")+")").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, providers...)
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains)
Expand Down Expand Up @@ -553,6 +561,12 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("plural-cluster", "When using the plural provider, specify the cluster name you're running with").Default(defaultConfig.PluralCluster).StringVar(&cfg.PluralCluster)
app.Flag("plural-provider", "When using the plural provider, specify the provider name you're running with").Default(defaultConfig.PluralProvider).StringVar(&cfg.PluralProvider)

// Flags related to the Netcup provider
app.Flag("netcup-customer-id", "When using the netcup provider, specify the customer id you're running with").Default(strconv.Itoa(defaultConfig.NetcupCustomerID)).IntVar(&cfg.NetcupCustomerID)
app.Flag("netcup-api-key", "When using the netcup provider, specify the api key you're running with").Default(defaultConfig.NetcupAPIKey).StringVar(&cfg.NetcupAPIKey)
app.Flag("netcup-api-password", "When using the netcup provider, specify the api password you're running with").Default(defaultConfig.NetcupAPIPassword).StringVar(&cfg.NetcupAPIPassword)
app.Flag("netcup-zones", "When using the netcup provider, specify a list of zones you're running with").Default("").StringsVar(&cfg.NetcupZones)

// Flags related to policies
app.Flag("policy", "Modify how DNS records are synchronized between sources and providers (default: sync, options: sync, upsert-only, create-only)").Default(defaultConfig.Policy).EnumVar(&cfg.Policy, "sync", "upsert-only", "create-only")

Expand Down
5 changes: 5 additions & 0 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ var (
IBMCloudConfigFile: "/etc/kubernetes/ibmcloud.json",
TencentCloudConfigFile: "/etc/kubernetes/tencent-cloud.json",
TencentCloudZoneType: "",
NetcupZones: []string{""},
}

overriddenConfig = &Config{
Expand Down Expand Up @@ -239,6 +240,7 @@ var (
IBMCloudConfigFile: "ibmcloud.json",
TencentCloudConfigFile: "tencent-cloud.json",
TencentCloudZoneType: "private",
NetcupZones: []string{"example.org", "company.com"},
}
)

Expand Down Expand Up @@ -379,6 +381,8 @@ func TestParseFlags(t *testing.T) {
"--ibmcloud-config-file=ibmcloud.json",
"--tencent-cloud-config-file=tencent-cloud.json",
"--tencent-cloud-zone-type=private",
"--netcup-zones=example.org",
"--netcup-zones=company.com",
},
envVars: map[string]string{},
expected: overriddenConfig,
Expand Down Expand Up @@ -494,6 +498,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_IBMCLOUD_CONFIG_FILE": "ibmcloud.json",
"EXTERNAL_DNS_TENCENT_CLOUD_CONFIG_FILE": "tencent-cloud.json",
"EXTERNAL_DNS_TENCENT_CLOUD_ZONE_TYPE": "private",
"EXTERNAL_DNS_NETCUP_ZONES": "example.org\ncompany.com",
},
expected: overriddenConfig,
},
Expand Down
Loading

0 comments on commit a7a5686

Please sign in to comment.