Merge branch 'master' into gloo-multiple-namespaces

This commit is contained in:
Megum1n 2023-06-24 02:11:35 +02:00
commit 41d3de5364
No known key found for this signature in database
GPG Key ID: 6A5CB0EBED11E256
190 changed files with 10809 additions and 3248 deletions

View File

@ -9,11 +9,23 @@ updates:
directory: "/" # Location of package manifests
schedule:
interval: "daily"
groups:
dev-dependencies:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
groups:
dev-dependencies:
patterns:
- "*"
- package-ecosystem: "docker" # Keep Docker dependencies up to date
directory: "/"
schedule:
interval: "daily"
groups:
dev-dependencies:
patterns:
- "*"

View File

@ -21,9 +21,9 @@ jobs:
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.19
go-version: '1.20'
id: go
- name: Check out code into the Go module directory
@ -38,7 +38,7 @@ jobs:
apt update
apt install -y make gcc libc-dev git
if: github.actor == 'nektos/act'
- name: Test
run: make test

View File

@ -28,9 +28,9 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
- name: Install go version
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: '^1.19'
go-version: '^1.20'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@ -18,15 +18,15 @@ jobs:
with:
fetch-depth: 0
- uses: actions/setup-python@v2
- uses: actions/setup-python@v4
with:
python-version: "3.10"
cache: "pip"
cache-dependency-path: "./docs/scripts/requirements.txt"
- uses: actions/setup-go@v3
- uses: actions/setup-go@v4
with:
go-version: ^1.19
go-version: '^1.20'
- run: |
pip install -r docs/scripts/requirements.txt

View File

