mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2026-05-05 06:36:11 +02:00
Merge remote-tracking branch 'remote/master' into target_annotation_for_service
This commit is contained in:
commit
9bbebf6c07
@ -25,7 +25,7 @@ RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.18
|
||||
|
||||
RUN apk update && apk add "libcrypto3>=3.0.8-r1" "libssl3>=3.0.8-r1" && rm -rf /var/cache/apt/*
|
||||
|
||||
|
||||
@ -336,7 +336,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 {
|
||||
|
||||
@ -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"
|
||||
|
||||
37
docs/faq.md
37
docs/faq.md
@ -257,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?
|
||||
|
||||
|
||||
@ -13,3 +13,68 @@ The controller will try to create the "new format" TXT records if they are not p
|
||||
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.
|
||||
|
||||
### Encryption of TXT Records
|
||||
TXT records may contain sensitive information, such as the internal ingress name or namespace, which attackers could exploit to gather information about your infrastructure.
|
||||
By encrypting TXT records, you can protect this information from unauthorized access. It is strongly recommended to encrypt all TXT records to prevent potential security breaches.
|
||||
|
||||
To enable encryption of TXT records, you can use the following parameters:
|
||||
- `--txt-encrypt-enabled=true`
|
||||
- `--txt-encrypt-aes-key=32bytesKey` (used for AES-256-GCM encryption and should be exactly 32 bytes)
|
||||
|
||||
Note that the key used for encryption should be a secure key and properly managed to ensure the security of your TXT records.
|
||||
|
||||
### Generating TXT encryption AES 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 Encrypt/Decrypt TXT Records
|
||||
|
||||
In some cases, you may need to edit labels generated by External-DNS, and in such cases, you can use simple Golang code to do that.
|
||||
|
||||
```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)
|
||||
}
|
||||
```
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -294,8 +294,6 @@ apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: nginx
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
@ -595,8 +593,6 @@ apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: nginx
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -213,7 +213,7 @@ 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
|
||||
@ -241,7 +241,7 @@ 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.4
|
||||
name: external-dns-public
|
||||
@ -251,7 +251,7 @@ 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
|
||||
@ -279,7 +279,7 @@ 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.4
|
||||
name: external-dns-private
|
||||
@ -287,20 +287,19 @@ spec:
|
||||
|
||||
## 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:
|
||||
|
||||
@ -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:
|
||||
|
||||
134
endpoint/crypto.go
Normal file
134
endpoint/crypto.go
Normal 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
58
endpoint/crypto_test.go
Normal 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.")
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
||||
3
main.go
3
main.go
@ -114,6 +114,7 @@ func main() {
|
||||
Namespace: cfg.Namespace,
|
||||
AnnotationFilter: cfg.AnnotationFilter,
|
||||
LabelFilter: labelSelector,
|
||||
IngressClassNames: cfg.IngressClassNames,
|
||||
FQDNTemplate: cfg.FQDNTemplate,
|
||||
CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation,
|
||||
IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation,
|
||||
@ -382,7 +383,7 @@ func main() {
|
||||
case "noop":
|
||||
r, err = registry.NewNoopRegistry(p)
|
||||
case "txt":
|
||||
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes)
|
||||
r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.TXTEncryptEnabled, []byte(cfg.TXTEncryptAESKey))
|
||||
case "aws-sd":
|
||||
r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID)
|
||||
default:
|
||||
|
||||
@ -54,6 +54,7 @@ type Config struct {
|
||||
Namespace string
|
||||
AnnotationFilter string
|
||||
LabelFilter string
|
||||
IngressClassNames []string
|
||||
FQDNTemplate string
|
||||
CombineFQDNAndAnnotation bool
|
||||
IgnoreHostnameAnnotation bool
|
||||
@ -147,6 +148,8 @@ type Config struct {
|
||||
TXTOwnerID string
|
||||
TXTPrefix string
|
||||
TXTSuffix string
|
||||
TXTEncryptEnabled bool
|
||||
TXTEncryptAESKey string
|
||||
Interval time.Duration
|
||||
MinEventSyncInterval time.Duration
|
||||
Once bool
|
||||
@ -216,6 +219,7 @@ var defaultConfig = &Config{
|
||||
Namespace: "",
|
||||
AnnotationFilter: "",
|
||||
LabelFilter: labels.Everything().String(),
|
||||
IngressClassNames: nil,
|
||||
FQDNTemplate: "",
|
||||
CombineFQDNAndAnnotation: false,
|
||||
IgnoreHostnameAnnotation: false,
|
||||
@ -295,6 +299,8 @@ var defaultConfig = &Config{
|
||||
TXTCacheInterval: 0,
|
||||
TXTWildcardReplacement: "",
|
||||
MinEventSyncInterval: 5 * time.Second,
|
||||
TXTEncryptEnabled: false,
|
||||
TXTEncryptAESKey: "",
|
||||
Interval: time.Minute,
|
||||
Once: false,
|
||||
DryRun: false,
|
||||
@ -413,6 +419,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace)
|
||||
app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter)
|
||||
app.Flag("label-filter", "Filter sources managed by external-dns via label selector when listing all resources; currently supported by source types CRD, ingress, service and openshift-route").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter)
|
||||
app.Flag("ingress-class", "Require an ingress to have this class name (defaults to any class; specify multiple times to allow more than one class)").StringsVar(&cfg.IngressClassNames)
|
||||
app.Flag("fqdn-template", "A templated string that's used to generate DNS names from sources that don't define a hostname themselves, or to add a hostname suffix when paired with the fake source (optional). Accepts comma separated list for multiple global FQDN.").Default(defaultConfig.FQDNTemplate).StringVar(&cfg.FQDNTemplate)
|
||||
app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation)
|
||||
app.Flag("ignore-hostname-annotation", "Ignore hostname annotation when generating DNS names, valid only when using fqdn-template is set (optional, default: false)").BoolVar(&cfg.IgnoreHostnameAnnotation)
|
||||
@ -570,6 +577,8 @@ func (cfg *Config) ParseFlags(args []string) error {
|
||||
app.Flag("txt-prefix", "When using the TXT registry, a custom string that's prefixed to each ownership DNS record (optional). Could contain record type template like '%{record_type}-prefix-'. Mutual exclusive with txt-suffix!").Default(defaultConfig.TXTPrefix).StringVar(&cfg.TXTPrefix)
|
||||
app.Flag("txt-suffix", "When using the TXT registry, a custom string that's suffixed to the host portion of each ownership DNS record (optional). Could contain record type template like '-%{record_type}-suffix'. Mutual exclusive with txt-prefix!").Default(defaultConfig.TXTSuffix).StringVar(&cfg.TXTSuffix)
|
||||
app.Flag("txt-wildcard-replacement", "When using the TXT registry, a custom string that's used instead of an asterisk for TXT records corresponding to wildcard DNS records (optional)").Default(defaultConfig.TXTWildcardReplacement).StringVar(&cfg.TXTWildcardReplacement)
|
||||
app.Flag("txt-encrypt-enabled", "When using the TXT registry, set if TXT records should be encrypted before stored (default: disabled)").BoolVar(&cfg.TXTEncryptEnabled)
|
||||
app.Flag("txt-encrypt-aes-key", "When using the TXT registry, set TXT record decryption and encryption 32 byte aes key (required when --txt-encrypt=true)").Default(defaultConfig.TXTEncryptAESKey).StringVar(&cfg.TXTEncryptAESKey)
|
||||
|
||||
// Flags related to the main control loop
|
||||
app.Flag("txt-cache-interval", "The interval between cache synchronizations in duration format (default: disabled)").Default(defaultConfig.TXTCacheInterval.String()).DurationVar(&cfg.TXTCacheInterval)
|
||||
|
||||
@ -509,7 +509,7 @@ func (p *AWSSDProvider) DeleteService(service *sd.Service) error {
|
||||
// convert ownerID string to service description format
|
||||
label := endpoint.NewLabels()
|
||||
label[endpoint.OwnerLabelKey] = p.ownerID
|
||||
label[endpoint.AWSSDDescriptionLabel] = label.Serialize(false)
|
||||
label[endpoint.AWSSDDescriptionLabel] = label.SerializePlain(false)
|
||||
|
||||
if strings.HasPrefix(aws.StringValue(service.Description), label[endpoint.AWSSDDescriptionLabel]) {
|
||||
log.Infof("Deleting service \"%s\"", *service.Name)
|
||||
|
||||
@ -55,7 +55,7 @@ func (sdr *AWSSDRegistry) Records(ctx context.Context) ([]*endpoint.Endpoint, er
|
||||
}
|
||||
|
||||
for _, record := range records {
|
||||
labels, err := endpoint.NewLabelsFromString(record.Labels[endpoint.AWSSDDescriptionLabel])
|
||||
labels, err := endpoint.NewLabelsFromStringPlain(record.Labels[endpoint.AWSSDDescriptionLabel])
|
||||
if err != nil {
|
||||
// if we fail to parse the output then simply assume the endpoint is not managed by any instance of External DNS
|
||||
record.Labels = endpoint.NewLabels()
|
||||
@ -96,7 +96,7 @@ func (sdr *AWSSDRegistry) updateLabels(endpoints []*endpoint.Endpoint) {
|
||||
ep.Labels = make(map[string]string)
|
||||
}
|
||||
ep.Labels[endpoint.OwnerLabelKey] = sdr.ownerID
|
||||
ep.Labels[endpoint.AWSSDDescriptionLabel] = ep.Labels.Serialize(false)
|
||||
ep.Labels[endpoint.AWSSDDescriptionLabel] = ep.Labels.SerializePlain(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -52,15 +52,27 @@ type TXTRegistry struct {
|
||||
|
||||
// missingTXTRecords stores TXT records which are missing after the migration to the new format
|
||||
missingTXTRecords []*endpoint.Endpoint
|
||||
|
||||
// encrypt text records
|
||||
txtEncryptEnabled bool
|
||||
txtEncryptAESKey []byte
|
||||
}
|
||||
|
||||
const keySuffixAAAA = ":AAAA"
|
||||
|
||||
// NewTXTRegistry returns new TXTRegistry object
|
||||
func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, cacheInterval time.Duration, txtWildcardReplacement string, managedRecordTypes []string) (*TXTRegistry, error) {
|
||||
func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID string, cacheInterval time.Duration, txtWildcardReplacement string, managedRecordTypes []string, txtEncryptEnabled bool, txtEncryptAESKey []byte) (*TXTRegistry, error) {
|
||||
if ownerID == "" {
|
||||
return nil, errors.New("owner id cannot be empty")
|
||||
}
|
||||
if len(txtEncryptAESKey) == 0 {
|
||||
txtEncryptAESKey = nil
|
||||
} else if len(txtEncryptAESKey) != 32 {
|
||||
return nil, errors.New("the AES Encryption key must have a length of 32 bytes")
|
||||
}
|
||||
if txtEncryptEnabled && txtEncryptAESKey == nil {
|
||||
return nil, errors.New("the AES Encryption key must be set when TXT record encryption is enabled")
|
||||
}
|
||||
|
||||
if len(txtPrefix) > 0 && len(txtSuffix) > 0 {
|
||||
return nil, errors.New("txt-prefix and txt-suffix are mutual exclusive")
|
||||
@ -75,6 +87,8 @@ func NewTXTRegistry(provider provider.Provider, txtPrefix, txtSuffix, ownerID st
|
||||
cacheInterval: cacheInterval,
|
||||
wildcardReplacement: txtWildcardReplacement,
|
||||
managedRecordTypes: managedRecordTypes,
|
||||
txtEncryptEnabled: txtEncryptEnabled,
|
||||
txtEncryptAESKey: txtEncryptAESKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -114,7 +128,7 @@ func (im *TXTRegistry) Records(ctx context.Context) ([]*endpoint.Endpoint, error
|
||||
continue
|
||||
}
|
||||
// We simply assume that TXT records for the registry will always have only one target.
|
||||
labels, err := endpoint.NewLabelsFromString(record.Targets[0])
|
||||
labels, err := endpoint.NewLabelsFromString(record.Targets[0], im.txtEncryptAESKey)
|
||||
if err == endpoint.ErrInvalidHeritage {
|
||||
// if no heritage is found or it is invalid
|
||||
// case when value of txt record cannot be identified
|
||||
@ -205,7 +219,7 @@ func (im *TXTRegistry) generateTXTRecord(r *endpoint.Endpoint) []*endpoint.Endpo
|
||||
|
||||
if r.RecordType != endpoint.RecordTypeAAAA {
|
||||
// old TXT record format
|
||||
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), endpoint.RecordTypeTXT, r.Labels.Serialize(true))
|
||||
txt := endpoint.NewEndpoint(im.mapper.toTXTName(r.DNSName), endpoint.RecordTypeTXT, r.Labels.Serialize(true, im.txtEncryptEnabled, im.txtEncryptAESKey))
|
||||
if txt != nil {
|
||||
txt.WithSetIdentifier(r.SetIdentifier)
|
||||
txt.Labels[endpoint.OwnedRecordLabelKey] = r.DNSName
|
||||
@ -213,9 +227,8 @@ func (im *TXTRegistry) generateTXTRecord(r *endpoint.Endpoint) []*endpoint.Endpo
|
||||
endpoints = append(endpoints, txt)
|
||||
}
|
||||
}
|
||||
|
||||
// new TXT record format (containing record type)
|
||||
txtNew := endpoint.NewEndpoint(im.mapper.toNewTXTName(r.DNSName, r.RecordType), endpoint.RecordTypeTXT, r.Labels.Serialize(true))
|
||||
txtNew := endpoint.NewEndpoint(im.mapper.toNewTXTName(r.DNSName, r.RecordType), endpoint.RecordTypeTXT, r.Labels.Serialize(true, im.txtEncryptEnabled, im.txtEncryptAESKey))
|
||||
if txtNew != nil {
|
||||
txtNew.WithSetIdentifier(r.SetIdentifier)
|
||||
txtNew.Labels[endpoint.OwnedRecordLabelKey] = r.DNSName
|
||||
|
||||
@ -46,20 +46,20 @@ func TestTXTRegistry(t *testing.T) {
|
||||
|
||||
func testTXTRegistryNew(t *testing.T) {
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
_, err := NewTXTRegistry(p, "txt", "", "", time.Hour, "", []string{})
|
||||
_, err := NewTXTRegistry(p, "txt", "", "", time.Hour, "", []string{}, false, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
_, err = NewTXTRegistry(p, "", "txt", "", time.Hour, "", []string{})
|
||||
_, err = NewTXTRegistry(p, "", "txt", "", time.Hour, "", []string{}, false, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
r, err := NewTXTRegistry(p, "txt", "", "owner", time.Hour, "", []string{})
|
||||
r, err := NewTXTRegistry(p, "txt", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, p, r.provider)
|
||||
|
||||
r, err = NewTXTRegistry(p, "", "txt", "owner", time.Hour, "", []string{})
|
||||
r, err = NewTXTRegistry(p, "", "txt", "owner", time.Hour, "", []string{}, false, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = NewTXTRegistry(p, "txt", "txt", "owner", time.Hour, "", []string{})
|
||||
_, err = NewTXTRegistry(p, "txt", "txt", "owner", time.Hour, "", []string{}, false, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
_, ok := r.mapper.(affixNameMapper)
|
||||
@ -67,7 +67,17 @@ func testTXTRegistryNew(t *testing.T) {
|
||||
assert.Equal(t, "owner", r.ownerID)
|
||||
assert.Equal(t, p, r.provider)
|
||||
|
||||
r, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{})
|
||||
aesKey := []byte(";k&l)nUC/33:{?d{3)54+,AD?]SX%yh^")
|
||||
_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, false, aesKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, true, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
r, err = NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, true, aesKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok = r.mapper.(affixNameMapper)
|
||||
@ -203,13 +213,13 @@ func testTXTRegistryRecordsPrefixed(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{})
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{}, false, nil)
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
|
||||
// Ensure prefix is case-insensitive
|
||||
r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour, "", []string{})
|
||||
r, _ = NewTXTRegistry(p, "TxT.", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
records, _ = r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpointLabels(records, expectedRecords))
|
||||
@ -328,13 +338,13 @@ func testTXTRegistryRecordsSuffixed(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "", []string{})
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "", []string{}, false, nil)
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
|
||||
// Ensure prefix is case-insensitive
|
||||
r, _ = NewTXTRegistry(p, "", "-TxT", "owner", time.Hour, "", []string{})
|
||||
r, _ = NewTXTRegistry(p, "", "-TxT", "owner", time.Hour, "", []string{}, false, nil)
|
||||
records, _ = r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpointLabels(records, expectedRecords))
|
||||
@ -429,7 +439,7 @@ func testTXTRegistryRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
records, _ := r.Records(ctx)
|
||||
|
||||
assert.True(t, testutils.SameEndpoints(records, expectedRecords))
|
||||
@ -472,7 +482,7 @@ func testTXTRegistryApplyChangesWithPrefix(t *testing.T) {
|
||||
newEndpointWithOwner("txt.cname-multiple.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{})
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -561,7 +571,7 @@ func testTXTRegistryApplyChangesWithTemplatedPrefix(t *testing.T) {
|
||||
p.ApplyChanges(ctx, &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "prefix%{record_type}.", "", "owner", time.Hour, "", []string{})
|
||||
r, _ := NewTXTRegistry(p, "prefix%{record_type}.", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
|
||||
@ -605,7 +615,7 @@ func testTXTRegistryApplyChangesWithTemplatedSuffix(t *testing.T) {
|
||||
p.OnApplyChanges = func(ctx context.Context, got *plan.Changes) {
|
||||
assert.Equal(t, ctxEndpoints, ctx.Value(provider.RecordsContextKey))
|
||||
}
|
||||
r, _ := NewTXTRegistry(p, "", "-%{record_type}suffix", "owner", time.Hour, "", []string{})
|
||||
r, _ := NewTXTRegistry(p, "", "-%{record_type}suffix", "owner", time.Hour, "", []string{}, false, nil)
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
newEndpointWithOwnerResource("new-record-1.test-zone.example.org", "new-loadbalancer-1.lb.com", endpoint.RecordTypeCNAME, "", "ingress/default/my-ingress"),
|
||||
@ -671,7 +681,7 @@ func testTXTRegistryApplyChangesWithSuffix(t *testing.T) {
|
||||
newEndpointWithOwner("cname-multiple-txt.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, "").WithSetIdentifier("test-set-2"),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "wildcard", []string{})
|
||||
r, _ := NewTXTRegistry(p, "", "-txt", "owner", time.Hour, "wildcard", []string{}, false, nil)
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -775,7 +785,7 @@ func testTXTRegistryApplyChangesNoPrefix(t *testing.T) {
|
||||
newEndpointWithOwner("cname-foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -941,7 +951,7 @@ func testTXTRegistryMissingRecordsNoPrefix(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS}, false, nil)
|
||||
records, _ := r.Records(ctx)
|
||||
missingRecords := r.MissingRecords()
|
||||
|
||||
@ -1045,7 +1055,7 @@ func testTXTRegistryMissingRecordsWithPrefix(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS})
|
||||
r, _ := NewTXTRegistry(p, "txt.", "", "owner", time.Hour, "wc", []string{endpoint.RecordTypeCNAME, endpoint.RecordTypeA, endpoint.RecordTypeNS}, false, nil)
|
||||
records, _ := r.Records(ctx)
|
||||
missingRecords := r.MissingRecords()
|
||||
|
||||
@ -1199,7 +1209,7 @@ func TestNewTXTScheme(t *testing.T) {
|
||||
newEndpointWithOwner("cname-foobar.test-zone.example.org", "\"heritage=external-dns,external-dns/owner=owner\"", endpoint.RecordTypeTXT, ""),
|
||||
},
|
||||
})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
|
||||
changes := &plan.Changes{
|
||||
Create: []*endpoint.Endpoint{
|
||||
@ -1275,7 +1285,7 @@ func TestGenerateTXT(t *testing.T) {
|
||||
}
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
gotTXT := r.generateTXTRecord(record)
|
||||
assert.Equal(t, expectedTXT, gotTXT)
|
||||
}
|
||||
@ -1294,7 +1304,7 @@ func TestGenerateTXTForAAAA(t *testing.T) {
|
||||
}
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
gotTXT := r.generateTXTRecord(record)
|
||||
assert.Equal(t, expectedTXT, gotTXT)
|
||||
}
|
||||
@ -1311,7 +1321,7 @@ func TestFailGenerateTXT(t *testing.T) {
|
||||
expectedTXT := []*endpoint.Endpoint{}
|
||||
p := inmemory.NewInMemoryProvider()
|
||||
p.CreateZone(testZone)
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{})
|
||||
r, _ := NewTXTRegistry(p, "", "", "owner", time.Hour, "", []string{}, false, nil)
|
||||
gotTXT := r.generateTXTRecord(cnameRecord)
|
||||
assert.Equal(t, expectedTXT, gotTXT)
|
||||
}
|
||||
|
||||
@ -157,11 +157,13 @@ func legacyEndpointsFromDNSControllerNodePortService(svc *v1.Service, sc *servic
|
||||
continue
|
||||
}
|
||||
for _, address := range node.Status.Addresses {
|
||||
if address.Type == v1.NodeExternalIP && isExternal {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, address.Address))
|
||||
recordType := suitableType(address.Address)
|
||||
// IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well.
|
||||
if isExternal && (address.Type == v1.NodeExternalIP || (address.Type == v1.NodeInternalIP && recordType == endpoint.RecordTypeAAAA)) {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, recordType, address.Address))
|
||||
}
|
||||
if address.Type == v1.NodeInternalIP && isInternal {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, endpoint.RecordTypeA, address.Address))
|
||||
if isInternal && address.Type == v1.NodeInternalIP {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(hostname, recordType, address.Address))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ package source
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -26,6 +27,7 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
networkv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
netinformers "k8s.io/client-go/informers/networking/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
@ -43,6 +45,8 @@ const (
|
||||
// Possible values for the ingress-hostname-source annotation
|
||||
IngressHostnameSourceAnnotationOnlyValue = "annotation-only"
|
||||
IngressHostnameSourceDefinedHostsOnlyValue = "defined-hosts-only"
|
||||
|
||||
IngressClassAnnotationKey = "kubernetes.io/ingress.class"
|
||||
)
|
||||
|
||||
// ingressSource is an implementation of Source for Kubernetes ingress objects.
|
||||
@ -53,6 +57,7 @@ type ingressSource struct {
|
||||
client kubernetes.Interface
|
||||
namespace string
|
||||
annotationFilter string
|
||||
ingressClassNames []string
|
||||
fqdnTemplate *template.Template
|
||||
combineFQDNAnnotation bool
|
||||
ignoreHostnameAnnotation bool
|
||||
@ -63,12 +68,27 @@ type ingressSource struct {
|
||||
}
|
||||
|
||||
// NewIngressSource creates a new ingressSource with the given config.
|
||||
func NewIngressSource(ctx context.Context, kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool, labelSelector labels.Selector) (Source, error) {
|
||||
func NewIngressSource(ctx context.Context, kubeClient kubernetes.Interface, namespace, annotationFilter string, fqdnTemplate string, combineFqdnAnnotation bool, ignoreHostnameAnnotation bool, ignoreIngressTLSSpec bool, ignoreIngressRulesSpec bool, labelSelector labels.Selector, ingressClassNames []string) (Source, error) {
|
||||
tmpl, err := parseTemplate(fqdnTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ensure that ingress class is only set in either the ingressClassNames or
|
||||
// annotationFilter but not both
|
||||
if ingressClassNames != nil && annotationFilter != "" {
|
||||
selector, err := getLabelSelector(annotationFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
requirements, _ := selector.Requirements()
|
||||
for _, requirement := range requirements {
|
||||
if requirement.Key() == "kubernetes.io/ingress.class" {
|
||||
return nil, errors.New("--ingress-class is mutually exclusive with the kubernetes.io/ingress.class annotation filter")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Use shared informer to listen for add/update/delete of ingresses in the specified namespace.
|
||||
// Set resync period to 0, to prevent processing when nothing has changed.
|
||||
informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0, kubeinformers.WithNamespace(namespace))
|
||||
@ -93,6 +113,7 @@ func NewIngressSource(ctx context.Context, kubeClient kubernetes.Interface, name
|
||||
client: kubeClient,
|
||||
namespace: namespace,
|
||||
annotationFilter: annotationFilter,
|
||||
ingressClassNames: ingressClassNames,
|
||||
fqdnTemplate: tmpl,
|
||||
combineFQDNAnnotation: combineFqdnAnnotation,
|
||||
ignoreHostnameAnnotation: ignoreHostnameAnnotation,
|
||||
@ -116,6 +137,11 @@ func (sc *ingressSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ingresses, err = sc.filterByIngressClass(ingresses)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoints := []*endpoint.Endpoint{}
|
||||
|
||||
for _, ing := range ingresses {
|
||||
@ -210,6 +236,50 @@ func (sc *ingressSource) filterByAnnotations(ingresses []*networkv1.Ingress) ([]
|
||||
return filteredList, nil
|
||||
}
|
||||
|
||||
// filterByIngressClass filters a list of ingresses based on a required ingress
|
||||
// class
|
||||
func (sc *ingressSource) filterByIngressClass(ingresses []*networkv1.Ingress) ([]*networkv1.Ingress, error) {
|
||||
// if no class filter is specified then there's nothing to do
|
||||
if len(sc.ingressClassNames) == 0 {
|
||||
return ingresses, nil
|
||||
}
|
||||
|
||||
classNameReq, err := labels.NewRequirement(IngressClassAnnotationKey, selection.In, sc.ingressClassNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
selector := labels.NewSelector()
|
||||
selector = selector.Add(*classNameReq)
|
||||
|
||||
filteredList := []*networkv1.Ingress{}
|
||||
|
||||
for _, ingress := range ingresses {
|
||||
var matched = false
|
||||
|
||||
for _, nameFilter := range sc.ingressClassNames {
|
||||
if ingress.Spec.IngressClassName != nil && len(*ingress.Spec.IngressClassName) > 0 {
|
||||
if nameFilter == *ingress.Spec.IngressClassName {
|
||||
matched = true
|
||||
}
|
||||
} else if matchLabelSelector(selector, ingress.Annotations) {
|
||||
matched = true
|
||||
}
|
||||
|
||||
if matched {
|
||||
filteredList = append(filteredList, ingress)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !matched {
|
||||
log.Debugf("Discarding ingress %s/%s because it does not match required ingress classes %v", ingress.Namespace, ingress.Name, sc.ingressClassNames)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredList, nil
|
||||
}
|
||||
|
||||
func (sc *ingressSource) setResourceLabel(ingress *networkv1.Ingress, endpoints []*endpoint.Endpoint) {
|
||||
for _, ep := range endpoints {
|
||||
ep.Labels[endpoint.ResourceLabelKey] = fmt.Sprintf("ingress/%s/%s", ingress.Namespace, ingress.Name)
|
||||
|
||||
@ -65,6 +65,7 @@ func (suite *IngressSuite) SetupTest() {
|
||||
false,
|
||||
false,
|
||||
labels.Everything(),
|
||||
[]string{},
|
||||
)
|
||||
suite.NoError(err, "should initialize ingress source")
|
||||
}
|
||||
@ -101,6 +102,7 @@ func TestNewIngressSource(t *testing.T) {
|
||||
fqdnTemplate string
|
||||
combineFQDNAndAnnotation bool
|
||||
expectError bool
|
||||
ingressClassNames []string
|
||||
}{
|
||||
{
|
||||
title: "invalid template",
|
||||
@ -132,6 +134,17 @@ func TestNewIngressSource(t *testing.T) {
|
||||
expectError: false,
|
||||
annotationFilter: "kubernetes.io/ingress.class=nginx",
|
||||
},
|
||||
{
|
||||
title: "non-empty ingress class name list",
|
||||
expectError: false,
|
||||
ingressClassNames: []string{"internal", "external"},
|
||||
},
|
||||
{
|
||||
title: "ingress class name and annotation filter jointly specified",
|
||||
expectError: true,
|
||||
ingressClassNames: []string{"internal", "external"},
|
||||
annotationFilter: "kubernetes.io/ingress.class=nginx",
|
||||
},
|
||||
} {
|
||||
ti := ti
|
||||
t.Run(ti.title, func(t *testing.T) {
|
||||
@ -148,6 +161,7 @@ func TestNewIngressSource(t *testing.T) {
|
||||
false,
|
||||
false,
|
||||
labels.Everything(),
|
||||
ti.ingressClassNames,
|
||||
)
|
||||
if ti.expectError {
|
||||
assert.Error(t, err)
|
||||
@ -374,6 +388,7 @@ func testIngressEndpoints(t *testing.T) {
|
||||
ignoreIngressTLSSpec bool
|
||||
ignoreIngressRulesSpec bool
|
||||
ingressLabelSelector labels.Selector
|
||||
ingressClassNames []string
|
||||
}{
|
||||
{
|
||||
title: "no ingress",
|
||||
@ -1220,6 +1235,116 @@ func testIngressEndpoints(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "ingressClassName filtering",
|
||||
targetNamespace: "",
|
||||
ingressClassNames: []string{"public", "dmz"},
|
||||
ingressItems: []fakeIngress{
|
||||
{
|
||||
name: "none",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"none.example.org"}},
|
||||
ips: []string{"1.0.0.0"},
|
||||
},
|
||||
{
|
||||
name: "fake-public",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"example.org"}},
|
||||
ips: []string{"1.2.3.4"},
|
||||
ingressClassName: "public", // match
|
||||
},
|
||||
{
|
||||
name: "fake-internal",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"int.example.org"}},
|
||||
ips: []string{"2.3.4.5"},
|
||||
ingressClassName: "internal",
|
||||
},
|
||||
{
|
||||
name: "fake-dmz",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"dmz.example.org"}},
|
||||
ips: []string{"3.4.5.6"},
|
||||
ingressClassName: "dmz", // match
|
||||
},
|
||||
{
|
||||
name: "annotated-dmz",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"annodmz.example.org"}},
|
||||
ips: []string{"4.5.6.7"},
|
||||
annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "dmz", // match
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fake-internal-annotated-dmz",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"int-annodmz.example.org"}},
|
||||
ips: []string{"5.6.7.8"},
|
||||
annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "dmz", // match but ignored (non-empty ingressClassName)
|
||||
},
|
||||
ingressClassName: "internal",
|
||||
},
|
||||
{
|
||||
name: "fake-dmz-annotated-internal",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"dmz-annoint.example.org"}},
|
||||
ips: []string{"6.7.8.9"},
|
||||
annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "internal",
|
||||
},
|
||||
ingressClassName: "dmz", // match
|
||||
},
|
||||
{
|
||||
name: "empty-annotated-dmz",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"empty-annotdmz.example.org"}},
|
||||
ips: []string{"7.8.9.0"},
|
||||
annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "dmz", // match (empty ingressClassName)
|
||||
},
|
||||
ingressClassName: "",
|
||||
},
|
||||
{
|
||||
name: "empty-annotated-internal",
|
||||
namespace: namespace,
|
||||
tlsdnsnames: [][]string{{"empty-annotint.example.org"}},
|
||||
ips: []string{"8.9.0.1"},
|
||||
annotations: map[string]string{
|
||||
"kubernetes.io/ingress.class": "internal",
|
||||
},
|
||||
ingressClassName: "",
|
||||
},
|
||||
},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "example.org",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"1.2.3.4"},
|
||||
},
|
||||
{
|
||||
DNSName: "dmz.example.org",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"3.4.5.6"},
|
||||
},
|
||||
{
|
||||
DNSName: "annodmz.example.org",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"4.5.6.7"},
|
||||
},
|
||||
{
|
||||
DNSName: "dmz-annoint.example.org",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"6.7.8.9"},
|
||||
},
|
||||
{
|
||||
DNSName: "empty-annotdmz.example.org",
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
Targets: endpoint.Targets{"7.8.9.0"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ingressLabelSelector: labels.SelectorFromSet(labels.Set{"app": "web-external"}),
|
||||
title: "ingress with matching labels",
|
||||
@ -1283,6 +1408,7 @@ func testIngressEndpoints(t *testing.T) {
|
||||
ti.ignoreIngressTLSSpec,
|
||||
ti.ignoreIngressRulesSpec,
|
||||
ti.ingressLabelSelector,
|
||||
ti.ingressClassNames,
|
||||
)
|
||||
// Informer cache has all of the ingresses. Retrieve and validate their endpoints.
|
||||
res, err := source.Endpoints(context.Background())
|
||||
@ -1298,14 +1424,15 @@ func testIngressEndpoints(t *testing.T) {
|
||||
|
||||
// ingress specific helper functions
|
||||
type fakeIngress struct {
|
||||
dnsnames []string
|
||||
tlsdnsnames [][]string
|
||||
ips []string
|
||||
hostnames []string
|
||||
namespace string
|
||||
name string
|
||||
annotations map[string]string
|
||||
labels map[string]string
|
||||
dnsnames []string
|
||||
tlsdnsnames [][]string
|
||||
ips []string
|
||||
hostnames []string
|
||||
namespace string
|
||||
name string
|
||||
annotations map[string]string
|
||||
labels map[string]string
|
||||
ingressClassName string
|
||||
}
|
||||
|
||||
func (ing fakeIngress) Ingress() *networkv1.Ingress {
|
||||
@ -1317,7 +1444,8 @@ func (ing fakeIngress) Ingress() *networkv1.Ingress {
|
||||
Labels: ing.labels,
|
||||
},
|
||||
Spec: networkv1.IngressSpec{
|
||||
Rules: []networkv1.IngressRule{},
|
||||
Rules: []networkv1.IngressRule{},
|
||||
IngressClassName: &ing.ingressClassName,
|
||||
},
|
||||
Status: networkv1.IngressStatus{
|
||||
LoadBalancer: networkv1.IngressLoadBalancerStatus{
|
||||
|
||||
@ -88,7 +88,7 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endpoints := map[string]*endpoint.Endpoint{}
|
||||
endpoints := map[endpointKey]*endpoint.Endpoint{}
|
||||
|
||||
// create endpoints for all nodes
|
||||
for _, node := range nodes {
|
||||
@ -109,8 +109,7 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
|
||||
// create new endpoint with the information we already have
|
||||
ep := &endpoint.Endpoint{
|
||||
RecordType: "A", // hardcoded DNS record type
|
||||
RecordTTL: ttl,
|
||||
RecordTTL: ttl,
|
||||
}
|
||||
|
||||
if ns.fqdnTemplate != nil {
|
||||
@ -134,14 +133,19 @@ func (ns *nodeSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
return nil, fmt.Errorf("failed to get node address from %s: %s", node.Name, err.Error())
|
||||
}
|
||||
|
||||
ep.Targets = endpoint.Targets(addrs)
|
||||
ep.Labels = endpoint.NewLabels()
|
||||
|
||||
log.Debugf("adding endpoint %s", ep)
|
||||
if _, ok := endpoints[ep.DNSName]; ok {
|
||||
endpoints[ep.DNSName].Targets = append(endpoints[ep.DNSName].Targets, ep.Targets...)
|
||||
} else {
|
||||
endpoints[ep.DNSName] = ep
|
||||
for _, addr := range addrs {
|
||||
log.Debugf("adding endpoint %s target %s", ep, addr)
|
||||
key := endpointKey{
|
||||
dnsName: ep.DNSName,
|
||||
recordType: suitableType(addr),
|
||||
}
|
||||
if _, ok := endpoints[key]; !ok {
|
||||
epCopy := *ep
|
||||
epCopy.RecordType = key.recordType
|
||||
endpoints[key] = &epCopy
|
||||
}
|
||||
endpoints[key].Targets = append(endpoints[key].Targets, addr)
|
||||
}
|
||||
}
|
||||
|
||||
@ -163,13 +167,18 @@ func (ns *nodeSource) nodeAddresses(node *v1.Node) ([]string, error) {
|
||||
v1.NodeExternalIP: {},
|
||||
v1.NodeInternalIP: {},
|
||||
}
|
||||
var ipv6Addresses []string
|
||||
|
||||
for _, addr := range node.Status.Addresses {
|
||||
addresses[addr.Type] = append(addresses[addr.Type], addr.Address)
|
||||
// IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well.
|
||||
if addr.Type == v1.NodeInternalIP && suitableType(addr.Address) == endpoint.RecordTypeAAAA {
|
||||
ipv6Addresses = append(ipv6Addresses, addr.Address)
|
||||
}
|
||||
}
|
||||
|
||||
if len(addresses[v1.NodeExternalIP]) > 0 {
|
||||
return addresses[v1.NodeExternalIP], nil
|
||||
return append(addresses[v1.NodeExternalIP], ipv6Addresses...), nil
|
||||
}
|
||||
|
||||
if len(addresses[v1.NodeInternalIP]) > 0 {
|
||||
|
||||
@ -127,6 +127,19 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"ipv6 node with fqdn returns one endpoint",
|
||||
"",
|
||||
"",
|
||||
"node1.example.org",
|
||||
[]v1.NodeAddress{{Type: v1.NodeInternalIP, Address: "2001:DB8::8"}},
|
||||
map[string]string{},
|
||||
map[string]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{RecordType: "AAAA", DNSName: "node1.example.org", Targets: endpoint.Targets{"2001:DB8::8"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"node with fqdn template returns endpoint with expanded hostname",
|
||||
"",
|
||||
@ -166,6 +179,20 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"node with fqdn template returns two endpoints with dual-stack IP addresses and expanded hostname",
|
||||
"",
|
||||
"{{.Name}}.example.org",
|
||||
"node1",
|
||||
[]v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "1.2.3.4"}, {Type: v1.NodeInternalIP, Address: "2001:DB8::8"}},
|
||||
map[string]string{},
|
||||
map[string]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{RecordType: "A", DNSName: "node1.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{RecordType: "AAAA", DNSName: "node1.example.org", Targets: endpoint.Targets{"2001:DB8::8"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"node with both external and internal IP returns an endpoint with external IP",
|
||||
"",
|
||||
@ -179,6 +206,20 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"node with both external, internal, and IPv6 IP returns endpoints with external IPs",
|
||||
"",
|
||||
"",
|
||||
"node1",
|
||||
[]v1.NodeAddress{{Type: v1.NodeExternalIP, Address: "1.2.3.4"}, {Type: v1.NodeInternalIP, Address: "2.3.4.5"}, {Type: v1.NodeInternalIP, Address: "2001:DB8::8"}},
|
||||
map[string]string{},
|
||||
map[string]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{RecordType: "A", DNSName: "node1", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||
{RecordType: "AAAA", DNSName: "node1", Targets: endpoint.Targets{"2001:DB8::8"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"node with only internal IP returns an endpoint with internal IP",
|
||||
"",
|
||||
@ -192,6 +233,20 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"node with only internal IPs returns endpoints with internal IPs",
|
||||
"",
|
||||
"",
|
||||
"node1",
|
||||
[]v1.NodeAddress{{Type: v1.NodeInternalIP, Address: "2.3.4.5"}, {Type: v1.NodeInternalIP, Address: "2001:DB8::8"}},
|
||||
map[string]string{},
|
||||
map[string]string{},
|
||||
[]*endpoint.Endpoint{
|
||||
{RecordType: "A", DNSName: "node1", Targets: endpoint.Targets{"2.3.4.5"}},
|
||||
{RecordType: "AAAA", DNSName: "node1", Targets: endpoint.Targets{"2001:DB8::8"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"node with neither external nor internal IP returns no endpoints",
|
||||
"",
|
||||
@ -318,7 +373,7 @@ func testNodeSourceEndpoints(t *testing.T) {
|
||||
false,
|
||||
},
|
||||
{
|
||||
"node with nil Lables returns valid endpoint",
|
||||
"node with nil Labels returns valid endpoint",
|
||||
"",
|
||||
"",
|
||||
"node1",
|
||||
|
||||
@ -82,7 +82,7 @@ func (ps *podSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
domains := make(map[string][]string)
|
||||
endpointMap := make(map[endpointKey][]string)
|
||||
for _, pod := range pods {
|
||||
if !pod.Spec.HostNetwork {
|
||||
log.Debugf("skipping pod %s. hostNetwork=false", pod.Name)
|
||||
@ -90,50 +90,51 @@ func (ps *podSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error
|
||||
}
|
||||
|
||||
if domain, ok := pod.Annotations[internalHostnameAnnotationKey]; ok {
|
||||
if _, ok := domains[domain]; !ok {
|
||||
domains[domain] = []string{}
|
||||
}
|
||||
domains[domain] = append(domains[domain], pod.Status.PodIP)
|
||||
addToEndpointMap(endpointMap, domain, suitableType(pod.Status.PodIP), pod.Status.PodIP)
|
||||
}
|
||||
|
||||
if domain, ok := pod.Annotations[hostnameAnnotationKey]; ok {
|
||||
if _, ok := domains[domain]; !ok {
|
||||
domains[domain] = []string{}
|
||||
}
|
||||
|
||||
node, _ := ps.nodeInformer.Lister().Get(pod.Spec.NodeName)
|
||||
for _, address := range node.Status.Addresses {
|
||||
if address.Type == corev1.NodeExternalIP {
|
||||
domains[domain] = append(domains[domain], address.Address)
|
||||
recordType := suitableType(address.Address)
|
||||
// IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well.
|
||||
if address.Type == corev1.NodeExternalIP || (address.Type == corev1.NodeInternalIP && recordType == endpoint.RecordTypeAAAA) {
|
||||
addToEndpointMap(endpointMap, domain, recordType, address.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ps.compatibility == "kops-dns-controller" {
|
||||
if domain, ok := pod.Annotations[kopsDNSControllerInternalHostnameAnnotationKey]; ok {
|
||||
if _, ok := domains[domain]; !ok {
|
||||
domains[domain] = []string{}
|
||||
}
|
||||
domains[domain] = append(domains[domain], pod.Status.PodIP)
|
||||
addToEndpointMap(endpointMap, domain, suitableType(pod.Status.PodIP), pod.Status.PodIP)
|
||||
}
|
||||
|
||||
if domain, ok := pod.Annotations[kopsDNSControllerHostnameAnnotationKey]; ok {
|
||||
if _, ok := domains[domain]; !ok {
|
||||
domains[domain] = []string{}
|
||||
}
|
||||
|
||||
node, _ := ps.nodeInformer.Lister().Get(pod.Spec.NodeName)
|
||||
for _, address := range node.Status.Addresses {
|
||||
if address.Type == corev1.NodeExternalIP {
|
||||
domains[domain] = append(domains[domain], address.Address)
|
||||
recordType := suitableType(address.Address)
|
||||
// IPv6 addresses are labeled as NodeInternalIP despite being usable externally as well.
|
||||
if address.Type == corev1.NodeExternalIP || (address.Type == corev1.NodeInternalIP && recordType == endpoint.RecordTypeAAAA) {
|
||||
addToEndpointMap(endpointMap, domain, recordType, address.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
endpoints := []*endpoint.Endpoint{}
|
||||
for domain, targets := range domains {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(domain, endpoint.RecordTypeA, targets...))
|
||||
for key, targets := range endpointMap {
|
||||
endpoints = append(endpoints, endpoint.NewEndpoint(key.dnsName, key.recordType, targets...))
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func addToEndpointMap(endpointMap map[endpointKey][]string, domain string, recordType string, address string) {
|
||||
key := endpointKey{
|
||||
dnsName: domain,
|
||||
recordType: recordType,
|
||||
}
|
||||
if _, ok := endpointMap[key]; !ok {
|
||||
endpointMap[key] = []string{}
|
||||
}
|
||||
endpointMap[key] = append(endpointMap[key], address)
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ func TestPodSource(t *testing.T) {
|
||||
pods []*corev1.Pod
|
||||
}{
|
||||
{
|
||||
"create records based on pod's external and internal IPs",
|
||||
"create IPv4 records based on pod's external and internal IPs",
|
||||
"",
|
||||
"",
|
||||
[]*endpoint.Endpoint{
|
||||
@ -111,7 +111,7 @@ func TestPodSource(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"create records based on pod's external and internal IPs using DNS Controller annotations",
|
||||
"create IPv4 records based on pod's external and internal IPs using DNS Controller annotations",
|
||||
"",
|
||||
"kops-dns-controller",
|
||||
[]*endpoint.Endpoint{
|
||||
@ -180,12 +180,149 @@ func TestPodSource(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"create IPv6 records based on pod's external and internal IPs",
|
||||
"",
|
||||
"",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "a.foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
{DNSName: "internal.a.foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
},
|
||||
false,
|
||||
[]*corev1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-node1",
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{Type: corev1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-node2",
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{Type: corev1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[]*corev1.Pod{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-pod1",
|
||||
Namespace: "kube-system",
|
||||
Annotations: map[string]string{
|
||||
internalHostnameAnnotationKey: "internal.a.foo.example.org",
|
||||
hostnameAnnotationKey: "a.foo.example.org",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
HostNetwork: true,
|
||||
NodeName: "my-node1",
|
||||
},
|
||||
Status: corev1.PodStatus{
|
||||
PodIP: "2001:DB8::1",
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-pod2",
|
||||
Namespace: "kube-system",
|
||||
Annotations: map[string]string{
|
||||
internalHostnameAnnotationKey: "internal.a.foo.example.org",
|
||||
hostnameAnnotationKey: "a.foo.example.org",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
HostNetwork: true,
|
||||
NodeName: "my-node2",
|
||||
},
|
||||
Status: corev1.PodStatus{
|
||||
PodIP: "2001:DB8::2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"create IPv6 records based on pod's external and internal IPs using DNS Controller annotations",
|
||||
"",
|
||||
"kops-dns-controller",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "a.foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
{DNSName: "internal.a.foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
},
|
||||
false,
|
||||
[]*corev1.Node{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-node1",
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{Type: corev1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-node2",
|
||||
},
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{Type: corev1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
[]*corev1.Pod{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-pod1",
|
||||
Namespace: "kube-system",
|
||||
Annotations: map[string]string{
|
||||
kopsDNSControllerInternalHostnameAnnotationKey: "internal.a.foo.example.org",
|
||||
kopsDNSControllerHostnameAnnotationKey: "a.foo.example.org",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
HostNetwork: true,
|
||||
NodeName: "my-node1",
|
||||
},
|
||||
Status: corev1.PodStatus{
|
||||
PodIP: "2001:DB8::1",
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "my-pod2",
|
||||
Namespace: "kube-system",
|
||||
Annotations: map[string]string{
|
||||
kopsDNSControllerInternalHostnameAnnotationKey: "internal.a.foo.example.org",
|
||||
kopsDNSControllerHostnameAnnotationKey: "a.foo.example.org",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
HostNetwork: true,
|
||||
NodeName: "my-node2",
|
||||
},
|
||||
Status: corev1.PodStatus{
|
||||
PodIP: "2001:DB8::2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"create multiple records",
|
||||
"",
|
||||
"",
|
||||
[]*endpoint.Endpoint{
|
||||
{DNSName: "a.foo.example.org", Targets: endpoint.Targets{"54.10.11.1"}, RecordType: endpoint.RecordTypeA},
|
||||
{DNSName: "a.foo.example.org", Targets: endpoint.Targets{"2001:DB8::1"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
{DNSName: "b.foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
},
|
||||
false,
|
||||
@ -197,6 +334,7 @@ func TestPodSource(t *testing.T) {
|
||||
Status: corev1.NodeStatus{
|
||||
Addresses: []corev1.NodeAddress{
|
||||
{Type: corev1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: corev1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
{Type: corev1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
},
|
||||
},
|
||||
|
||||
@ -216,7 +216,10 @@ func (sc *serviceSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e
|
||||
})
|
||||
// Use stable sort to not disrupt the order of services
|
||||
sort.SliceStable(endpoints, func(i, j int) bool {
|
||||
return endpoints[i].DNSName < endpoints[j].DNSName
|
||||
if endpoints[i].DNSName != endpoints[j].DNSName {
|
||||
return endpoints[i].DNSName < endpoints[j].DNSName
|
||||
}
|
||||
return endpoints[i].RecordType < endpoints[j].RecordType
|
||||
})
|
||||
mergedEndpoints := []*endpoint.Endpoint{}
|
||||
mergedEndpoints = append(mergedEndpoints, endpoints[0])
|
||||
@ -308,8 +311,8 @@ func (sc *serviceSource) extractHeadlessEndpoints(svc *v1.Service, hostname stri
|
||||
return endpoints
|
||||
}
|
||||
for _, address := range node.Status.Addresses {
|
||||
if address.Type == v1.NodeExternalIP {
|
||||
targets = endpoint.Targets{address.Address}
|
||||
if address.Type == v1.NodeExternalIP || (address.Type == v1.NodeInternalIP && suitableType(address.Address) == endpoint.RecordTypeAAAA) {
|
||||
targets = append(targets, address.Address)
|
||||
log.Debugf("Generating matching endpoint %s with NodeExternalIP %s", headlessDomain, address.Address)
|
||||
}
|
||||
}
|
||||
@ -502,7 +505,7 @@ func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, pro
|
||||
log.Errorf("Unable to extract targets from service %s/%s error: %v", svc.Namespace, svc.Name, err)
|
||||
return endpoints
|
||||
}
|
||||
endpoints = append(endpoints, sc.extractNodePortEndpoints(svc, targets, hostname, ttl)...)
|
||||
endpoints = append(endpoints, sc.extractNodePortEndpoints(svc, hostname, ttl)...)
|
||||
case v1.ServiceTypeExternalName:
|
||||
targets = append(targets, extractServiceExternalName(svc)...)
|
||||
}
|
||||
@ -591,6 +594,7 @@ func (sc *serviceSource) extractNodePortTargets(svc *v1.Service) (endpoint.Targe
|
||||
var (
|
||||
internalIPs endpoint.Targets
|
||||
externalIPs endpoint.Targets
|
||||
ipv6IPs endpoint.Targets
|
||||
nodes []*v1.Node
|
||||
err error
|
||||
)
|
||||
@ -638,24 +642,27 @@ func (sc *serviceSource) extractNodePortTargets(svc *v1.Service) (endpoint.Targe
|
||||
externalIPs = append(externalIPs, address.Address)
|
||||
case v1.NodeInternalIP:
|
||||
internalIPs = append(internalIPs, address.Address)
|
||||
if suitableType(address.Address) == endpoint.RecordTypeAAAA {
|
||||
ipv6IPs = append(ipv6IPs, address.Address)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
access := getAccessFromAnnotations(svc.Annotations)
|
||||
if access == "public" {
|
||||
return externalIPs, nil
|
||||
return append(externalIPs, ipv6IPs...), nil
|
||||
}
|
||||
if access == "private" {
|
||||
return internalIPs, nil
|
||||
}
|
||||
if len(externalIPs) > 0 {
|
||||
return externalIPs, nil
|
||||
return append(externalIPs, ipv6IPs...), nil
|
||||
}
|
||||
return internalIPs, nil
|
||||
}
|
||||
|
||||
func (sc *serviceSource) extractNodePortEndpoints(svc *v1.Service, nodeTargets endpoint.Targets, hostname string, ttl endpoint.TTL) []*endpoint.Endpoint {
|
||||
func (sc *serviceSource) extractNodePortEndpoints(svc *v1.Service, hostname string, ttl endpoint.TTL) []*endpoint.Endpoint {
|
||||
var endpoints []*endpoint.Endpoint
|
||||
|
||||
for _, port := range svc.Spec.Ports {
|
||||
|
||||
@ -1615,6 +1615,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
},
|
||||
nodes: []*v1.Node{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1624,6 +1625,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -1634,6 +1636,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -1656,6 +1659,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -1666,6 +1670,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -1681,6 +1686,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "_foo._tcp.foo.bar.example.com", Targets: endpoint.Targets{"0 50 30192 foo.bar.example.com"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
{DNSName: "foo.bar.example.com", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
},
|
||||
nodes: []*v1.Node{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1690,6 +1696,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -1700,6 +1707,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -1716,6 +1724,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
},
|
||||
nodes: []*v1.Node{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1724,6 +1733,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -1733,6 +1743,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Status: v1.NodeStatus{
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -1749,6 +1760,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
},
|
||||
nodes: []*v1.Node{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1758,6 +1770,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -1768,6 +1781,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -1788,6 +1802,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
},
|
||||
nodes: []*v1.Node{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1797,6 +1812,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -1807,6 +1823,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -1828,6 +1845,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}, RecordType: endpoint.RecordTypeA},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
},
|
||||
nodes: []*v1.Node{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1837,6 +1855,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -1847,6 +1866,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -1865,6 +1885,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "_foo._tcp.foo.example.org", Targets: endpoint.Targets{"0 50 30192 foo.example.org"}, RecordType: endpoint.RecordTypeSRV},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}, RecordType: endpoint.RecordTypeA},
|
||||
{DNSName: "foo.example.org", Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}, RecordType: endpoint.RecordTypeAAAA},
|
||||
},
|
||||
nodes: []*v1.Node{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1874,6 +1895,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -1884,6 +1906,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -1901,7 +1924,9 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1"}},
|
||||
{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
|
||||
{DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1"}},
|
||||
{DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1"}},
|
||||
},
|
||||
nodes: []*v1.Node{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1914,6 +1939,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -1927,6 +1953,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -1943,7 +1970,9 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}},
|
||||
{DNSName: "internal.foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}},
|
||||
{DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"10.0.1.1", "10.0.1.2"}},
|
||||
{DNSName: "internal.bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}},
|
||||
},
|
||||
nodes: []*v1.Node{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1956,6 +1985,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -1969,6 +1999,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -1985,7 +2016,9 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
},
|
||||
expected: []*endpoint.Endpoint{
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}},
|
||||
{DNSName: "foo.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}},
|
||||
{DNSName: "bar.example.org", RecordType: endpoint.RecordTypeA, Targets: endpoint.Targets{"54.10.11.1", "54.10.11.2"}},
|
||||
{DNSName: "bar.example.org", RecordType: endpoint.RecordTypeAAAA, Targets: endpoint.Targets{"2001:DB8::1", "2001:DB8::2"}},
|
||||
},
|
||||
nodes: []*v1.Node{{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -1998,6 +2031,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -2011,6 +2045,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
@ -2039,6 +2074,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.1"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::1"},
|
||||
},
|
||||
},
|
||||
}, {
|
||||
@ -2052,6 +2088,7 @@ func TestServiceSourceNodePortServices(t *testing.T) {
|
||||
Addresses: []v1.NodeAddress{
|
||||
{Type: v1.NodeExternalIP, Address: "54.10.11.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "10.0.1.2"},
|
||||
{Type: v1.NodeInternalIP, Address: "2001:DB8::2"},
|
||||
},
|
||||
},
|
||||
}},
|
||||
|
||||
@ -29,11 +29,14 @@ func sortEndpoints(endpoints []*endpoint.Endpoint) {
|
||||
sort.Strings([]string(ep.Targets))
|
||||
}
|
||||
sort.Slice(endpoints, func(i, k int) bool {
|
||||
// Sort by DNSName and Targets
|
||||
// Sort by DNSName, RecordType, and Targets
|
||||
ei, ek := endpoints[i], endpoints[k]
|
||||
if ei.DNSName != ek.DNSName {
|
||||
return ei.DNSName < ek.DNSName
|
||||
}
|
||||
if ei.RecordType != ek.RecordType {
|
||||
return ei.RecordType < ek.RecordType
|
||||
}
|
||||
// Targets are sorted ahead of time.
|
||||
for j, ti := range ei.Targets {
|
||||
if j >= len(ek.Targets) {
|
||||
|
||||
@ -86,6 +86,12 @@ type Source interface {
|
||||
AddEventHandler(context.Context, func())
|
||||
}
|
||||
|
||||
// endpointKey is the type of a map key for separating endpoints or targets.
|
||||
type endpointKey struct {
|
||||
dnsName string
|
||||
recordType string
|
||||
}
|
||||
|
||||
func getTTLFromAnnotations(annotations map[string]string) (endpoint.TTL, error) {
|
||||
ttlNotConfigured := endpoint.TTL(0)
|
||||
ttlAnnotation, exists := annotations[ttlAnnotationKey]
|
||||
|
||||
@ -46,6 +46,7 @@ type Config struct {
|
||||
Namespace string
|
||||
AnnotationFilter string
|
||||
LabelFilter labels.Selector
|
||||
IngressClassNames []string
|
||||
FQDNTemplate string
|
||||
CombineFQDNAndAnnotation bool
|
||||
IgnoreHostnameAnnotation bool
|
||||
@ -222,7 +223,7 @@ func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewIngressSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.LabelFilter)
|
||||
return NewIngressSource(ctx, client, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation, cfg.IgnoreIngressTLSSpec, cfg.IgnoreIngressRulesSpec, cfg.LabelFilter, cfg.IngressClassNames)
|
||||
case "pod":
|
||||
client, err := p.KubeClient()
|
||||
if err != nil {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user