@ -17,11 +17,11 @@ jobs:
actions: write
steps:
- name: Update PR
uses: actions/github-script@d556feaca394842dc55e4734bf3bb9f685482fa0 # v6.3.3
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1
continue-on-error: true
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
debug: ${{ secrets.ACTIONS_RUNNER_DEBUG }}
debug: ${{ secrets.ACTIONS_RUNNER_DEBUG == 'true' }}
script: |
const result = await github.rest.actions.listWorkflowRunsForRepo({
owner: context.repo.owner,

View File

@ -0,0 +1,23 @@
name: json-yaml-validate
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
pull-requests: write # enable write permissions for pull requests
jobs:
json-yaml-validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: json-yaml-validate
uses: GrantBirki/json-yaml-validate@v1.4.0
with:
comment: "true" # enable comment mode
yaml_exclude_regex: "(charts/external-dns/templates.*|mkdocs.yml)"

View File

@ -38,21 +38,21 @@ jobs:
python-version: "3.x"
- name: Set-up chart-testing
uses: helm/chart-testing-action@afea100a513515fbd68b0e72a7bb0ae34cb62aec
uses: helm/chart-testing-action@e8788873172cb653a90ca2e819d79d65a66d4e76
- name: Run chart-testing (list-changed)
id: list-changed
run: |
changed=$(ct list-changed)
if [[ -n "$changed" ]]; then
echo "::set-output name=changed::true"
echo "changed=true" >> $GITHUB_OUTPUT
fi
- name: Run chart-testing (lint)
run: ct lint --check-version-increment=false
- name: Set-up Kind cluster
uses: helm/kind-action@d8ccf8fb623ce1bb360ae2f45f323d9d5c5e9f00
uses: helm/kind-action@fa81e57adff234b2908110485695db0f181f3c67
with:
wait: 120s
if: steps.list-changed.outputs.changed == 'true'

View File

@ -21,9 +21,9 @@ jobs:
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v3
uses: actions/setup-go@v4
with:
go-version: 1.19
go-version: '1.20'
id: go
- name: Check out code into the Go module directory
@ -31,5 +31,5 @@ jobs:
- name: Lint
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.50.1
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.53.3
make lint

View File

@ -29,7 +29,7 @@ jobs:
run: |
set -euo pipefail
chart_version="$(grep -Po "(?<=^version: ).+" charts/external-dns/Chart.yaml)"
echo "::set-output name=version::${chart_version}"
echo "version=${chart_version}" >> $GITHUB_OUTPUT
- name: Get changelog entry
id: changelog_reader

View File

@ -0,0 +1,37 @@
name: Build all images
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
build:
permissions:
contents: read # to fetch code (actions/checkout)
checks: write # to create a new check based on the results (shogo82148/actions-goveralls)
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.x
uses: actions/setup-go@v4
with:
go-version: 1.19
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v3
- name: Install CI
run: |
go get -v -t -d ./...
- name: Test
run: make build.image/multiarch

View File

@ -19,7 +19,6 @@ linters:
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint
disable-all: true
enable:
- depguard
- dogsled
- gofmt
- goimports
@ -61,6 +60,14 @@ issues:
- unused
- varcheck
- whitespace
- path: source/ambassador_host.go
linters: [ typecheck ]
- path: source/contour_httpproxy.go
linters: [ typecheck ]
- path: source/f5_virtualserver.go
linters: [ typecheck ]
- path: source/kong_tcpingress.go
linters: [ typecheck ]
run:
skip-files:

View File

@ -14,7 +14,7 @@
# builder image
ARG ARCH
FROM golang:1.19 as builder
FROM golang:1.20 as builder
ARG ARCH
WORKDIR /sigs.k8s.io/external-dns
@ -24,12 +24,10 @@ COPY go.sum .
RUN go mod download
COPY . .
RUN make test build.$ARCH
# final image
FROM $ARCH/alpine:3.17
FROM alpine:3.18
RUN apk update && apk add "libcrypto3>=3.0.8-r0" "libssl3>=3.0.8-r0" && rm -rf /var/cache/apt/*
RUN apk update && apk add "libcrypto3>=3.0.8-r4" "libssl3>=3.0.8-r4" && rm -rf /var/cache/apt/*
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
COPY --from=builder /sigs.k8s.io/external-dns/build/external-dns /bin/external-dns

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
FROM golang:1.19 as builder
FROM golang:1.20 as builder
WORKDIR /sigs.k8s.io/external-dns

View File

@ -19,7 +19,7 @@
cover:
go get github.com/wadey/gocovmerge
$(eval PKGS := $(shell go list ./... | grep -v /vendor/))
$(eval PKGS_DELIM := $(shell echo $(PKGS) | sed -e 's/ /,/g'))
$(eval PKGS_DELIM := $(shell echo $(PKGS) | tr / -'))
go list -f '{{if or (len .TestGoFiles) (len .XTestGoFiles)}}go test -test.v -test.timeout=120s -covermode=count -coverprofile={{.Name}}_{{len .Imports}}_{{len .Deps}}.coverprofile -coverpkg $(PKGS_DELIM) {{.ImportPath}}{{end}}' $(PKGS) | xargs -0 sh -c
gocovmerge `ls *.coverprofile` > cover.out
rm *.coverprofile
@ -90,8 +90,11 @@ IMAGE ?= us.gcr.io/k8s-artifacts-prod/external-dns/$(BINARY)
VERSION ?= $(shell git describe --tags --always --dirty)
BUILD_FLAGS ?= -v
LDFLAGS ?= -X sigs.k8s.io/external-dns/pkg/apis/externaldns.Version=$(VERSION) -w -s
ARCHS = amd64 arm64v8 arm32v7
SHELL = /bin/bash
ARCHS = amd64 arm64 arm/v7
ARCH ?= amd64
DEFAULT_ARCH = amd64
SHELL = /bin/bash
OUTPUT_TYPE ?= docker
build: build/$(BINARY)
@ -99,37 +102,65 @@ build: build/$(BINARY)
build/$(BINARY): $(SOURCES)
CGO_ENABLED=0 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.push/multiarch:
build.push/multiarch: $(addprefix build.push-,$(ARCHS))
arch_specific_tags=()
for arch in $(ARCHS); do \
image="$(IMAGE):$(VERSION)-$${arch}" ;\
# pre-pull due to https://github.com/kubernetes-sigs/cluster-addons/pull/84/files ;\
docker pull $${arch}/alpine:3.17 ;\
docker pull golang:1.19 ;\
DOCKER_BUILDKIT=1 docker build --rm --tag $${image} --build-arg VERSION="$(VERSION)" --build-arg ARCH="$${arch}" . ;\
docker push $${image} ;\
arch_specific_tags+=( "--amend $${image}" ) ;\
image="$(IMAGE):$(VERSION)-$$(echo $$arch | tr / -)" ;\
arch_specific_tags+=( " $${image}" ) ;\
done ;\
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create "$(IMAGE):$(VERSION)" $${arch_specific_tags[@]} ;\
for arch in $(ARCHS); do \
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate --arch $${arch} "$(IMAGE):$(VERSION)" "$(IMAGE):$(VERSION)-$${arch}" ;\
done;\
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$(IMAGE):$(VERSION)" \
echo $${arch_specific_tags[@]} ;\
DOCKER_CLI_EXPERIMENTAL=enabled docker buildx imagetools create --tag "$(IMAGE):$(VERSION)" $${arch_specific_tags[@]} ;\
build.push: build.docker
docker push "$(IMAGE):$(VERSION)"
build.image/multiarch: $(addprefix build.image-,$(ARCHS))
build.arm64v8:
build.image:
$(MAKE) ARCH=$(ARCH) OUTPUT_TYPE=docker build.docker
build.image-amd64:
$(MAKE) ARCH=amd64 build.image
build.image-arm64:
$(MAKE) ARCH=arm64 build.image
build.image-arm/v7:
$(MAKE) ARCH=arm/v7 build.image
build.push:
$(MAKE) ARCH=$(ARCH) OUTPUT_TYPE=registry build.docker
build.push-amd64:
$(MAKE) ARCH=amd64 build.push
build.push-arm64:
$(MAKE) ARCH=arm64 build.push
build.push-arm/v7:
$(MAKE) ARCH=arm/v7 build.push
build.arm64:
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.amd64:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.arm32v7:
build.arm/v7:
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o build/$(BINARY) $(BUILD_FLAGS) -ldflags "$(LDFLAGS)" .
build.docker:
docker build --rm --tag "$(IMAGE):$(VERSION)" --build-arg VERSION="$(VERSION)" --build-arg ARCH="amd64" .
build.setup:
docker buildx inspect img-builder > /dev/null || docker buildx create --name img-builder --use
build.docker: build.setup build.$(ARCH)
docker build --rm --tag "$(IMAGE):$(VERSION)" --build-arg VERSION="$(VERSION)" --build-arg ARCH="$(ARCH)" .
image="$(IMAGE):$(VERSION)-$(subst /,-,$(ARCH))"; \
docker buildx build \
--pull \
--provenance=false \
--sbom=false \
--output=type=$(OUTPUT_TYPE) \
--platform linux/$(ARCH) \
--build-arg ARCH="$(ARCH)" \
--build-arg VERSION="$(VERSION)" \
--tag $${image} .
build.mini:
docker build --rm --tag "$(IMAGE):$(VERSION)-mini" --build-arg VERSION="$(VERSION)" -f Dockerfile.mini .
@ -140,8 +171,8 @@ clean:
# Builds and push container images to the staging bucket.
.PHONY: release.staging
release.staging:
release.staging: test
IMAGE=$(IMAGE_STAGING) $(MAKE) build.push/multiarch
release.prod:
release.prod: test
$(MAKE) build.push/multiarch

1
OWNERS
View File

@ -8,6 +8,7 @@ approvers:
- szuecs
reviewers:
- johngmyers
- njuettner
- raffo
- seanmalloy

View File

@ -5,7 +5,7 @@ hide:
---
<p align="center">
<img src="docs/img/external-dns.png" width="40%" align="center" alt="ExternalDNS">
<img src="img/external-dns.png" width="40%" align="center" alt="ExternalDNS">
</p>
# ExternalDNS
@ -63,11 +63,10 @@ ExternalDNS allows you to keep selected zones (via `--domain-filter`) synchroniz
* [Plural](https://www.plural.sh/)
* [Pi-hole](https://pi-hole.net/)
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.
ExternalDNS is, by default, aware of the records it is managing, therefore it can safely manage non-empty hosted zones. We strongly encourage you to set `--txt-owner-id` 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.
Note that all flags can be replaced with environment variables; for instance,
`--dry-run` could be replaced with `EXTERNAL_DNS_DRY_RUN=1`, or
`--registry txt` could be replaced with `EXTERNAL_DNS_REGISTRY=txt`.
`--dry-run` could be replaced with `EXTERNAL_DNS_DRY_RUN=1`.
## Status of providers
@ -176,6 +175,7 @@ The following tutorials are provided:
* [Nginx Ingress Controller](docs/tutorials/nginx-ingress.md)
* [NS1](docs/tutorials/ns1.md)
* [NS Record Creation with CRD Source](docs/tutorials/ns-record.md)
* [MX Record Creation with CRD Source](docs/tutorials/mx-record.md)
* [OpenStack Designate](docs/tutorials/designate.md)
* [Oracle Cloud Infrastructure (OCI) DNS](docs/tutorials/oracle.md)
* [PowerDNS](docs/tutorials/pdns.md)
@ -236,17 +236,17 @@ If the service is not of type Loadbalancer you need the --publish-internal-servi
Locally run a single sync loop of ExternalDNS.
```console
external-dns --registry txt --txt-owner-id my-cluster-id --provider google --google-project example-project --source service --once --dry-run
external-dns --txt-owner-id my-cluster-id --provider google --google-project example-project --source service --once --dry-run
```
This should output the DNS records it will modify to match the managed zone with the DNS records you desire. It also assumes you are running in the `default` namespace. See the [FAQ](docs/faq.md) for more information regarding namespaces.
Note: TXT records will have `my-cluster-id` value embedded. Those are used to ensure that ExternalDNS is aware of the records it manages.
Note: TXT records will have the `my-cluster-id` value embedded. Those are used to ensure that ExternalDNS is aware of the records it manages.
Once you're satisfied with the result, you can run ExternalDNS like you would run it in your cluster: as a control loop, and **not in dry-run** mode:
```console
external-dns --registry txt --txt-owner-id my-cluster-id --provider google --google-project example-project --source service
external-dns --txt-owner-id my-cluster-id --provider google --google-project example-project --source service
```
Check that ExternalDNS has created the desired DNS record for your Service and that it points to its load balancer's IP. Then try to resolve it:
@ -270,71 +270,6 @@ If using a txt registry and attempting to use a CNAME the `--txt-prefix` must be
If `externalIPs` list is defined for a `LoadBalancer` service, this list will be used instead of an assigned load balancer IP to create a DNS record. It's useful when you run bare metal Kubernetes clusters behind NAT or in a similar setup, where a load balancer IP differs from a public IP (e.g. with [MetalLB](https://metallb.universe.tf)).
# Roadmap
ExternalDNS was built with extensibility in mind. Adding and experimenting with new DNS providers and sources of desired DNS records should be as easy as possible. It should also be possible to modify how ExternalDNS behaves—e.g. whether it should add records but never delete them.
Here's a rough outline on what is to come (subject to change):
### v0.1
- [x] Support for Google CloudDNS
- [x] Support for Kubernetes Services
### v0.2
- [x] Support for AWS Route 53
- [x] Support for Kubernetes Ingresses
### v0.3
- [x] Support for AWS Route 53 via ALIAS
- [x] Support for multiple zones
- [x] Ownership System
### v0.4
- [x] Support for AzureDNS
- [x] Support for CloudFlare
- [x] Support for DigitalOcean
- [x] Multiple DNS names per Service
### v0.5
- [x] Support for creating DNS records to multiple targets (for Google and AWS)
- [x] Support for OpenStack Designate
- [x] Support for PowerDNS
- [x] Support for Linode
- [x] Support for RcodeZero
- [x] Support for NS1
- [x] Support for TransIP
- [x] Support for Azure Private DNS
### v0.6
- [ ] Ability to replace kOps' [DNS Controller](https://github.com/kubernetes/kops/tree/HEAD/dns-controller) (This could also directly become `v1.0`)
- [x] Support for OVH
### v1.0
- [ ] Ability to replace kOps' [DNS Controller](https://github.com/kubernetes/kops/tree/HEAD/dns-controller)
- [x] Add support for pod source
- [x] Add support for DNS Controller annotations for pod and service sources
- [ ] Add support for kOps gossip provider
- [x] Ability to replace Zalando's [Mate](https://github.com/linki/mate)
- [x] Ability to replace Molecule Software's [route53-kubernetes](https://github.com/wearemolecule/route53-kubernetes)
### Yet to be defined
* Support for CoreDNS
* Support for record weights
* Support for different behavioral policies
* Support for Services with `type=NodePort`
* Support for CRDs
* Support for more advanced DNS record configurations
Have a look at [the milestones](https://github.com/kubernetes-sigs/external-dns/milestones) to get an idea of where we currently stand.
## Contributing
Are you interested in contributing to external-dns? We, the maintainers and community, would love your

View File

@ -7,15 +7,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
---
<!-- ## [vX.Y.Z] - UNRELEASED
### Highlights
## [UNRELEASED]
### All Changes
- Added
- Updated
- Changed
- Fixed
- Deprecated
- Removed -->
- Disallowed privilege escalation in container security context and set the seccomp profile type to `RuntimeDefault`. ([#3689](https://github.com/kubernetes-sigs/external-dns/pull/3689)) [@nrvnrvn](https://github.com/nrvnrvn)
## [v1.13.0] - 2023-03-30
### All Changes
- Updated _ExternalDNS_ version to [v0.13.5](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.13.5). ([#3661](https://github.com/kubernetes-sigs/external-dns/pull/3661)) [@GMartinez-Sisti](https://github.com/GMartinez-Sisti)
- Adding missing gateway-httproute cluster role permission. ([#3541](https://github.com/kubernetes-sigs/external-dns/pull/3541)) [@nicon89](https://github.com/nicon89)
## [v1.12.2] - 2023-03-30
### All Changes
- Added support for ServiceMonitor relabelling. ([#3366](https://github.com/kubernetes-sigs/external-dns/pull/3366)) [@jkroepke](https://github.com/jkroepke)
- Updated chart icon path. ([#3492](https://github.com/kubernetes-sigs/external-dns/pull/3494)) [kundan2707](https://github.com/kundan2707)
- Added RBAC for Gateway-API resources to ClusterRole. ([#3499](https://github.com/kubernetes-sigs/external-dns/pull/3499)) [@michaelvl](https://github.com/MichaelVL)
- Added RBAC for F5 VirtualServer to ClusterRole. ([#3503](https://github.com/kubernetes-sigs/external-dns/pull/3503)) [@mikejoh](https://github.com/mikejoh)
- Added support for running ExternalDNS with namespaced scope. ([#3403](https://github.com/kubernetes-sigs/external-dns/pull/3403)) [@jkroepke](https://github.com/jkroepke)
- Updated _ExternalDNS_ version to [v0.13.4](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.13.4). ([#3516](https://github.com/kubernetes-sigs/external-dns/pull/3516)) [@stevehipwell](https://github.com/stevehipwell)
## [v1.12.1] - 2023-02-06

View File

@ -2,8 +2,8 @@ apiVersion: v2
name: external-dns
description: ExternalDNS synchronizes exposed Kubernetes Services and Ingresses with DNS providers.
type: application
version: 1.12.1
appVersion: 0.13.2
version: 1.13.0
appVersion: 0.13.5
keywords:
- kubernetes
- externaldns
@ -12,7 +12,7 @@ keywords:
- service
- ingress
home: https://github.com/kubernetes-sigs/external-dns/
icon: https://github.com/kubernetes-sigs/external-dns/raw/master/img/external-dns.png
icon: https://github.com/kubernetes-sigs/external-dns/raw/master/docs/img/external-dns.png
sources:
- https://github.com/kubernetes-sigs/external-dns/
maintainers:
@ -21,8 +21,6 @@ maintainers:
annotations:
artifacthub.io/changes: |
- kind: changed
description: "Updated ExternalDNS version to v0.13.2."
- kind: added
description: "Added secretConfiguration.subPath to mount specific files from secret as a sub-path."
description: "Updated _ExternalDNS_ version to [v0.13.5](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.13.5)."
- kind: changed
description: "Changed to use registry.k8s.io instead of k8s.gcr.io."
description: "Adding missing gateway-httproute cluster role permission."

View File

@ -69,6 +69,7 @@ The following table lists the configurable parameters of the _ExternalDNS_ chart
| `logFormat` | Formats of the logs, available values are: `text`, `json`. | `text` |
| `interval` | The interval for DNS updates. | `1m` |
| `triggerLoopOnEvent` | When enabled, triggers run loop on create/update/delete events in addition of regular interval. | `false` |
| `namespaced` | When enabled, external-dns runs on namespace scope. Additionally, Role and Rolebinding will be namespaced, too. | `false` |
| `sources` | K8s resources type to be observed for new DNS entries. | See _values.yaml_ |
| `policy` | How DNS records are synchronized between sources and providers, available values are: `sync`, `upsert-only`. | `upsert-only` |
| `registry` | Registry Type, available types are: `txt`, `noop`. | `txt` |
@ -82,3 +83,36 @@ The following table lists the configurable parameters of the _ExternalDNS_ chart
| `secretConfiguration.mountPath` | Mount path of secret configuration secret (this can be templated). | `""` |
| `secretConfiguration.data` | Secret configuration secret data. Could be used to store DNS provider credentials. | `{}` |
| `secretConfiguration.subPath` | Sub-path of secret configuration secret (this can be templated). | `""` |
## Namespaced scoped installation
external-dns supports running on a namespaced only scope, too.
If `namespaced=true` is defined, the helm chart will setup `Roles` and `RoleBindings` instead `ClusterRoles` and `ClusterRoleBindings`.
### Limited supported
Not all sources are supported in namespaced scope, since some sources depends on cluster-wide resources.
For example: Source `node` isn't supported, since `kind: Node` has scope `Cluster`.
Sources like `istio-virtualservice` only work, if all resources like `Gateway` and `VirtualService` are present in the same
namespaces as `external-dns`.
The annotation `external-dns.alpha.kubernetes.io/endpoints-type: NodeExternalIP` is not supported.
If `namespaced` is set to `true`, please ensure that `sources` my only contains supported sources (Default: `service,ingress`.
### Support matrix
| Source | Supported | Infos |
|------------------------|-----------|------------------------|
| `ingress` | ✅ | |
| `istio-gateway` | ✅ | |
| `istio-virtualservice` | ✅ | |
| `contour-ingressroute` | ✅ | |
| `crd` | ✅ | |
| `kong-tcpingress` | ✅ | |
| `openshift-route` | ✅ | |
| `skipper-routegroup` | ✅ | |
| `gloo-proxy` | ✅ | |
| `contour-httpproxy` | ✅ | |
| `service` | ⚠️️ | NodePort not supported |
| `node` | ❌ | |
| `pod` | ❌ | |

View File

@ -0,0 +1 @@
provider: inmemory

View File

@ -1,12 +1,12 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
kind: {{ .Values.namespaced | ternary "Role" "ClusterRole" }}
metadata:
name: {{ template "external-dns.fullname" . }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
rules:
{{- if or (has "node" .Values.sources) (has "pod" .Values.sources) (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
{{- if and (not .Values.namespaced) (or (has "node" .Values.sources) (has "pod" .Values.sources) (has "service" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "gloo-proxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources)) }}
- apiGroups: [""]
resources: ["nodes"]
verbs: ["list","watch"]
@ -60,6 +60,41 @@ rules:
resources: ["dnsendpoints/status"]
verbs: ["*"]
{{- end }}
{{- if or (has "gateway-httproute" .Values.sources) (has "gateway-grpcroute" .Values.sources) (has "gateway-tlsroute" .Values.sources) (has "gateway-tcproute" .Values.sources) (has "gateway-udproute" .Values.sources) }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["gateways"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-httproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["httproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-httproute" .Values.sources }}
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-grpcroute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["grpcroutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-tlsroute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["tlsroutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-tcproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["tcproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gateway-udproute" .Values.sources }}
- apiGroups: ["gateway.networking.k8s.io"]
resources: ["udproutes"]
verbs: ["get","watch","list"]
{{- end }}
{{- if has "gloo-proxy" .Values.sources }}
- apiGroups: ["gloo.solo.io","gateway.solo.io"]
resources: ["proxies","virtualservices"]
@ -83,6 +118,11 @@ rules:
resources: ["routegroups/status"]
verbs: ["patch","update"]
{{- end }}
{{- if has "f5-virtualserver" .Values.sources }}
- apiGroups: ["cis.f5.com"]
resources: ["virtualservers"]
verbs: ["get","watch","list"]
{{- end }}
{{- with .Values.rbac.additionalPermissions }}
{{- toYaml . | nindent 2 }}
{{- end }}

View File

@ -1,13 +1,13 @@
{{- if .Values.rbac.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
kind: {{ .Values.namespaced | ternary "RoleBinding" "ClusterRoleBinding" }}
metadata:
name: {{ printf "%s-viewer" (include "external-dns.fullname" .) }}
labels:
{{- include "external-dns.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
kind: {{ .Values.namespaced | ternary "Role" "ClusterRole" }}
name: {{ template "external-dns.fullname" . }}
subjects:
- kind: ServiceAccount

View File

@ -89,6 +89,9 @@ spec:
- --txt-suffix={{ .Values.txtSuffix }}
{{- end }}
{{- end }}
{{- if .Values.namespaced }}
- --namespace={{ .Release.Namespace }}
{{- end }}
{{- range .Values.domainFilters }}
- --domain-filter={{ . }}
{{- end }}

View File

@ -43,8 +43,11 @@ shareProcessNamespace: false
podSecurityContext:
fsGroup: 65534
seccompProfile:
type: RuntimeDefault
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 65534
readOnlyRootFilesystem: true
@ -151,6 +154,8 @@ logFormat: text
interval: 1m
triggerLoopOnEvent: false
namespaced: false
sources:
- service
- ingress

View File

@ -2,15 +2,22 @@
timeout: 5000s
options:
substitution_option: ALLOW_LOOSE
machineType: 'N1_HIGHCPU_8'
steps:
- name: "gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20211118-2f2d816b90"
entrypoint: make
- name: "gcr.io/k8s-staging-test-infra/gcb-docker-gcloud:v20230206-8160eea68e"
entrypoint: bash
env:
- DOCKER_CLI_EXPERIMENTAL=enabled
- VERSION=$_GIT_TAG
- PULL_BASE_REF=$_PULL_BASE_REF
- HOME=/root
args:
- release.staging
- -c
- |
gcloud auth configure-docker
/buildx-entrypoint version
apk add musl-dev gcc
make release.staging
substitutions:
# _GIT_TAG will be filled with a git-based tag for the image, of the form vYYYYMMDD-hash, and
# can be used as a substitution

View File

@ -102,6 +102,14 @@ var (
Help: "Number of Registry A records.",
},
)
registryAAAARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "registry",
Name: "aaaa_records",
Help: "Number of Registry AAAA records.",
},
)
sourceARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
@ -110,6 +118,14 @@ var (
Help: "Number of Source A records.",
},
)
sourceAAAARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "source",
Name: "aaaa_records",
Help: "Number of Source AAAA records.",
},
)
verifiedARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
@ -118,6 +134,14 @@ var (
Help: "Number of DNS A-records that exists both in source and registry.",
},
)
verifiedAAAARecords = prometheus.NewGauge(
prometheus.GaugeOpts{
Namespace: "external_dns",
Subsystem: "controller",
Name: "verified_aaaa_records",
Help: "Number of DNS AAAA-records that exists both in source and registry.",
},
)
)
func init() {
@ -130,8 +154,11 @@ func init() {
prometheus.MustRegister(deprecatedSourceErrors)
prometheus.MustRegister(controllerNoChangesTotal)
prometheus.MustRegister(registryARecords)
prometheus.MustRegister(registryAAAARecords)
prometheus.MustRegister(sourceARecords)
prometheus.MustRegister(sourceAAAARecords)
prometheus.MustRegister(verifiedARecords)
prometheus.MustRegister(verifiedAAAARecords)
}
// Controller is responsible for orchestrating the different components.
@ -168,11 +195,10 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return err
}
missingRecords := c.Registry.MissingRecords()
registryEndpointsTotal.Set(float64(len(records)))
regARecords := filterARecords(records)
registryARecords.Set(float64(len(regARecords)))
regARecords, regAAAARecords := countAddressRecords(records)
registryARecords.Set(float64(regARecords))
registryAAAARecords.Set(float64(regAAAARecords))
ctx = context.WithValue(ctx, provider.RecordsContextKey, records)
endpoints, err := c.Source.Endpoints(ctx)
@ -182,35 +208,14 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return err
}
sourceEndpointsTotal.Set(float64(len(endpoints)))
srcARecords := filterARecords(endpoints)
sourceARecords.Set(float64(len(srcARecords)))
vRecords := fetchMatchingARecords(endpoints, records)
verifiedARecords.Set(float64(len(vRecords)))
srcARecords, srcAAAARecords := countAddressRecords(endpoints)
sourceARecords.Set(float64(srcARecords))
sourceAAAARecords.Set(float64(srcAAAARecords))
vARecords, vAAAARecords := countMatchingAddressRecords(endpoints, records)
verifiedARecords.Set(float64(vARecords))
verifiedAAAARecords.Set(float64(vAAAARecords))
endpoints = c.Registry.AdjustEndpoints(endpoints)
if len(missingRecords) > 0 {
// Add missing records before the actual plan is applied.
// This prevents the problems when the missing TXT record needs to be
// created and deleted/upserted in the same batch.
missingRecordsPlan := &plan.Plan{
Policies: []plan.Policy{c.Policy},
Missing: missingRecords,
DomainFilter: endpoint.MatchAllDomainFilters{c.DomainFilter, c.Registry.GetDomainFilter()},
PropertyComparator: c.Registry.PropertyValuesEqual,
ManagedRecords: c.ManagedRecordTypes,
}
missingRecordsPlan = missingRecordsPlan.Calculate()
if missingRecordsPlan.Changes.HasChanges() {
err = c.Registry.ApplyChanges(ctx, missingRecordsPlan.Changes)
if err != nil {
registryErrorsTotal.Inc()
deprecatedRegistryErrors.Inc()
return err
}
log.Info("All missing records are created")
}
}
plan := &plan.Plan{
Policies: []plan.Policy{c.Policy},
Current: records,
@ -238,30 +243,44 @@ func (c *Controller) RunOnce(ctx context.Context) error {
return nil
}
// Checks and returns the intersection of A records in endpoint and registry.
func fetchMatchingARecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) []string {
aRecords := filterARecords(endpoints)
recordsMap := make(map[string]struct{})
// Counts the intersections of A and AAAA records in endpoint and registry.
func countMatchingAddressRecords(endpoints []*endpoint.Endpoint, registryRecords []*endpoint.Endpoint) (int, int) {
recordsMap := make(map[string]map[string]struct{})
for _, regRecord := range registryRecords {
recordsMap[regRecord.DNSName] = struct{}{}
if _, found := recordsMap[regRecord.DNSName]; !found {
recordsMap[regRecord.DNSName] = make(map[string]struct{})
}
recordsMap[regRecord.DNSName][regRecord.RecordType] = struct{}{}
}
var cm []string
for _, sourceRecord := range aRecords {
if _, found := recordsMap[sourceRecord]; found {
cm = append(cm, sourceRecord)
aCount := 0
aaaaCount := 0
for _, sourceRecord := range endpoints {
if _, found := recordsMap[sourceRecord.DNSName]; found {
if _, found := recordsMap[sourceRecord.DNSName][sourceRecord.RecordType]; found {
switch sourceRecord.RecordType {
case endpoint.RecordTypeA:
aCount++
case endpoint.RecordTypeAAAA:
aaaaCount++
}
}
}
}
return cm
return aCount, aaaaCount
}
func filterARecords(endpoints []*endpoint.Endpoint) []string {
var aRecords []string
func countAddressRecords(endpoints []*endpoint.Endpoint) (int, int) {
aCount := 0
aaaaCount := 0
for _, endPoint := range endpoints {
if endPoint.RecordType == endpoint.RecordTypeA {
aRecords = append(aRecords, endPoint.DNSName)
switch endPoint.RecordType {
case endpoint.RecordTypeA:
aCount++
case endpoint.RecordTypeAAAA:
aaaaCount++
}
}
return aRecords
return aCount, aaaaCount
}
// ScheduleRunOnce makes sure execution happens at most once per interval.
@ -292,7 +311,7 @@ func (c *Controller) Run(ctx context.Context) {
for {
if c.ShouldRunOnce(time.Now()) {
if err := c.RunOnce(ctx); err != nil {
log.Error(err)
log.Fatal(err)
}
}
select {

View File

@ -21,6 +21,7 @@ import (
"errors"
"math"
"reflect"
"sort"
"testing"
"time"
@ -83,32 +84,20 @@ func (p *errorMockProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
// ApplyChanges validates that the passed in changes satisfy the assumptions.
func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error {
if len(changes.Create) != len(p.ExpectChanges.Create) {
return errors.New("number of created records is wrong")
if err := verifyEndpoints(changes.Create, p.ExpectChanges.Create); err != nil {
return err
}
for i := range changes.Create {
if changes.Create[i].DNSName != p.ExpectChanges.Create[i].DNSName || !changes.Create[i].Targets.Same(p.ExpectChanges.Create[i].Targets) {
return errors.New("created record is wrong")
}
if err := verifyEndpoints(changes.UpdateNew, p.ExpectChanges.UpdateNew); err != nil {
return err
}
for i := range changes.UpdateNew {
if changes.UpdateNew[i].DNSName != p.ExpectChanges.UpdateNew[i].DNSName || !changes.UpdateNew[i].Targets.Same(p.ExpectChanges.UpdateNew[i].Targets) {
return errors.New("delete record is wrong")
}
if err := verifyEndpoints(changes.UpdateOld, p.ExpectChanges.UpdateOld); err != nil {
return err
}
for i := range changes.UpdateOld {
if changes.UpdateOld[i].DNSName != p.ExpectChanges.UpdateOld[i].DNSName || !changes.UpdateOld[i].Targets.Same(p.ExpectChanges.UpdateOld[i].Targets) {
return errors.New("delete record is wrong")
}
}
for i := range changes.Delete {
if changes.Delete[i].DNSName != p.ExpectChanges.Delete[i].DNSName || !changes.Delete[i].Targets.Same(p.ExpectChanges.Delete[i].Targets) {
return errors.New("delete record is wrong")
}
if err := verifyEndpoints(changes.Delete, p.ExpectChanges.Delete); err != nil {
return err
}
if !reflect.DeepEqual(ctx.Value(provider.RecordsContextKey), p.RecordsStore) {
@ -117,6 +106,21 @@ func (p *mockProvider) ApplyChanges(ctx context.Context, changes *plan.Changes)
return nil
}
func verifyEndpoints(actual, expected []*endpoint.Endpoint) error {
if len(actual) != len(expected) {
return errors.New("number of records is wrong")
}
sort.Slice(actual, func(i, j int) bool {
return actual[i].DNSName < actual[j].DNSName
})
for i := range actual {
if actual[i].DNSName != expected[i].DNSName || !actual[i].Targets.Same(expected[i].Targets) {
return errors.New("record is wrong")
}
}
return nil
}
// newMockProvider creates a new mockProvider returning the given endpoints and validating the desired changes.
func newMockProvider(endpoints []*endpoint.Endpoint, changes *plan.Changes) provider.Provider {
dnsProvider := &mockProvider{
@ -132,7 +136,7 @@ func TestRunOnce(t *testing.T) {
// Fake some desired endpoints coming from our source.
source := new(testutils.MockSource)
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
source.On("Endpoints").Return([]*endpoint.Endpoint{
{
DNSName: "create-record",
@ -144,6 +148,16 @@ func TestRunOnce(t *testing.T) {
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.4.4"},
},
{
DNSName: "create-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "update-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
}, nil)
// Fake some existing records in our DNS provider and validate some desired changes.
@ -159,18 +173,32 @@ func TestRunOnce(t *testing.T) {
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"4.3.2.1"},
},
{
DNSName: "update-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
{
DNSName: "delete-aaaa-record",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::4"},
},
},
&plan.Changes{
Create: []*endpoint.Endpoint{
{DNSName: "create-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
{DNSName: "create-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"1.2.3.4"}},
},
UpdateNew: []*endpoint.Endpoint{
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::2"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.4.4"}},
},
UpdateOld: []*endpoint.Endpoint{
{DNSName: "update-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::3"}},
{DNSName: "update-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"8.8.8.8"}},
},
Delete: []*endpoint.Endpoint{
{DNSName: "delete-aaaa-record", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::4"}},
{DNSName: "delete-record", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"4.3.2.1"}},
},
},
@ -193,6 +221,7 @@ func TestRunOnce(t *testing.T) {
source.AssertExpectations(t)
// check the verified records
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedARecords))
assert.Equal(t, math.Float64bits(1), valueFromMetric(verifiedAAAARecords))
}
func valueFromMetric(metric prometheus.Gauge) uint64 {
@ -253,7 +282,7 @@ func TestShouldRunOnce(t *testing.T) {
func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter endpoint.DomainFilterInterface, providerEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) {
t.Helper()
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME}
source := new(testutils.MockSource)
source.On("Endpoints").Return(configuredEndpoints, nil)
@ -282,51 +311,6 @@ func testControllerFiltersDomains(t *testing.T, configuredEndpoints []*endpoint.
}
}
type noopRegistryWithMissing struct {
*registry.NoopRegistry
missingRecords []*endpoint.Endpoint
}
func (r *noopRegistryWithMissing) MissingRecords() []*endpoint.Endpoint {
return r.missingRecords
}
func testControllerFiltersDomainsWithMissing(t *testing.T, configuredEndpoints []*endpoint.Endpoint, domainFilter endpoint.DomainFilterInterface, providerEndpoints, missingEndpoints []*endpoint.Endpoint, expectedChanges []*plan.Changes) {
t.Helper()
cfg := externaldns.NewConfig()
cfg.ManagedDNSRecordTypes = []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME}
source := new(testutils.MockSource)
source.On("Endpoints").Return(configuredEndpoints, nil)
// Fake some existing records in our DNS provider and validate some desired changes.
provider := &filteredMockProvider{
RecordsStore: providerEndpoints,
}
noop, err := registry.NewNoopRegistry(provider)
require.NoError(t, err)
r := &noopRegistryWithMissing{
NoopRegistry: noop,
missingRecords: missingEndpoints,
}
ctrl := &Controller{
Source: source,
Registry: r,
Policy: &plan.SyncPolicy{},
DomainFilter: domainFilter,
ManagedRecordTypes: cfg.ManagedDNSRecordTypes,
}
assert.NoError(t, ctrl.RunOnce(context.Background()))
assert.Equal(t, 1, provider.RecordsCallCount)
require.Len(t, provider.ApplyChangesCalls, len(expectedChanges))
for i, change := range expectedChanges {
assert.Equal(t, *change, *provider.ApplyChangesCalls[i])
}
}
func TestControllerSkipsEmptyChanges(t *testing.T) {
testControllerFiltersDomains(
t,
@ -526,6 +510,85 @@ func TestVerifyARecords(t *testing.T) {
}},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedARecords))
assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedAAAARecords))
}
func TestVerifyAAAARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "create-record.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
},
[]*plan.Changes{},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords))
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "some-record.1.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "some-record.2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "some-record.3.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::3"},
},
},
}},
)
assert.Equal(t, math.Float64bits(0), valueFromMetric(verifiedARecords))
assert.Equal(t, math.Float64bits(2), valueFromMetric(verifiedAAAARecords))
}
func TestARecords(t *testing.T) {
@ -575,56 +638,49 @@ func TestARecords(t *testing.T) {
assert.Equal(t, math.Float64bits(1), valueFromMetric(registryARecords))
}
// TestMissingRecordsApply validates that the missing records result in the dedicated plan apply.
func TestMissingRecordsApply(t *testing.T) {
testControllerFiltersDomainsWithMissing(
func TestAAAARecords(t *testing.T) {
testControllerFiltersDomains(
t,
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
endpoint.NewDomainFilter([]string{"used.tld"}),
[]*endpoint.Endpoint{
{
DNSName: "record1.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"1.2.3.4"},
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::1"},
},
{
DNSName: "_mysql-svc._tcp.mysql.used.tld",
RecordType: endpoint.RecordTypeSRV,
Targets: endpoint.Targets{"0 50 30007 mysql.used.tld"},
},
},
[]*endpoint.Endpoint{
{
DNSName: "a-record1.used.tld",
RecordType: endpoint.RecordTypeTXT,
Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=owner\""},
},
},
[]*plan.Changes{
// Missing record had its own plan applied.
{
Create: []*endpoint.Endpoint{
{
DNSName: "a-record1.used.tld",
RecordType: endpoint.RecordTypeTXT,
Targets: endpoint.Targets{"\"heritage=external-dns,external-dns/owner=owner\""},
},
[]*plan.Changes{{
Create: []*endpoint.Endpoint{
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeAAAA,
Targets: endpoint.Targets{"2001:DB8::2"},
},
},
{
Create: []*endpoint.Endpoint{
{
DNSName: "record2.used.tld",
RecordType: endpoint.RecordTypeA,
Targets: endpoint.Targets{"8.8.8.8"},
},
},
},
})
}},
)
assert.Equal(t, math.Float64bits(2), valueFromMetric(sourceAAAARecords))
assert.Equal(t, math.Float64bits(1), valueFromMetric(registryAAAARecords))
}

View File

@ -15,10 +15,11 @@ Here is typical example of [CRD API type](https://github.com/kubernetes-sigs/ext
type TTL int64
type Targets []string
type ProviderSpecificProperty struct {
Name string
Value string
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
}
type ProviderSpecific []ProviderSpecificProperty
type Labels map[string]string
type Endpoint struct {
// The hostname of the DNS record

View File

@ -0,0 +1,18 @@
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: examplednsrecord
spec:
endpoints:
- dnsName: subdomain.foo.bar.com
providerSpecific:
- name: "aws/failover"
value: "PRIMARY"
- name: "aws/health-check-id"
value: "asdf1234-as12-as12-as12-asdf12345678"
- name: "aws/evaluate-target-health"
value: "true"
recordType: CNAME
setIdentifier: some-unique-id
targets:
- other-subdomain.foo.bar.com

View File

@ -9,3 +9,7 @@ spec:
recordType: A
targets:
- 192.168.99.216
# Provider specific configurations are set like an annotation would on other sources
providerSpecific:
- name: external-dns.alpha.kubernetes.io/cloudflare-proxied
value: "true"

View File

@ -1,7 +1,7 @@
# Quick Start
- [Git](https://git-scm.com/downloads)
- [Go 1.19+](https://golang.org/dl/)
- [Go 1.20+](https://golang.org/dl/)
- [Go modules](https://github.com/golang/go/wiki/Modules)
- [golangci-lint](https://github.com/golangci/golangci-lint)
- [Docker](https://docs.docker.com/install/)

View File

@ -178,16 +178,18 @@ You can use the host label in the metric to figure out if the request was agains
Here is the full list of available metrics provided by ExternalDNS:
| Name | Description | Type |
| --------------------------------------------------- | ------------------------------------------------------- | ------- |
| external_dns_controller_last_sync_timestamp_seconds | Timestamp of last successful sync with the DNS provider | Gauge |
| external_dns_registry_endpoints_total | Number of Endpoints in all sources | Gauge |
| external_dns_registry_errors_total | Number of Registry errors | Counter |
| external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge |
| external_dns_source_errors_total | Number of Source errors | Counter |
| external_dns_controller_verified_records | Number of DNS A-records that exists both in | Gauge |
| | source & registry | |
| Name | Description | Type |
| --------------------------------------------------- | ------------------------------------------------------------------ | ------- |
| external_dns_controller_last_sync_timestamp_seconds | Timestamp of last successful sync with the DNS provider | Gauge |
| external_dns_registry_endpoints_total | Number of Endpoints in all sources | Gauge |
| external_dns_registry_errors_total | Number of Registry errors | Counter |
| external_dns_source_endpoints_total | Number of Endpoints in the registry | Gauge |
| external_dns_source_errors_total | Number of Source errors | Counter |
| external_dns_controller_verified_aaaa_records | Number of DNS AAAA-records that exists both in source and registry | Gauge |
| external_dns_controller_verified_a_records | Number of DNS A-records that exists both in source and registry | Gauge |
| external_dns_registry_aaaa_records | Number of AAAA records in registry | Gauge |
| external_dns_registry_a_records | Number of A records in registry | Gauge |
| external_dns_source_aaaa_records | Number of AAAA records in source | Gauge |
| external_dns_source_a_records | Number of A records in source | Gauge |
### How can I run ExternalDNS under a specific GCP Service Account, e.g. to access DNS records in other projects?
@ -205,7 +207,7 @@ $ docker run \
-e EXTERNAL_DNS_SOURCE=$'service\ningress' \
-e EXTERNAL_DNS_PROVIDER=google \
-e EXTERNAL_DNS_DOMAIN_FILTER=$'foo.com\nbar.com' \
registry.k8s.io/external-dns/external-dns:v0.13.2
registry.k8s.io/external-dns/external-dns:v0.13.5
time="2017-08-08T14:10:26Z" level=info msg="config: &{APIServerURL: KubeConfig: Sources:[service ingress] Namespace: ...
```
@ -255,24 +257,33 @@ spec:
### Running an internal and external dns service
Sometimes you need to run an internal and an external dns service.
The internal one should provision hostnames used on the internal network (perhaps inside a VPC), and the external
one to expose DNS to the internet.
The internal one should provision hostnames used on the internal network (perhaps inside a VPC), and the external one to expose DNS to the internet.
To do this with ExternalDNS you can use the `--annotation-filter` to specifically tie an instance of ExternalDNS to
an instance of an ingress controller. Let's assume you have two ingress controllers `nginx-internal` and `nginx-external`
then you can start two ExternalDNS providers one with `--annotation-filter=kubernetes.io/ingress.class in (nginx-internal)`
and one with `--annotation-filter=kubernetes.io/ingress.class in (nginx-external)`.
To do this with ExternalDNS you can use the `--ingress-class` flag to specifically tie an instance of ExternalDNS to an instance of a ingress controller.
Let's assume you have two ingress controllers, `internal` and `external`.
You can then start two ExternalDNS providers, one with `--ingress-class=internal` and one with `--ingress-class=external`.
If you need to search for multiple values of said annotation, you can provide a comma separated list, like so:
`--annotation-filter=kubernetes.io/ingress.class in (nginx-internal, alb-ingress-internal)`.
If you need to search for multiple ingress classes, you can specify the flag multiple times, like so:
`--ingress-class=internal --ingress-class=external`.
Beware when using multiple sources, e.g. `--source=service --source=ingress`, `--annotation-filter` will filter every given source objects.
If you need to filter only one specific source you have to run a separated external dns service containing only the wanted `--source` and `--annotation-filter`.
The `--ingress-class` flag will check both the `spec.ingressClassName` field and the deprecated `kubernetes.io/ingress.class` annotation.
The `spec.ingressClassName` tasks precedence over the annotation if both are supplied.
**Note:** Filtering based on annotation means that the external-dns controller will receive all resources of that kind and then filter on the client-side.
In larger clusters with many resources which change frequently this can cause performance issues. If only some resources need to be managed by an instance
of external-dns then label filtering can be used instead of annotation filtering. This means that only those resources which match the selector specified
in `--label-filter` will be passed to the controller.
**Backward compatibility**
The previous `--annotation-filter` flag can still be used to restrict which objects ExternalDNS considers; for example, `--annotation-filter=kubernetes.io/ingress.class in (public,dmz)`.
However, beware when using annotation filters with multiple sources, e.g. `--source=service --source=ingress`, since `--annotation-filter` will filter every given source object.
If you need to use annotation filters against a specific source you have to run a separated external dns service containing only the wanted `--source` and `--annotation-filter`.
Note: the `--ingress-class` flag cannot be used at the same time as the `--annotation-filter=kubernetes.io/ingress.class in (...)` flag; if you do this an error will be raised.
**Performance considerations**
Filtering based on ingress class name or annotations means that the external-dns controller will receive all resources of that kind and then filter on the client-side.
In larger clusters with many resources which change frequently this can cause performance issues.
If only some resources need to be managed by an instance of external-dns then label filtering can be used instead of ingress class filtering (or legacy annotation filtering).
This means that only those resources which match the selector specified in `--label-filter` will be passed to the controller.
### How do I specify that I want the DNS record to point to either the Node's public or private IP when it has both?

View File

@ -1,124 +0,0 @@
# Registry
#### [Old name: storage]
Initial discussion - https://github.com/kubernetes-sigs/external-dns/issues/44
## Purpose
One should not be afraid to use external-dns, because it can delete or overwrite the records preexisting in the DNS provider.
**Why we need it?**
DNS provider (AWS Route53, Google DNS, etc.) stores dns records which are created via various means. Integration of External-DNS should be safe and should not delete or overwrite the records which it is not responsible for. Moreover, it should certainly be possible for multiple kubernetes clusters to share the same hosted zone within the dns provider, additionally multiple external-dns instances inside the same cluster should be able to co-exist without messing with the same set of records.
Registry provides a mechanism to ensure the safe management of DNS records
This proposal introduces multiple possible implementation with the details depending on the setup.
## Requirements and assumptions
1. Pre-existing records should not be modified by external-dns
2. External-dns instance only creates/modifies/deletes records which are created by this instance
3. It should be possible to transfer the ownership to another external-dns instance
4. ~~Any integrated DNS provider should provide at least a single way to implement the registry~~ Noop registry can be used to disable ownership
5. Lifetime of the records should not be limited to lifetime of external-dns
6. External-dns should have its identifier for marking the managed records - **`owner-id`**
## Types of registry
The following presents two ways to implement the registry, and we are planning to implement both for compatibility purposes.
### TXT records
This implementation idea is borrowed from [Mate](https://github.com/linki/mate/)
Each record created by external-dns is accompanied by the TXT record, which internally stores the external-dns identifier. For example, if external dns with `owner-id="external-dns-1"` record to be created with dns name `foo.zone.org`, external-dns will create a TXT record with the same dns name `<record_type>-foo.zone.org` and injected value of `"external-dns-1"`. The transfer of ownership can be done by modifying the value of the TXT record. If no TXT record exists for the record or the value does not match its own `owner-id`, then external-dns will simply ignore it.
#### Goods
1. Easy to guarantee cross-cluster ownership safety
2. Data lifetime is not limited to cluster or external-dns lifetime
3. Supported by major DNS providers
4. TXT record are created alongside other records in a batch request. Hence **eliminating possibility of having inconsistent ownership information and dns provider state**
#### Bads
1. TXT record cannot co-exist with CNAME records (this can be solved by creating a TXT record with another domain name, e.g. `foo.org->foo.txt.org`)
2. Introduces complexity to the logic
3. Difficult to do the transfer of ownership
4. Too easy to mess up with manual modifications
### ConfigMap
**This implementation cannot be considered 100% error free**, hence use with caution [see **Possible failure scenario** below]
Store the state in the configmap. ConfigMap is created and managed by each external-dns individually, i.e. external-dns with **`owner-id=external-dns-1`** will create and operate on `extern-dns-1-registry` ConfigMap. ConfigMap will store **all** the records present in the DNS provider as serialized JSON. For example:
```
kind: ConfigMap
apiVersion: v1
metadata:
creationTimestamp: 2016-03-09T19:14:38Z
name: external-dns-1-storage
namespace: same-as-external-dns-1
data:
records: "[{
\"dnsname\": \"foo.org\",
\"owner\": \"external-dns-1\",
\"target\": \"loadbalancer1.com\",
\"type\": \"A\"
}, {
\"dnsname\": \"bar.org\",
\"owner\": \"external-dns-2\",
\"target\": \"loadbalancer2.com\",
\"type\": \"A\"
}, {
\"dnsname\": \"unmanaged.org\",
\"owner\": \"\",
\"target\": \"loadbalancer2.com\",
\"type\": \"CNAME\"
}]"
```
ConfigMap will be periodically resynced with the dns provider by fetching the dns records and comparing it with the data currently stored and hence rebuilding the ownership information.
#### Goods
1. Not difficult to implement and easy to do the ownership transfer
2. ConfigMap is a first class citizen in kubernetes world
3. Does not create dependency/restriction on DNS provider
4. Cannot be easily messed with by other parties
#### Bads
1. ConfigMap might be out of sync with dns provider state
2. LifeTime is obviously limited to the cluster lifetime
3. Not supported in older kubernetes clusters
4. Bloated ConfigMap - cannot be paginated and will grow very big on DNS provider with thousands of records
#### Failure scenario
It is possible that the configmap will go out of sync with the dns provider state. In the implementation flow external-dns will first modify records on dns provider side to subsequently update configmap. And if ExternalDNS will crash in-between two operation created records will be left unmanaged and not viable for update/deletion by External DNS.
## Component integration
Components:
* Source - all endpoints ( collection of ingress, service[type=LoadBalancer] etc.)
* [Plan](https://github.com/kubernetes-sigs/external-dns/issues/13) - object responsible for the create of change lists in external-dns
* Provider - interface to access the DNS provider API
Registry will serve as wrapper around `Provider` providing additional information regarding endpoint ownership. Ownership will further taken into account by `Plan` to filter out records to include only records managed by current ExternalDNS instance (having same `owner-id` value)
A single loop iteration of external-dns operation:
1. Get all endpoints ( collection ingress, service[type=LoadBalancer] etc.) into collection of `endpoints`
2. Get registry `Records()` (makes the call to DNSProvider and also build ownership information)
3. Pass `Records` (including ownership information) and list of endpoints to `Plan` to do the calculation
4. Call registry `ApplyChanges()` method (which subsequently calls DNS Provider Apply method to update records)
5. If ConfigMap implementation of Registry is used, then ConfigMap needs to be updated separately
~~In case of configmap, Registry gets updated all the time via `Poll`. `Plan` does not call DNS provider directly. Good value of the `Poll` is to have simple rate limiting mechanism on DNS provider.~~
#### Notes:
1. DNS Provider should use batch operations
2. DNS Provider should be called with CREATE operation (not UPSERT!) when the record does not yet exist!

View File

@ -1,15 +0,0 @@
### TXT Registry migration to a new format ###
In order to support more record types and be able to track ownership without TXT record name clash, a new TXT record is introduced.
It contains record type it manages, e.g.:
* A record foo.example.com will be tracked with classic foo.example.com TXT record
* At the same time a new TXT record will be created a-foo.example.com
Prefix and suffix are extended with %{record_type} template where the user can control how prefixed/suffixed records should look like.
In order to maintain compatibility, both records will be maintained for some time, in order to have downgrade possibility.
The controller will try to create the "new format" TXT records if they are not present to ease the migration from the versions < 0.12.0.
Later on, the old format will be dropped and only the new format will be kept (<record_type>-<endpoint_name>).
Cleanup will be done by controller itself.

41
docs/registry/dynamodb.md Normal file
View File

@ -0,0 +1,41 @@
# The DynamoDB registry
The DynamoDB registry stores DNS record metadata in an AWS DynamoDB table.
## The DynamoDB Table
By default, the DynamoDB registry stores data in the table named `external-dns`.
A different table may be specified using the `--dynamodb-table` flag.
A different region may be specified using the `--dynamodb-region` flag.
The table must have a partition (hash) key named `k` and string type.
The table must not have a sort (range) key.
## IAM permissions
The ExternalDNS Role must be granted the following permissions:
```json
{
"Effect": "Allow",
"Action": [
"DynamoDB:DescribeTable",
"DynamoDB:PartiQLDelete",
"DynamoDB:PartiQLInsert",
"DynamoDB:PartiQLUpdate",
"DynamoDB:Scan"
],
"Resource": [
"arn:aws:dynamodb:*:*:table/external-dns"
]
}
```
The region and account ID may be specified explicitly specified instead of using wildcards.
## Caching
The DynamoDB registry can optionally cache DNS records read from the provider. This can mitigate
rate limits imposed by the provider.
Caching is enabled by specifying a cache duration with the `--txt-cache-interval` flag.

17
docs/registry/registry.md Normal file
View File

@ -0,0 +1,17 @@
# Registries
A registry persists metadata pertaining to DNS records.
The most important metadata is the owning external-dns deployment.
This is specified using the `--txt-owner-id` flag, specifying a value unique to the
deployment of external-dns and which doesn't change for the lifetime of the deployment.
Deployments in different clusters but sharing a DNS zone need to use different owner IDs.
The registry implementation is specified using the `--registry` flag.
## Supported registries
* [txt](txt.md) (default) - Stores metadata in TXT records in the same provider.
* [dynamodb](dynamodb.md) - Stores metadata in an AWS DynamoDB table.
* noop - Passes metadata directly to the provider. For most providers, this means the metadata is not persisted.
* aws-sd - Stores metadata in AWS Service Discovery. Only usable with the `aws-sd` provider.

97
docs/registry/txt.md Normal file
View File

@ -0,0 +1,97 @@
# The TXT registry
The TXT registry is the default registry.
It stores DNS record metadata in TXT records, using the same provider.
## Prefixes and Suffixes
In order to avoid having the registry TXT records collide with
TXT or CNAME records created from sources, you can configure a fixed prefix or suffix
to be added to the first component of the domain of all registry TXT records.
The prefix or suffix may not be changed after initial deployment,
lest the registry records be orphaned and the metadata be lost.
The prefix or suffix may contain the substring `%{record_type}`, which is replaced with
the record type of the DNS record for which it is storing metadata.
The prefix is specified using the `--txt-prefix` flag and the suffix is specified using
the `--txt-suffix` flag. The two flags are mutually exclusive.
## Wildcard Replacement
The `--txt-wildcard-replacement` flag specifies a string to use to replace the "*" in
registry TXT records for wildcard domains. Without using this, registry TXT records for
wildcard domains will have invalid domain syntax and be rejected by most providers.
## Encryption
Registry TXT records may contain information, such as the internal ingress name or namespace, considered sensitive, , which attackers could exploit to gather information about your infrastructure.
By encrypting TXT records, you can protect this information from unauthorized access.
Encryption is enabled by using the `--txt-encrypt-enabled` flag. The 32-byte AES-256-GCM encryption
key must be specified in URL-safe base64 form, using the `--txt-encrypt-aes-key` flag.
Note that the key used for encryption should be a secure key and properly managed to ensure the security of your TXT records.
### Generating the TXT Encryption Key
Python
```python
python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'
```
Bash
```shell
dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64 | tr -d -- '\n' | tr -- '+/' '-_'; echo
```
OpenSSL
```shell
openssl rand -base64 32 | tr -- '+/' '-_'
```
PowerShell
```powershell
# Add System.Web assembly to session, just in case
Add-Type -AssemblyName System.Web
[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes([System.Web.Security.Membership]::GeneratePassword(32,4))).Replace("+","-").Replace("/","_")
```
Terraform
```hcl
resource "random_password" "txt_key" {
length = 32
override_special = "-_"
}
```
### Manually Encrypting/Decrypting TXT Records
In some cases you might need to edit registry TXT records. The following example Go code encrypts and decrypts such records.
```go
package main
import (
"fmt"
"sigs.k8s.io/external-dns/endpoint"
)
func main() {
key := []byte("testtesttesttesttesttesttesttest")
encrypted, _ := endpoint.EncryptText(
"heritage=external-dns,external-dns/owner=example,external-dns/resource=ingress/default/example",
key,
nil,
)
decrypted, _, _ := endpoint.DecryptText(encrypted, key)
fmt.Println(decrypted)
}
```
## Caching
The TXT registry can optionally cache DNS records read from the provider. This can mitigate
rate limits imposed by the provider.
Caching is enabled by specifying a cache duration with the `--txt-cache-interval` flag.

View File

@ -48,7 +48,7 @@ spec:
- name: external-dns
# You will need to check what the latest version is yourself:
# https://github.com/kubernetes-sigs/external-dns/releases
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
# (optional) limit to only example.com domains; change to match the
@ -114,7 +114,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
# (optional) limit to only example.com domains; change to match the

View File

@ -57,7 +57,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # or ingress or both
- --provider=akamai
@ -143,7 +143,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # or ingress or both
- --provider=akamai

View File

@ -113,7 +113,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -187,7 +187,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -233,9 +233,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foo
annotations:
kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller.
spec:
ingressClassName: nginx # use the one that corresponds to your ingress controller.
rules:
- host: foo.external-dns-test.com
http:

View File

@ -24,7 +24,7 @@ as Kubernetes does with the AWS cloud provider.
In the examples that follow, it is assumed that you configured the ALB Ingress
Controller with the `ingress-class=alb` argument (not to be confused with the
same argument to ExternalDNS) so that the controller will only respect Ingress
objects with the `kubernetes.io/ingress.class` annotation set to "alb".
objects with the `ingressClassName` field set to "alb".
## Deploy an example application
@ -80,7 +80,6 @@ kind: Ingress
metadata:
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
kubernetes.io/ingress.class: alb
name: echoserver
spec:
ingressClassName: alb
@ -120,7 +119,6 @@ metadata:
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
external-dns.alpha.kubernetes.io/hostname: echoserver.mycluster.example.org, echoserver.example.org
kubernetes.io/ingress.class: alb
name: echoserver
spec:
ingressClassName: alb
@ -159,7 +157,6 @@ metadata:
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/ip-address-type: dualstack
kubernetes.io/ingress.class: alb
name: echoserver
spec:
ingressClassName: alb

View File

@ -81,7 +81,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
env:
- name: AWS_REGION
value: us-east-1 # put your CloudMap NameSpace region
@ -148,7 +148,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
env:
- name: AWS_REGION
value: us-east-1 # put your CloudMap NameSpace region

View File

@ -413,7 +413,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -508,7 +508,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -739,9 +739,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: "nginx" # use the one that corresponds to your ingress controller.
spec:
ingressClassName: nginx
rules:
- host: server.example.com
http:
@ -936,7 +935,7 @@ Running several fast polling ExternalDNS instances in a given account can easily
* `--source=ingress --source=service` - specify multiple times for multiple sources
* `--namespace=my-app`
* `--label-filter=app in (my-app)`
* `--annotation-filter=kubernetes.io/ingress.class in (nginx-external)` - note that this filter would apply to services too..
* `--ingress-class=nginx-external`
* Limit services watched by type (not applicable to ingress or other types)
* `--service-type-filter=LoadBalancer` default `all`
* Limit the hosted zones considered
@ -962,7 +961,7 @@ A simple way to implement randomised startup is with an init container:
spec:
initContainers:
- name: init-jitter
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
command:
- /bin/sh
- -c

View File

@ -130,7 +130,7 @@ spec:
spec:
containers:
- name: externaldns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -201,7 +201,7 @@ spec:
serviceAccountName: externaldns
containers:
- name: externaldns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -272,7 +272,7 @@ spec:
serviceAccountName: externaldns
containers:
- name: externaldns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -416,9 +416,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
ingressClassName: nginx
rules:
- host: server.example.com
http:

View File

@ -50,7 +50,7 @@ The following fields are used:
* `tenantId` (**required**) - run `az account show --query "tenantId"` or by selecting Azure Active Directory in the Azure Portal and checking the _Directory ID_ under Properties.
* `subscriptionId` (**required**) - run `az account show --query "id"` or by selecting Subscriptions in the Azure Portal.
* `resourceGroup` (**required**) is the Resource Group created in a previous step that contains the Azure DNS Zone.
* `aadClientID` and `aaClientSecret` are associated with the Service Principal. This is only used with Service Principal method documented in the next section.
* `aadClientID` and `aadClientSecret` are associated with the Service Principal. This is only used with Service Principal method documented in the next section.
* `useManagedIdentityExtension` - this is set to `true` if you use either AKS Kubelet Identity or AAD Pod Identities methods documented in the next section.
* `userAssignedIdentityID` - this contains the client id from the Managed identitty when using the AAD Pod Identities method documented in the next setion.
@ -356,7 +356,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -424,7 +424,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -495,7 +495,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -560,9 +560,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
ingressClassName: nginx
rules:
- host: server.example.com
http:
@ -649,3 +648,9 @@ resource group:
```bash
$ az group delete --name "MyDnsResourceGroup"
```
## More tutorials
A video explanantion is available here: https://www.youtube.com/watch?v=VSn6DPKIhM8&list=PLpbcUe4chE79sB7Jg7B4z3HytqUUEwcNE
![image](https://user-images.githubusercontent.com/6548359/235437721-87611869-75f2-4f32-bb35-9da585e46299.png)

View File

@ -46,7 +46,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --log-level=debug
- --source=service
@ -136,7 +136,7 @@ spec:
secretName: bluecatconfig
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
volumeMounts:
- name: bluecatconfig
mountPath: "/etc/external-dns/"

View File

@ -41,7 +41,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -59,7 +59,7 @@ kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
@ -74,7 +74,7 @@ rules:
resources: ["nodes"]
verbs: ["list"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
@ -105,7 +105,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -20,6 +20,8 @@ Snippet from [Cloudflare - Getting Started](https://api.cloudflare.com/#getting-
API Token will be preferred for authentication if `CF_API_TOKEN` environment variable is set.
Otherwise `CF_API_KEY` and `CF_API_EMAIL` should be set to run ExternalDNS with Cloudflare.
You may provide the Cloudflare API token through a file by setting the
`CF_API_TOKEN="file:/path/to/token"`.
When using API Token authentication, the token should be granted Zone `Read`, DNS `Edit` privileges, and access to `All zones`.
@ -54,7 +56,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -123,7 +125,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -26,7 +26,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -102,7 +102,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress

View File

@ -108,7 +108,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --provider=coredns
@ -175,7 +175,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --provider=coredns
@ -198,9 +198,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
ingressClassName: nginx
rules:
- host: nginx.example.org
http:

View File

@ -59,7 +59,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -136,7 +136,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -43,7 +43,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -107,7 +107,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -35,7 +35,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone you create in DNSimple.
@ -100,7 +100,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone you create in DNSimple.

View File

@ -43,7 +43,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --txt-prefix=_d

View File

@ -41,7 +41,7 @@ spec:
# serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress # or service or both
- --provider=exoscale
@ -109,9 +109,9 @@ kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
external-dns.alpha.kubernetes.io/target: {{ Elastic-IP-address }}
spec:
ingressClassName: nginx
rules:
- host: via-ingress.example.com
http:

View File

@ -27,7 +27,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --log-level=debug
- --source=service

View File

@ -0,0 +1,33 @@
# Configuring ExternalDNS to use the F5 Networks VirtualServer Source
This tutorial describes how to configure ExternalDNS to use the F5 Networks VirtualServer Source. It is meant to supplement the other provider-specific setup tutorials.
The F5 Networks VirtualServer CRD is part of [this](https://github.com/F5Networks/k8s-bigip-ctlr) project. See more in-depth info regarding the VirtualServer CRD [here](https://github.com/F5Networks/k8s-bigip-ctlr/blob/master/docs/config_examples/customResource/CustomResource.md#virtualserver).
## Start with ExternalDNS with the F5 Networks VirtualServer source
1. Make sure that you have the `k8s-bigip-ctlr` installed in your cluster. The needed CRDs are bundled within the controller.
2. In your Helm `values.yaml` add:
```
sources:
- ...
- f5-virtualserver
- ...
```
or add it in your `Deployment` if you aren't installing `external-dns` via Helm:
```
args:
- --source=f5-virtualserver
```
Note that, in case you're not installing via Helm, you'll need the following in the `ClusterRole` bound to the service account of `external-dns`:
```
- apiGroups:
- cis.f5.com
resources:
- virtualservers
verbs:
- get
- list
- watch
```

View File

@ -39,7 +39,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -56,7 +56,7 @@ kind: ServiceAccount
metadata:
name: external-dns
---
apiVersion: rbac.authorization.k8s.io/v1beta1
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: external-dns
@ -71,7 +71,7 @@ rules:
resources: ["nodes"]
verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: external-dns-viewer
@ -103,7 +103,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -72,7 +72,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
# Add desired Gateway API Route sources.
- --source=gateway-httproute

View File

@ -319,7 +319,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress

View File

@ -22,7 +22,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=gloo-proxy
- --gloo-namespace=custom-gloo-system # comma separated gloo system namespace list. Omit to use the default (gloo-system)
@ -90,7 +90,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=gloo-proxy
- --gloo-namespace=custom-gloo-system # comma separated gloo system namespace list. Omit to use the default (gloo-system)

View File

@ -44,7 +44,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -115,7 +115,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -31,7 +31,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --log-level=debug
- --source=service
@ -96,7 +96,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --log-level=debug
- --source=service
@ -114,7 +114,7 @@ spec:
First lets deploy a Kafka Stateful set, a simple example(a lot of stuff is missing) with a headless service called `ksvc`
```yaml
apiVersion: apps/v1beta1
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: kafka

View File

@ -69,7 +69,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -142,7 +142,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -69,7 +69,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains.
@ -150,7 +150,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --domain-filter=example.com # (optional) limit to only example.com domains.

View File

@ -28,7 +28,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -98,7 +98,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress

View File

@ -22,7 +22,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=kong-tcpingress
- --provider=aws
@ -86,7 +86,7 @@ spec:
containers:
- name: external-dns
# update this to the desired external-dns version
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=kong-tcpingress
- --provider=aws

View File

@ -141,8 +141,6 @@ Create the following Ingress to expose the echoserver application to the Interne
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: skipper
name: echoserver
spec:
ingressClassName: skipper
@ -181,7 +179,6 @@ kind: Ingress
metadata:
annotations:
external-dns.alpha.kubernetes.io/hostname: echoserver.mycluster.example.org, echoserver.example.org
kubernetes.io/ingress.class: skipper
name: echoserver
spec:
ingressClassName: skipper
@ -218,7 +215,6 @@ kind: Ingress
metadata:
annotations:
alb.ingress.kubernetes.io/ip-address-type: dualstack
kubernetes.io/ingress.class: skipper
name: echoserver
spec:
ingressClassName: skipper
@ -256,7 +252,6 @@ kind: Ingress
metadata:
annotations:
zalando.org/aws-load-balancer-type: nlb
kubernetes.io/ingress.class: skipper
name: echoserver
spec:
ingressClassName: skipper

View File

@ -41,7 +41,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -105,7 +105,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -0,0 +1,28 @@
# Creating MX record with CRD source
You can create and manage MX records with the help of [CRD source](/docs/contributing/crd-source.md)
and `DNSEndpoint` CRD. Currently, this feature is only supported by `aws`, `azure`, and `google` providers.
In order to start managing MX records you need to set the `--managed-record-types MX` flag.
```console
external-dns --source crd --provider {aws|azure|google} --managed-record-types A --managed-record-types CNAME --managed-record-types MX
```
Targets within the CRD need to be specified according to the RFC 1034 (section 3.6.1). Below is an example of
`example.com` DNS MX record which specifies two separate targets with distinct priorities.
```yaml
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
name: examplemxrecord
spec:
endpoints:
- dnsName: example.com
recordTTL: 180
recordType: MX
targets:
- 10 mailhost1.example.com
- 20 mailhost2.example.com
```

View File

@ -273,7 +273,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --domain-filter=external-dns-test.gcp.zalan.do
@ -294,8 +294,6 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
ingressClassName: nginx
rules:
@ -570,7 +568,7 @@ spec:
- --google-project=zalando-external-dns-test
- --registry=txt
- --txt-owner-id=my-identifier
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
name: external-dns
securityContext:
fsGroup: 65534
@ -595,8 +593,6 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: nginx
spec:
ingressClassName: nginx
rules:

View File

@ -3,8 +3,9 @@
This tutorial describes how to configure ExternalDNS to use the cluster nodes as source.
Using nodes (`--source=node`) as source is possible to synchronize a DNS zone with the nodes of a cluster.
The node source adds an `A` record per each node `externalIP` (if not found, node's `internalIP` is used).
The TTL record can be set with the `external-dns.alpha.kubernetes.io/ttl` node annotation.
The node source adds an `A` record per each node `externalIP` (if not found, any IPv4 `internalIP` is used instead).
It also adds an `AAAA` record per each node IPv6 `internalIP`.
The TTL of the records can be set with the `external-dns.alpha.kubernetes.io/ttl` node annotation.
## Manifest (for cluster without RBAC enabled)
@ -28,7 +29,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=node # will use nodes as source
- --provider=aws
@ -99,7 +100,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=node # will use nodes as source
- --provider=aws

View File

@ -61,7 +61,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -125,7 +125,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -66,7 +66,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=openshift-route
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones
@ -133,7 +133,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=openshift-route
- --domain-filter=external-dns-test.my-org.com # will make ExternalDNS see only the hosted zones matching provided domain, omit to process all available hosted zones

View File

@ -6,16 +6,25 @@ Make sure to use the latest version of ExternalDNS for this tutorial.
## Creating an OCI DNS Zone
Create a DNS zone which will contain the managed DNS records. Let's use `example.com` as an reference here.
Create a DNS zone which will contain the managed DNS records. Let's use
`example.com` as a reference here. Make note of the OCID of the compartment
in which you created the zone; you'll need to provide that later.
For more information about OCI DNS see the documentation [here][1].
## Deploy ExternalDNS
Connect your `kubectl` client to the cluster you want to test ExternalDNS with.
The OCI provider supports two authentication options: key-based and instance
principals.
### Key-based
We first need to create a config file containing the information needed to connect with the OCI API.
Create a new file (oci.yaml) and modify the contents to match the example below. Be sure to adjust the values to match your own credentials:
Create a new file (oci.yaml) and modify the contents to match the example
below. Be sure to adjust the values to match your own credentials, and the OCID
of the compartment containing the zone:
```yaml
auth:
@ -37,7 +46,29 @@ Create a secret using the config file above:
$ kubectl create secret generic external-dns-config --from-file=oci.yaml
```
### Manifest (for clusters with RBAC enabled)
### OCI IAM Instance Principal
If you're running ExternalDNS within OCI, you can use OCI IAM instance
principals to authenticate with OCI. This obviates the need to create the
secret with your credentials. You'll need to ensure an OCI IAM policy exists
with a statement granting the `manage dns` permission on zones and records in
the target compartment to the dynamic group covering your instance running
ExternalDNS.
E.g.:
```
Allow dynamic-group <dynamic-group-name> to manage dns in compartment id <target-compartment-OCID>
```
You'll also need to add the `--oci-auth-instance-principal` flag to enable
this type of authentication. Finally, you'll need to add the
`--oci-compartment-ocid=ocid1.compartment.oc1...` flag to provide the OCID of
the compartment containing the zone to be managed.
For more information about OCI IAM instance principals, see the documentation [here][2].
For more information about OCI IAM policy details for the DNS service, see the documentation [here][3].
## Manifest (for clusters with RBAC enabled)
Apply the following manifest to deploy ExternalDNS.
@ -93,7 +124,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress
@ -159,3 +190,6 @@ $ kubectl apply -f nginx.yaml
```
[1]: https://docs.cloud.oracle.com/iaas/Content/DNS/Concepts/dnszonemanagement.htm
[2]: https://docs.cloud.oracle.com/iaas/Content/Identity/Reference/dnspolicyreference.htm
[3]: https://docs.cloud.oracle.com/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm

View File

@ -86,7 +86,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -160,7 +160,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -42,7 +42,7 @@ spec:
# serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # or ingress or both
- --provider=pdns

View File

@ -78,7 +78,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
# If authentication is disabled and/or you didn't create
# a secret, you can remove this block.
envFrom:

View File

@ -35,7 +35,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -105,7 +105,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -213,10 +213,10 @@ spec:
Consult [AWS ExternalDNS setup docs](aws.md) for installation guidelines.
In ExternalDNS containers args, make sure to specify `annotation-filter` and `aws-zone-type`:
In ExternalDNS containers args, make sure to specify `aws-zone-type` and `ingress-class`:
```yaml
apiVersion: apps/v1beta2
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
@ -241,9 +241,9 @@ spec:
- --provider=aws
- --registry=txt
- --txt-owner-id=external-dns
- --annotation-filter=kubernetes.io/ingress.class in (external-ingress)
- --ingress-class=external-ingress
- --aws-zone-type=public
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
name: external-dns-public
```
@ -251,10 +251,10 @@ spec:
Consult [AWS ExternalDNS setup docs](aws.md) for installation guidelines.
In ExternalDNS containers args, make sure to specify `annotation-filter` and `aws-zone-type`:
In ExternalDNS containers args, make sure to specify `aws-zone-type` and `ingress-class`:
```yaml
apiVersion: apps/v1beta2
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
@ -279,28 +279,27 @@ spec:
- --provider=aws
- --registry=txt
- --txt-owner-id=dev.k8s.nexus
- --annotation-filter=kubernetes.io/ingress.class in (internal-ingress)
- --ingress-class=internal-ingress
- --aws-zone-type=private
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
name: external-dns-private
```
## Create application Service definitions
For this setup to work, you've to create two Service definitions for your application.
For this setup to work, you need to create two Ingress definitions for your application.
At first, create public Service definition:
At first, create a public Ingress definition:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "external-ingress"
labels:
app: app
name: app-public
spec:
ingressClassName: external-ingress
rules:
- host: app.domain.com
http:
@ -313,18 +312,17 @@ spec:
pathType: Prefix
```
Then create private Service definition:
Then create a private Ingress definition:
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "internal-ingress"
labels:
app: app
name: app-private
spec:
ingressClassName: internal-ingress
rules:
- host: app.domain.com
http:
@ -347,12 +345,12 @@ metadata:
certmanager.k8s.io/acme-challenge-type: "dns01"
certmanager.k8s.io/acme-dns01-provider: "route53"
certmanager.k8s.io/cluster-issuer: "letsencrypt-production"
kubernetes.io/ingress.class: "external-ingress"
kubernetes.io/tls-acme: "true"
labels:
app: app
name: app-public
spec:
ingressClassName: "external-ingress"
rules:
- host: app.domain.com
http:
@ -375,12 +373,11 @@ And reuse the requested certificate in private Service definition:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: "internal-ingress"
labels:
app: app
name: app-private
spec:
ingressClassName: "internal-ingress"
rules:
- host: app.domain.com
http:

View File

@ -53,7 +53,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -120,7 +120,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -54,7 +54,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --provider=rdns
@ -123,7 +123,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=ingress
- --provider=rdns
@ -142,9 +142,8 @@ apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
ingressClassName: nginx
rules:
- host: nginx.lb.rancher.cloud
http:

View File

@ -218,7 +218,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --registry=txt
- --txt-prefix=external-dns-
@ -260,7 +260,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --registry=txt
- --txt-prefix=external-dns-

View File

@ -53,7 +53,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -121,7 +121,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

View File

@ -20,7 +20,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- ... # your arguments here
securityContext:

View File

@ -129,7 +129,7 @@ spec:
- --policy=sync # set `upsert-only` would prevent ExternalDNS from deleting any records
- --tencent-cloud-zone-type=private # only look at private hosted zones. set `public` to use the public dns service.
- --tencent-cloud-config-file=/etc/kubernetes/tencent-cloud.json
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
imagePullPolicy: Always
name: external-dns
resources: {}

View File

@ -0,0 +1,96 @@
# Configuring ExternalDNS to use the Traefik Proxy Source
This tutorial describes how to configure ExternalDNS to use the Traefik Proxy source.
It is meant to supplement the other provider-specific setup tutorials.
## 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
# update this to the desired external-dns version
image: registry.k8s.io/external-dns/external-dns:v0.13.3
args:
- --source=traefik-proxy
- --provider=aws
- --registry=txt
- --txt-owner-id=my-identifier
```
## 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: [""]
resources: ["nodes"]
verbs: ["list","watch"]
- apiGroups: ["traefik.containo.us","traefik.io"]
resources: ["ingressroutes", "ingressroutetcps", "ingressrouteudps"]
verbs: ["get","watch","list"]
---
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
# update this to the desired external-dns version
image: registry.k8s.io/external-dns/external-dns:v0.13.3
args:
- --source=traefik-proxy
- --provider=aws
- --registry=txt
- --txt-owner-id=my-identifier
```

View File

@ -36,7 +36,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains
@ -107,7 +107,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains

View File

@ -44,7 +44,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress # ingress is also possible
@ -116,7 +116,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service
- --source=ingress

View File

@ -66,7 +66,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --provider=vinyldns
- --source=service
@ -137,7 +137,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --provider=vinyldns
- --source=service

View File

@ -42,7 +42,7 @@ spec:
spec:
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.
@ -106,7 +106,7 @@ spec:
serviceAccountName: external-dns
containers:
- name: external-dns
image: registry.k8s.io/external-dns/external-dns:v0.13.2
image: registry.k8s.io/external-dns/external-dns:v0.13.5
args:
- --source=service # ingress is also possible
- --domain-filter=example.com # (optional) limit to only example.com domains; change to match the zone created above.

134
endpoint/crypto.go Normal file
View File

@ -0,0 +1,134 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package endpoint
import (
"bytes"
"compress/gzip"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"fmt"
"io"
log "github.com/sirupsen/logrus"
)
// EncryptText gzip input data and encrypts it using the supplied AES key
func EncryptText(text string, aesKey []byte, nonceEncoded []byte) (string, error) {
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", err
}
nonce := make([]byte, gcm.NonceSize())
if nonceEncoded == nil {
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return "", err
}
} else {
if _, err = base64.StdEncoding.Decode(nonce, nonceEncoded); err != nil {
return "", err
}
}
data, err := compressData([]byte(text))
if err != nil {
return "", err
}
cipherData := gcm.Seal(nonce, nonce, data, nil)
return base64.StdEncoding.EncodeToString(cipherData), nil
}
// DecryptText decrypt gziped data using a supplied AES encryption key ang ungzip it
// in case of decryption failed, will return original input and decryption error
func DecryptText(text string, aesKey []byte) (decryptResult string, encryptNonce string, err error) {
block, err := aes.NewCipher(aesKey)
if err != nil {
return "", "", err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return "", "", err
}
nonceSize := gcm.NonceSize()
data, err := base64.StdEncoding.DecodeString(text)
if err != nil {
return "", "", err
}
if len(data) <= nonceSize {
return "", "", fmt.Errorf("the encoded data from text %#v is shorter than %#v bytes and can't be decoded", text, nonceSize)
}
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
plaindata, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return "", "", err
}
plaindata, err = decompressData(plaindata)
if err != nil {
log.Debugf("Failed to decompress data based on the base64 encoded text %#v. Got error %#v.", text, err)
return "", "", err
}
return string(plaindata), base64.StdEncoding.EncodeToString(nonce), nil
}
// decompressData gzip compressed data
func decompressData(data []byte) (resData []byte, err error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, err
}
defer gz.Close()
var b bytes.Buffer
if _, err = b.ReadFrom(gz); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// compressData by gzip, for minify data stored in registry
func compressData(data []byte) (compressedData []byte, err error) {
var b bytes.Buffer
gz, err := gzip.NewWriterLevel(&b, gzip.BestCompression)
if err != nil {
return nil, err
}
defer gz.Close()
if _, err = gz.Write(data); err != nil {
return nil, err
}
if err = gz.Flush(); err != nil {
return nil, err
}
if err = gz.Close(); err != nil {
return nil, err
}
return b.Bytes(), nil
}

58
endpoint/crypto_test.go Normal file
View File

@ -0,0 +1,58 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package endpoint
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestEncrypt(t *testing.T) {
// Verify that text encryption and decryption works
aesKey := []byte("s%zF`.*'5`9.AhI2!B,.~hmbs^.*TL?;")
plaintext := "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
encryptedtext, err := EncryptText(plaintext, aesKey, nil)
require.NoError(t, err)
decryptedtext, _, err := DecryptText(encryptedtext, aesKey)
require.NoError(t, err)
if plaintext != decryptedtext {
t.Errorf("Original plain text %#v differs from the resulting decrypted text %#v", plaintext, decryptedtext)
}
// Verify that decrypt returns an error and empty data if wrong AES encryption key is used
decryptedtext, _, err = DecryptText(encryptedtext, []byte("s'J!jD`].LC?g&Oa11AgTub,j48ts/96"))
require.Error(t, err)
if decryptedtext != "" {
t.Error("Data decryption failed, empty string should be as result")
}
// Verify that decrypt returns an error and empty data if unencrypted input is is supplied
decryptedtext, _, err = DecryptText(plaintext, aesKey)
require.Error(t, err)
if decryptedtext != "" {
t.Errorf("Data decryption failed, empty string should be as result")
}
// Verify that a known encrypted text is decrypted to what is expected
encryptedtext = "0Mfzf6wsN8llrfX0ucDZ6nlc2+QiQfKKedjPPLu5atb2I35L9nUZeJcCnuLVW7CVW3K0h94vSuBLdXnMrj8Vcm0M09shxaoF48IcCpD03XtQbKXqk2hPbsW6+JybvplHIQGr16/PcjUSObGmR9yjf38+qEltApkKvrPjsyw43BX4eE10rL0Bln33UJD7/w+zazRDPFlAcbGtkt0ETKHnvyB3/aCddLipvrhjCXj2ZY/ktRF6h716kJRgXU10dCIQHFYU45MIdxI+k10HK3yZqhI2V0Gp2xjrFV/LRQ7/OS9SFee4asPWUYxbCEsnOzp8qc0dCPFSo1dtADzWnUZnsAcbnjtudT4milfLJc5CxDk1v3ykqQ/ajejwHjWQ7b8U6AsTErbezfdcqrb5IzkLgHb5TosnfrdDmNc9GcKfpsrCHbVY8KgNwMVdtwavLv7d9WM6sooUlZ3t0sABGkzagXQmPRvwLnkSOlie5XrnzWo8/8/4UByLga29CaXO"
decryptedtext, _, err = DecryptText(encryptedtext, aesKey)
require.NoError(t, err)
if decryptedtext != plaintext {
t.Error("Decryption of text didn't result in expected plaintext result.")
}
}

View File

@ -24,41 +24,22 @@ import (
// DomainFilterInterface defines the interface to select matching domains for a specific provider or runtime
type DomainFilterInterface interface {
Match(domain string) bool
IsConfigured() bool
}
type MatchAllDomainFilters []DomainFilterInterface
func (f MatchAllDomainFilters) Match(domain string) bool {
if !f.IsConfigured() {
return true
}
for _, filter := range f {
if filter == nil {
continue
}
if filter.IsConfigured() && !filter.Match(domain) {
if !filter.Match(domain) {
return false
}
}
return true
}
func (f MatchAllDomainFilters) IsConfigured() bool {
if f == nil {
return false
}
for _, filter := range f {
if filter == nil {
continue
}
if filter.IsConfigured() {
return true
}
}
return len(f) > 0
}
// DomainFilter holds a lists of valid domain names
type DomainFilter struct {
// Filters define what domains to match

View File

@ -30,6 +30,8 @@ import (
const (
// RecordTypeA is a RecordType enum value
RecordTypeA = "A"
// RecordTypeAAAA is a RecordType enum value
RecordTypeAAAA = "AAAA"
// RecordTypeCNAME is a RecordType enum value
RecordTypeCNAME = "CNAME"
// RecordTypeTXT is a RecordType enum value
@ -40,6 +42,8 @@ const (
RecordTypeNS = "NS"
// RecordTypePTR is a RecordType enum value
RecordTypePTR = "PTR"
// RecordTypeMX is a RecordType enum value
RecordTypeMX = "MX"
)
// TTL is a structure defining the TTL of a DNS record
@ -158,13 +162,20 @@ type ProviderSpecificProperty struct {
// ProviderSpecific holds configuration which is specific to individual DNS providers
type ProviderSpecific []ProviderSpecificProperty
// EndpointKey is the type of a map key for separating endpoints or targets.
type EndpointKey struct {
DNSName string
RecordType string
SetIdentifier string
}
// Endpoint is a high-level way of a connection between a service and an IP
type Endpoint struct {
// The hostname of the DNS record
DNSName string `json:"dnsName,omitempty"`
// The targets the DNS record points to
Targets Targets `json:"targets,omitempty"`
// RecordType type of record, e.g. CNAME, A, SRV, TXT etc
// RecordType type of record, e.g. CNAME, A, AAAA, SRV, TXT etc
RecordType string `json:"recordType,omitempty"`
// Identifier to distinguish multiple records with the same name and type (e.g. Route53 records with routing policies other than 'simple')
SetIdentifier string `json:"setIdentifier,omitempty"`
@ -218,22 +229,52 @@ func (e *Endpoint) WithSetIdentifier(setIdentifier string) *Endpoint {
// warrant its own field on the Endpoint object itself. It differs from Labels in the fact that it's
// not persisted in the Registry but only kept in memory during a single record synchronization.
func (e *Endpoint) WithProviderSpecific(key, value string) *Endpoint {
if e.ProviderSpecific == nil {
e.ProviderSpecific = ProviderSpecific{}
}
e.ProviderSpecific = append(e.ProviderSpecific, ProviderSpecificProperty{Name: key, Value: value})
e.SetProviderSpecificProperty(key, value)
return e
}
// GetProviderSpecificProperty returns a ProviderSpecificProperty if the property exists.
func (e *Endpoint) GetProviderSpecificProperty(key string) (ProviderSpecificProperty, bool) {
// GetProviderSpecificProperty returns the value of a ProviderSpecificProperty if the property exists.
func (e *Endpoint) GetProviderSpecificProperty(key string) (string, bool) {
for _, providerSpecific := range e.ProviderSpecific {
if providerSpecific.Name == key {
return providerSpecific, true
return providerSpecific.Value, true
}
}
return ProviderSpecificProperty{}, false
return "", false
}
// SetProviderSpecificProperty sets the value of a ProviderSpecificProperty.
func (e *Endpoint) SetProviderSpecificProperty(key string, value string) {
for i, providerSpecific := range e.ProviderSpecific {
if providerSpecific.Name == key {
e.ProviderSpecific[i] = ProviderSpecificProperty{
Name: key,
Value: value,
}
return
}
}
e.ProviderSpecific = append(e.ProviderSpecific, ProviderSpecificProperty{Name: key, Value: value})
}
// DeleteProviderSpecificProperty deletes any ProviderSpecificProperty of the specified name.
func (e *Endpoint) DeleteProviderSpecificProperty(key string) {
for i, providerSpecific := range e.ProviderSpecific {
if providerSpecific.Name == key {
e.ProviderSpecific = append(e.ProviderSpecific[:i], e.ProviderSpecific[i+1:]...)
return
}
}
}
// Key returns the EndpointKey of the Endpoint.
func (e *Endpoint) Key() EndpointKey {
return EndpointKey{
DNSName: e.DNSName,
RecordType: e.RecordType,
SetIdentifier: e.SetIdentifier,
}
}
func (e *Endpoint) String() string {

View File

@ -17,6 +17,8 @@ limitations under the License.
package endpoint
import (
log "github.com/sirupsen/logrus"
"errors"
"fmt"
"sort"
@ -41,6 +43,9 @@ const (
// DualstackLabelKey is the name of the label that identifies dualstack endpoints
DualstackLabelKey = "dualstack"
// txtEncryptionNonce label for keep same nonce for same txt records, for prevent different result of encryption for same txt record, it can cause issues for some providers
txtEncryptionNonce = "txt-encryption-nonce"
)
// Labels store metadata related to the endpoint
@ -55,7 +60,7 @@ func NewLabels() Labels {
// NewLabelsFromString constructs endpoints labels from a provided format string
// if heritage set to another value is found then error is returned
// no heritage automatically assumes is not owned by external-dns and returns invalidHeritage error
func NewLabelsFromString(labelText string) (Labels, error) {
func NewLabelsFromStringPlain(labelText string) (Labels, error) {
endpointLabels := map[string]string{}
labelText = strings.Trim(labelText, "\"") // drop quotes
tokens := strings.Split(labelText, ",")
@ -85,9 +90,26 @@ func NewLabelsFromString(labelText string) (Labels, error) {
return endpointLabels, nil
}
// Serialize transforms endpoints labels into a external-dns recognizable format string
func NewLabelsFromString(labelText string, aesKey []byte) (Labels, error) {
if len(aesKey) != 0 {
decryptedText, encryptionNonce, err := DecryptText(strings.Trim(labelText, "\""), aesKey)
//in case if we have decryption error, just try process original text
//decryption errors should be ignored here, because we can already have plain-text labels in registry
if err == nil {
labels, err := NewLabelsFromStringPlain(decryptedText)
if err == nil {
labels[txtEncryptionNonce] = encryptionNonce
}
return labels, err
}
}
return NewLabelsFromStringPlain(labelText)
}
// SerializePlain transforms endpoints labels into a external-dns recognizable format string
// withQuotes adds additional quotes
func (l Labels) Serialize(withQuotes bool) string {
func (l Labels) SerializePlain(withQuotes bool) string {
var tokens []string
tokens = append(tokens, fmt.Sprintf("heritage=%s", heritage))
var keys []string
@ -104,3 +126,31 @@ func (l Labels) Serialize(withQuotes bool) string {
}
return strings.Join(tokens, ",")
}
// Serialize same to SerializePlain, but encrypt data, if encryption enabled
func (l Labels) Serialize(withQuotes bool, txtEncryptEnabled bool, aesKey []byte) string {
if !txtEncryptEnabled {
return l.SerializePlain(withQuotes)
}
var encryptionNonce []byte = nil
if extractedNonce, nonceExists := l[txtEncryptionNonce]; nonceExists {
encryptionNonce = []byte(extractedNonce)
delete(l, txtEncryptionNonce)
}
text := l.SerializePlain(false)
log.Debugf("Encrypt the serialized text %#v before returning it.", text)
var err error
text, err = EncryptText(text, aesKey, encryptionNonce)
if err != nil {
log.Fatalf("Failed to encrypt the text %#v using the encryption key %#v. Got error %#v.", text, aesKey, err)
}
if withQuotes {
text = fmt.Sprintf("\"%s\"", text)
}
log.Debugf("Serialized text after encryption is %#v.", text)
return text
}

View File

@ -25,14 +25,18 @@ import (
type LabelsSuite struct {
suite.Suite
foo Labels
fooAsText string
fooAsTextWithQuotes string
barText string
barTextAsMap Labels
noHeritageText string
wrongHeritageText string
multipleHeritageText string // considered invalid
aesKey []byte
foo Labels
fooAsText string
fooAsTextWithQuotes string
fooAsTextEncrypted string
fooAsTextWithQuotesEncrypted string
barText string
barTextEncrypted string
barTextAsMap Labels
noHeritageText string
wrongHeritageText string
multipleHeritageText string // considered invalid
}
func (suite *LabelsSuite) SetupTest() {
@ -40,48 +44,79 @@ func (suite *LabelsSuite) SetupTest() {
"owner": "foo-owner",
"resource": "foo-resource",
}
suite.aesKey = []byte(")K_Fy|?Z.64#UuHm`}[d!GC%WJM_fs{_")
suite.fooAsText = "heritage=external-dns,external-dns/owner=foo-owner,external-dns/resource=foo-resource"
suite.fooAsTextWithQuotes = fmt.Sprintf(`"%s"`, suite.fooAsText)
suite.fooAsTextEncrypted = `+lvP8q9KHJ6BS6O81i2Q6DLNdf2JSKy8j/gbZKviTZlGYj7q+yDoYMgkQ1hPn6urtGllM5bfFMcaaHto52otQtiOYrX8990J3kQqg4s47m3bH3Ejl8RSxSSuWJM3HJtPghQzYg0/LSOsdQ0=`
suite.fooAsTextWithQuotesEncrypted = fmt.Sprintf(`"%s"`, suite.fooAsTextEncrypted)
suite.barTextAsMap = map[string]string{
"owner": "bar-owner",
"resource": "bar-resource",
"new-key": "bar-new-key",
}
suite.barText = "heritage=external-dns,,external-dns/owner=bar-owner,external-dns/resource=bar-resource,external-dns/new-key=bar-new-key,random=stuff,no-equal-sign,," // also has some random gibberish
suite.barTextEncrypted = "yi6vVATlgYN0enXBIupVK2atNUKtajofWMroWtvZjUanFZXlWvqjJPpjmMd91kv86bZj+syQEP0uR3TK6eFVV7oKFh/NxYyh238FjZ+25zlXW9TgbLoMalUNOkhKFdfXkLeeaqJjePB59t+kQBYX+ZEryK652asPs6M+xTIvtg07N7WWZ6SjJujm0RRISg=="
suite.noHeritageText = "external-dns/owner=random-owner"
suite.wrongHeritageText = "heritage=mate,external-dns/owner=random-owner"
suite.multipleHeritageText = "heritage=mate,heritage=external-dns,external-dns/owner=random-owner"
}
func (suite *LabelsSuite) TestSerialize() {
suite.Equal(suite.fooAsText, suite.foo.Serialize(false), "should serializeLabel")
suite.Equal(suite.fooAsTextWithQuotes, suite.foo.Serialize(true), "should serializeLabel")
suite.Equal(suite.fooAsText, suite.foo.SerializePlain(false), "should serializeLabel")
suite.Equal(suite.fooAsTextWithQuotes, suite.foo.SerializePlain(true), "should serializeLabel")
suite.Equal(suite.fooAsText, suite.foo.Serialize(false, false, nil), "should serializeLabel")
suite.Equal(suite.fooAsTextWithQuotes, suite.foo.Serialize(true, false, nil), "should serializeLabel")
suite.Equal(suite.fooAsText, suite.foo.Serialize(false, false, suite.aesKey), "should serializeLabel")
suite.Equal(suite.fooAsTextWithQuotes, suite.foo.Serialize(true, false, suite.aesKey), "should serializeLabel")
suite.NotEqual(suite.fooAsText, suite.foo.Serialize(false, true, suite.aesKey), "should serializeLabel and encrypt")
suite.NotEqual(suite.fooAsTextWithQuotes, suite.foo.Serialize(true, true, suite.aesKey), "should serializeLabel and encrypt")
}
func (suite *LabelsSuite) TestEncryptionNonceReUsage() {
foo, err := NewLabelsFromString(suite.fooAsTextEncrypted, suite.aesKey)
suite.NoError(err, "should succeed for valid label text")
serialized := foo.Serialize(false, true, suite.aesKey)
suite.Equal(serialized, suite.fooAsTextEncrypted, "serialized result should be equal")
}
func (suite *LabelsSuite) TestDeserialize() {
foo, err := NewLabelsFromString(suite.fooAsText)
foo, err := NewLabelsFromStringPlain(suite.fooAsText)
suite.NoError(err, "should succeed for valid label text")
suite.Equal(suite.foo, foo, "should reconstruct original label map")
foo, err = NewLabelsFromString(suite.fooAsTextWithQuotes)
foo, err = NewLabelsFromStringPlain(suite.fooAsTextWithQuotes)
suite.NoError(err, "should succeed for valid label text")
suite.Equal(suite.foo, foo, "should reconstruct original label map")
bar, err := NewLabelsFromString(suite.barText)
foo, err = NewLabelsFromString(suite.fooAsTextEncrypted, suite.aesKey)
suite.NoError(err, "should succeed for valid encrypted label text")
for key, val := range suite.foo {
suite.Equal(val, foo[key], "should contains all keys from original label map")
}
foo, err = NewLabelsFromString(suite.fooAsTextWithQuotesEncrypted, suite.aesKey)
suite.NoError(err, "should succeed for valid encrypted label text")
for key, val := range suite.foo {
suite.Equal(val, foo[key], "should contains all keys from original label map")
}
bar, err := NewLabelsFromStringPlain(suite.barText)
suite.NoError(err, "should succeed for valid label text")
suite.Equal(suite.barTextAsMap, bar, "should reconstruct original label map")
noHeritage, err := NewLabelsFromString(suite.noHeritageText)
bar, err = NewLabelsFromString(suite.barText, suite.aesKey)
suite.NoError(err, "should succeed for valid encrypted label text")
suite.Equal(suite.barTextAsMap, bar, "should reconstruct original label map")
noHeritage, err := NewLabelsFromStringPlain(suite.noHeritageText)
suite.Equal(ErrInvalidHeritage, err, "should fail if no heritage is found")
suite.Nil(noHeritage, "should return nil")
wrongHeritage, err := NewLabelsFromString(suite.wrongHeritageText)
wrongHeritage, err := NewLabelsFromStringPlain(suite.wrongHeritageText)
suite.Equal(ErrInvalidHeritage, err, "should fail if wrong heritage is found")
suite.Nil(wrongHeritage, "if error should return nil")
multipleHeritage, err := NewLabelsFromString(suite.multipleHeritageText)
multipleHeritage, err := NewLabelsFromStringPlain(suite.multipleHeritageText)
suite.Equal(ErrInvalidHeritage, err, "should fail if multiple heritage is found")
suite.Nil(multipleHeritage, "if error should return nil")
}

View File

@ -26,7 +26,6 @@ import (
// TargetFilterInterface defines the interface to select matching targets for a specific provider or runtime
type TargetFilterInterface interface {
Match(target string) bool
IsConfigured() bool
}
// TargetNetFilter holds a lists of valid target names
@ -61,11 +60,6 @@ func NewTargetNetFilterWithExclusions(targetFilterNets []string, excludeNets []s
return TargetNetFilter{FilterNets: prepareTargetFilters(targetFilterNets), excludeNets: prepareTargetFilters(excludeNets)}
}
// NewTargetNetFilter returns a new TargetNetFilter given a comma separated list of targets
func NewTargetNetFilter(targetFilterNets []string) TargetNetFilter {
return TargetNetFilter{FilterNets: prepareTargetFilters(targetFilterNets)}
}
// Match checks whether a target can be found in the TargetNetFilter.
func (tf TargetNetFilter) Match(target string) bool {
return matchTargetNetFilter(tf.FilterNets, target, true) && !matchTargetNetFilter(tf.excludeNets, target, false)
@ -89,11 +83,3 @@ func matchTargetNetFilter(filters []*net.IPNet, target string, emptyval bool) bo
return false
}
// IsConfigured returns true if TargetFilter is configured, false otherwise
func (tf TargetNetFilter) IsConfigured() bool {
if len(tf.FilterNets) == 1 {
return tf.FilterNets[0].Network() != ""
}
return len(tf.FilterNets) > 0
}

View File

@ -68,19 +68,6 @@ var targetFilterTests = []targetFilterTest{
},
}
func TestTargetFilterMatch(t *testing.T) {
for i, tt := range targetFilterTests {
if len(tt.exclusions) > 0 {
t.Logf("NewTargetFilter() doesn't support exclusions - skipping test %+v", tt)
continue
}
targetFilter := NewTargetNetFilter(tt.targetFilter)
for _, target := range tt.targets {
assert.Equal(t, tt.expected, targetFilter.Match(target), "should not fail: %v in test-case #%v", target, i)
}
}
}
func TestTargetFilterWithExclusions(t *testing.T) {
for i, tt := range targetFilterTests {
if len(tt.exclusions) == 0 {
@ -107,47 +94,3 @@ func TestMatchTargetFilterReturnsProperEmptyVal(t *testing.T) {
assert.Equal(t, true, matchFilter(emptyFilters, "sometarget.com", true))
assert.Equal(t, false, matchFilter(emptyFilters, "sometarget.com", false))
}
func TestTargetFilterIsConfigured(t *testing.T) {
for _, tt := range []struct {
filters []string
exclude []string
expected bool
}{
{
[]string{""},
[]string{""},
false,
},
{
[]string{" "},
[]string{" "},
false,
},
{
[]string{"", ""},
[]string{""},
false,
},
{
[]string{"10/8"},
[]string{" "},
false,
},
{
[]string{"10.0.0.0/8"},
[]string{" "},
true,
},
{
[]string{" 10.0.0.0/8 "},
[]string{" ignored "},
true,
},
} {
t.Run("test IsConfigured", func(t *testing.T) {
tf := NewTargetNetFilterWithExclusions(tt.filters, tt.exclude)
assert.Equal(t, tt.expected, tf.IsConfigured())
})
}
}

231
go.mod
View File

@ -1,81 +1,83 @@
module sigs.k8s.io/external-dns
go 1.19
go 1.20
require (
cloud.google.com/go/compute/metadata v0.2.3
github.com/Azure/azure-sdk-for-go v66.0.0+incompatible
github.com/Azure/go-autorest/autorest v0.11.28
github.com/Azure/go-autorest/autorest/adal v0.9.21
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
github.com/Azure/go-autorest/autorest v0.11.29
github.com/Azure/go-autorest/autorest/adal v0.9.23
github.com/Azure/go-autorest/autorest/to v0.4.0
github.com/IBM-Cloud/ibm-cloud-cli-sdk v1.0.0
github.com/IBM/go-sdk-core/v5 v5.8.0
github.com/IBM/networking-go-sdk v0.36.0
github.com/StackExchange/dnscontrol/v3 v3.27.1
github.com/F5Networks/k8s-bigip-ctlr/v2 v2.13.0
github.com/IBM-Cloud/ibm-cloud-cli-sdk v1.1.0
github.com/IBM/go-sdk-core/v5 v5.13.4
github.com/IBM/networking-go-sdk v0.42.0
github.com/StackExchange/dnscontrol/v3 v3.31.6
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2
github.com/alecthomas/kingpin v2.2.5+incompatible
github.com/aliyun/alibaba-cloud-sdk-go v1.62.4
github.com/ans-group/sdk-go v1.10.4
github.com/aws/aws-sdk-go v1.44.136
github.com/bodgit/tsig v1.2.0
github.com/alecthomas/kingpin v2.2.6+incompatible
github.com/aliyun/alibaba-cloud-sdk-go v1.62.380
github.com/ans-group/sdk-go v1.16.5
github.com/aws/aws-sdk-go v1.44.285
github.com/bodgit/tsig v1.2.2
github.com/civo/civogo v0.3.14
github.com/cloudflare/cloudflare-go v0.58.1
github.com/cloudflare/cloudflare-go v0.69.0
github.com/cloudfoundry-community/go-cfclient v0.0.0-20190201205600-f136f9222381
github.com/datawire/ambassador v1.6.0
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba
github.com/digitalocean/godo v1.97.0
github.com/dnsimple/dnsimple-go v1.0.1
github.com/exoscale/egoscale v0.97.0
github.com/digitalocean/godo v1.99.0
github.com/dnsimple/dnsimple-go v1.2.0
github.com/exoscale/egoscale v1.19.0
github.com/ffledgling/pdns-go v0.0.0-20180219074714-524e7daccd99
github.com/go-gandi/go-gandi v0.6.0
github.com/google/go-cmp v0.5.9
github.com/gophercloud/gophercloud v0.25.0
github.com/gophercloud/gophercloud v1.4.0
github.com/hooklift/gowsdl v0.5.0
github.com/infobloxopen/infoblox-go-client/v2 v2.1.2-0.20220407114022-6f4c71443168
github.com/infobloxopen/infoblox-go-client/v2 v2.3.0
github.com/linki/instrumented_http v0.3.0
github.com/linode/linodego v1.9.1
github.com/maxatome/go-testdeep v1.12.0
github.com/miekg/dns v1.1.51
github.com/linode/linodego v1.17.0
github.com/maxatome/go-testdeep v1.13.0
github.com/miekg/dns v1.1.55
github.com/nesv/go-dynect v0.6.0
github.com/nic-at/rc0go v1.1.1
github.com/onsi/ginkgo v1.16.5
github.com/openshift/api v0.0.0-20200605231317-fb2a6ca106ae
github.com/openshift/client-go v0.0.0-20200608144219-584632b8fc73
github.com/oracle/oci-go-sdk v24.3.0+incompatible
github.com/ovh/go-ovh v1.1.0
github.com/openshift/api v0.0.0-20230607130528-611114dca681
github.com/openshift/client-go v0.0.0-20230607134213-3cd0021bbee3
github.com/oracle/oci-go-sdk/v65 v65.41.0
github.com/ovh/go-ovh v1.4.1
github.com/pkg/errors v0.9.1
github.com/pluralsh/gqlclient v1.1.6
github.com/projectcontour/contour v1.23.2
github.com/prometheus/client_golang v1.14.0
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.7.0.20210127161313-bd30bebeac4f
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.2
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.599
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.344
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.599
github.com/transip/gotransip/v6 v6.19.0
github.com/ultradns/ultradns-sdk-go v0.0.0-20200616202852-e62052662f60
github.com/vinyldns/go-vinyldns v0.0.0-20200211145900-fe8a3d82e556
github.com/pluralsh/gqlclient v1.3.17
github.com/projectcontour/contour v1.25.0
github.com/prometheus/client_golang v1.16.0
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.17
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.4
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.684
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.684
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/privatedns v1.0.684
github.com/transip/gotransip/v6 v6.20.0
github.com/ultradns/ultradns-sdk-go v1.3.7
github.com/vinyldns/go-vinyldns v0.9.16
github.com/vultr/govultr/v2 v2.17.2
go.etcd.io/etcd/api/v3 v3.5.5
go.etcd.io/etcd/client/v3 v3.5.5
go.etcd.io/etcd/api/v3 v3.5.9
go.etcd.io/etcd/client/v3 v3.5.9
go.uber.org/ratelimit v0.2.0
golang.org/x/net v0.7.0
golang.org/x/oauth2 v0.5.0
golang.org/x/sync v0.1.0
google.golang.org/api v0.110.0
gopkg.in/ns1/ns1-go.v2 v2.7.4
golang.org/x/net v0.11.0
golang.org/x/oauth2 v0.9.0
golang.org/x/sync v0.3.0
golang.org/x/time v0.3.0
google.golang.org/api v0.128.0
gopkg.in/ns1/ns1-go.v2 v2.7.6
gopkg.in/yaml.v2 v2.4.0
istio.io/api v0.0.0-20210128181506-0c4b8e54850f
istio.io/client-go v0.0.0-20210128182905-ee2edd059e02
k8s.io/api v0.26.0
k8s.io/apimachinery v0.26.0
k8s.io/client-go v0.26.0
sigs.k8s.io/gateway-api v0.6.0
istio.io/api v0.0.0-20230524015941-fa6c5f7916bf
istio.io/client-go v1.18.0
k8s.io/api v0.27.3
k8s.io/apimachinery v0.27.3
k8s.io/client-go v0.27.3
sigs.k8s.io/gateway-api v0.7.1
)
require (
cloud.google.com/go/compute v1.18.0 // indirect
cloud.google.com/go/compute v1.19.3 // indirect
code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
@ -83,66 +85,68 @@ require (
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Masterminds/semver v1.4.2 // indirect
github.com/Yamashou/gqlgenc v0.11.0 // indirect
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 // indirect
github.com/alecthomas/colour v0.1.0 // indirect
github.com/alecthomas/repr v0.0.0-20200325044227-4184120f674c // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 // indirect
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
github.com/ans-group/go-durationstring v1.2.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/coreos/go-semver v0.3.0 // indirect
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/deepmap/oapi-codegen v1.9.1 // indirect
github.com/emicklei/go-restful/v3 v3.9.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/emicklei/go-restful/v3 v3.10.2 // indirect
github.com/evanphx/json-patch v5.6.0+incompatible // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/frankban/quicktest v1.14.4 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-openapi/errors v0.19.8 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect
github.com/go-openapi/strfmt v0.20.2 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-openapi/errors v0.20.3 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.1 // indirect
github.com/go-openapi/strfmt v0.21.5 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.13.0 // indirect
github.com/go-resty/resty/v2 v2.7.0 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/gofrs/uuid v4.0.0+incompatible // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.4 // indirect
github.com/googleapis/gax-go/v2 v2.10.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.2 // indirect
github.com/hashicorp/go-uuid v1.0.2 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/hashicorp/go-retryablehttp v0.7.3 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/imdario/mergo v0.3.15 // indirect
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
github.com/jcmturner/gofork v1.0.0 // indirect
github.com/jcmturner/gofork v1.7.6 // indirect
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
github.com/jcmturner/gokrb5/v8 v8.4.2 // indirect
github.com/jcmturner/gokrb5/v8 v8.4.3 // indirect
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
github.com/jinzhu/copier v0.3.5 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/leodido/go-urn v1.2.3 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
@ -154,51 +158,56 @@ require (
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/peterhellberg/link v1.1.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.43.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/schollz/progressbar/v3 v3.8.6 // indirect
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect
github.com/smartystreets/gunit v1.3.4 // indirect
github.com/sony/gobreaker v0.5.0 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.15.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/terra-farm/udnssdk v1.3.5 // indirect
github.com/vektah/gqlparser/v2 v2.5.0 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.5 // indirect
go.mongodb.org/mongo-driver v1.5.1 // indirect
github.com/vektah/gqlparser/v2 v2.5.1 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.9 // indirect
go.mongodb.org/mongo-driver v1.11.3 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.19.1 // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/mod v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/tools v0.3.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.10.0 // indirect
golang.org/x/mod v0.10.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/term v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
golang.org/x/tools v0.8.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc // indirect
google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/grpc v1.55.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/go-playground/validator.v9 v9.31.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/resty.v1 v1.12.0 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
istio.io/gogo-genproto v0.0.0-20190930162913-45029607206a // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
k8s.io/klog/v2 v2.100.1 // indirect
k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect
k8s.io/utils v0.0.0-20230505201702-9f6742963106 // indirect
moul.io/http2curl v1.0.0 // indirect
sigs.k8s.io/controller-runtime v0.12.1 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/controller-runtime v0.14.6 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace k8s.io/klog/v2 => github.com/Raffo/knolog v0.0.0-20211016155154-e4d5e0cc970a

Some files were not shown because too many files have changed in this diff Show More