aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Chris O'Haver <cohaver@infoblox.com> 2017-01-15 03:12:28 -0500
committerGravatar Miek Gieben <miek@miek.nl> 2017-01-15 08:12:28 +0000
commita6d232a622a2f83a5d5ea1d9d946da7f910a0f9e (patch)
tree187a9366c8515bf1d8f6d483476555ea4f9897b8
parentb6a2a5aeaa0811199c6206eb87893eaa2298ae95 (diff)
downloadcoredns-a6d232a622a2f83a5d5ea1d9d946da7f910a0f9e.tar.gz
coredns-a6d232a622a2f83a5d5ea1d9d946da7f910a0f9e.tar.zst
coredns-a6d232a622a2f83a5d5ea1d9d946da7f910a0f9e.zip
dont require/allow "_" prefix for srv wildcard fields (#472)
* dont require/allow "_" prefix for srv wildcard fields * streamline parse/validation of req name * removing nametemplate * error when zone not found, loopify unit tests
-rw-r--r--middleware/kubernetes/README.md37
-rw-r--r--middleware/kubernetes/kubernetes.go249
-rw-r--r--middleware/kubernetes/kubernetes_test.go112
-rw-r--r--middleware/kubernetes/lookup.go6
-rw-r--r--middleware/kubernetes/nametemplate/nametemplate.go195
-rw-r--r--middleware/kubernetes/nametemplate/nametemplate_test.go127
-rw-r--r--middleware/kubernetes/setup.go16
-rw-r--r--middleware/kubernetes/setup_test.go71
-rw-r--r--test/kubernetes_test.go30
9 files changed, 244 insertions, 599 deletions
diff --git a/middleware/kubernetes/README.md b/middleware/kubernetes/README.md
index 54dd98e57..85e2356b6 100644
--- a/middleware/kubernetes/README.md
+++ b/middleware/kubernetes/README.md
@@ -1,15 +1,14 @@
# kubernetes
*kubernetes* enables reading zone data from a kubernetes cluster. Record names
-are constructed as "myservice.mynamespace.coredns.local" where:
+are constructed as "myservice.mynamespace.type.coredns.local" where:
* "myservice" is the name of the k8s service (this may include multiple DNS labels,
such as "c1.myservice"),
* "mynamespace" is the k8s namespace for the service, and
+* "type" is svc or pod
* "coredns.local" is the zone configured for `kubernetes`.
-The record name format can be changed by specifying a name template in the Corefile.
-
## Syntax
~~~
@@ -50,9 +49,6 @@ This is the default kubernetes setup, with everything specified in full:
# The tls cert, key and the CA cert filenames
tls cert key cacert
- # Assemble k8s record names with the template
- template {service}.{namespace}.{type}.{zone}
-
# Only expose the k8s namespace "demo"
namespaces demo
@@ -64,15 +60,15 @@ This is the default kubernetes setup, with everything specified in full:
# "application=nginx" in the staging or qa environments.
#labels environment in (staging, qa),application=nginx
- # The mode of responding to pod A record requests.
- # e.g 1-2-3-4.ns.pod.zone. This option is provided to allow use of
- # SSL certs when connecting directly to pods.
- # Valid values: disabled, verified, insecure
- # disabled: default. ignore pod requests, always returning NXDOMAIN
- # insecure: Always return an A record with IP from request (without
- # checking k8s). This option is is vulnerable to abuse if
- # used maliciously in conjuction with wildcard SSL certs.
- pods disabled
+ # The mode of responding to pod A record requests.
+ # e.g 1-2-3-4.ns.pod.zone. This option is provided to allow use of
+ # SSL certs when connecting directly to pods.
+ # Valid values: disabled, verified, insecure
+ # disabled: default. ignore pod requests, always returning NXDOMAIN
+ # insecure: Always return an A record with IP from request (without
+ # checking k8s). This option is is vulnerable to abuse if
+ # used maliciously in conjuction with wildcard SSL certs.
+ pods disabled
}
# Perform DNS response caching for the coredns.local zone
# Cache timeout is specified by an integer in seconds
@@ -82,22 +78,12 @@ This is the default kubernetes setup, with everything specified in full:
Defaults:
* If the `namespaces` keyword is omitted, all kubernetes namespaces are exposed.
-* If the `template` keyword is omitted, the default template of "{service}.{namespace}.{type}.{zone}" is used.
* If the `resyncperiod` keyword is omitted, the default resync period is 5 minutes.
* The `labels` keyword is only used when filtering results based on kubernetes label selector syntax
is required. The label selector syntax is described in the kubernetes API documentation at:
http://kubernetes.io/docs/user-guide/labels/
* If the `pods` keyword is omitted, all pod type requests will result in NXDOMAIN
-### Template Syntax
-Record name templates can be constructed using the symbolic elements:
-
-| template symbol | description |
-| `{service}` | Kubernetes object/service name. |
-| `{namespace}` | The kubernetes namespace. |
-| `{type}` | The type of the kubernetes object. Supports values 'svc' and 'pod'. |
-| `{zone}` | The zone configured for the kubernetes middleware. |
-
### Basic Setup
#### Launch Kubernetes
@@ -146,7 +132,6 @@ Build CoreDNS and launch using this configuration file:
kubernetes coredns.local {
resyncperiod 5m
endpoint http://localhost:8080
- template {service}.{namespace}.{type}.{zone}
namespaces demo
# Only expose the records for kubernetes objects
# that matches this label selector.
diff --git a/middleware/kubernetes/kubernetes.go b/middleware/kubernetes/kubernetes.go
index b93358863..be0a57778 100644
--- a/middleware/kubernetes/kubernetes.go
+++ b/middleware/kubernetes/kubernetes.go
@@ -10,7 +10,6 @@ import (
"github.com/miekg/coredns/middleware"
"github.com/miekg/coredns/middleware/etcd/msg"
- "github.com/miekg/coredns/middleware/kubernetes/nametemplate"
"github.com/miekg/coredns/middleware/pkg/dnsutil"
dnsstrings "github.com/miekg/coredns/middleware/pkg/strings"
"github.com/miekg/coredns/middleware/proxy"
@@ -38,7 +37,6 @@ type Kubernetes struct {
APIClientKey string
APIConn *dnsController
ResyncPeriod time.Duration
- NameTemplate *nametemplate.Template
Namespaces []string
LabelSelector *unversionedapi.LabelSelector
Selector *labels.Selector
@@ -69,16 +67,22 @@ type pod struct {
addr string
}
+type recordRequest struct {
+ port, protocol, endpoint, service, namespace, typeName, zone string
+}
+
var errNoItems = errors.New("no items found")
var errNsNotExposed = errors.New("namespace is not exposed")
var errInvalidRequest = errors.New("invalid query name")
// Services implements the ServiceBackend interface.
func (k *Kubernetes) Services(state request.Request, exact bool, opt middleware.Options) ([]msg.Service, []msg.Service, error) {
- if state.Type() == "SRV" && !ValidSRV(state.Name()) {
- return nil, nil, errInvalidRequest
+
+ r, e := k.parseRequest(state.Name(), state.Type())
+ if e != nil {
+ return nil, nil, e
}
- s, e := k.Records(state.Name(), exact)
+ s, e := k.Records(r)
return s, nil, e // Haven't implemented debug queries yet.
}
@@ -177,85 +181,94 @@ func (k *Kubernetes) InitKubeCache() error {
return err
}
-// getZoneForName returns the zone string that matches the name and a
-// list of the DNS labels from name that are within the zone.
-// For example, if "coredns.local" is a zone configured for the
-// Kubernetes middleware, then getZoneForName("a.b.coredns.local")
-// will return ("coredns.local", ["a", "b"]).
-func (k *Kubernetes) getZoneForName(name string) (string, []string) {
- var zone string
- var serviceSegments []string
+func (k *Kubernetes) parseRequest(lowerCasedName, qtype string) (r recordRequest, err error) {
+ // 3 Possible cases
+ // SRV Request: _port._protocol.service.namespace.type.zone
+ // A Request (endpoint): endpoint.service.namespace.type.zone
+ // A Request (service): service.namespace.type.zone
+ // separate zone from rest of lowerCasedName
+ var segs []string
for _, z := range k.Zones {
- if dns.IsSubDomain(z, name) {
- zone = z
+ if dns.IsSubDomain(z, lowerCasedName) {
+ r.zone = z
- serviceSegments = dns.SplitDomainName(name)
- serviceSegments = serviceSegments[:len(serviceSegments)-dns.CountLabel(zone)]
+ segs = dns.SplitDomainName(lowerCasedName)
+ segs = segs[:len(segs)-dns.CountLabel(r.zone)]
break
}
}
+ if r.zone == "" {
+ return r, errors.New("zone not found")
+ }
- return zone, serviceSegments
-}
+ offset := 0
+ if len(segs) == 5 {
+ // This is a SRV style request, get first two elements as port and
+ // protocol, stripping leading underscores if present.
+ if segs[0][0] == '_' {
+ r.port = segs[0][1:]
+ } else {
+ r.port = segs[0]
+ if !symbolContainsWildcard(r.port) {
+ return r, errors.New("srv port must start with an underscore or be a wildcard")
+ }
+ }
+ if segs[1][0] == '_' {
+ r.protocol = segs[1][1:]
+ if r.protocol != "tcp" && r.protocol != "udp" {
+ return r, errors.New("invalid srv protocol: " + r.protocol)
+ }
+ } else {
+ r.protocol = segs[1]
+ if !symbolContainsWildcard(r.protocol) {
+ return r, errors.New("srv protocol must start with an underscore or be a wildcard")
+ }
+ }
+ offset = 2
+ } else if len(segs) == 4 {
+ // This is an endpoint A style request. Get first element as endpoint.
+ r.endpoint = segs[0]
+ offset = 1
+ }
-// stripSRVPrefix separates out the port and protocol segments, if present
-// If not present, assume all ports/protocols (e.g. wildcard)
-func stripSRVPrefix(name []string) (string, string, []string) {
- if name[0][0] == '_' && name[1][0] == '_' {
- return name[0][1:], name[1][1:], name[2:]
+ // SRV requests require a port and protocol
+ if qtype == "SRV" {
+ if r.port == "" || r.protocol == "" {
+ return r, errors.New("invalid srv request")
+ }
}
- // no srv prefix present
- return "*", "*", name
-}
+ // A requests cannot have port/protocol
+ if qtype == "A" {
+ if r.port != "" && r.protocol != "" {
+ return r, errors.New("invalid a request")
+ }
+ }
+
+ if len(segs) == (offset + 3) {
+ r.service = segs[offset]
+ r.namespace = segs[offset+1]
+ r.typeName = segs[offset+2]
-func stripEndpointName(name []string) (endpoint string, nameOut []string) {
- if len(name) == 4 {
- return strings.ToLower(name[0]), name[1:]
+ return r, nil
}
- return "", name
+
+ return r, errors.New("invalid request")
+
}
// Records looks up services in kubernetes. If exact is true, it will lookup
// just this name. This is used when find matches when completing SRV lookups
// for instance.
-func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) {
- var (
- serviceName string
- namespace string
- typeName string
- )
-
- zone, serviceSegments := k.getZoneForName(name)
- port, protocol, serviceSegments := stripSRVPrefix(serviceSegments)
- endpointname, serviceSegments := stripEndpointName(serviceSegments)
- if len(serviceSegments) < 3 {
- return nil, errNoItems
- }
-
- serviceName = serviceSegments[0]
- namespace = serviceSegments[1]
- typeName = serviceSegments[2]
-
- if namespace == "" {
- err := errors.New("Parsing query string did not produce a namespace value. Assuming wildcard namespace.")
- log.Printf("[WARN] %v\n", err)
- namespace = "*"
- }
-
- if serviceName == "" {
- err := errors.New("Parsing query string did not produce a serviceName value. Assuming wildcard serviceName.")
- log.Printf("[WARN] %v\n", err)
- serviceName = "*"
- }
+func (k *Kubernetes) Records(r recordRequest) ([]msg.Service, error) {
// Abort if the namespace does not contain a wildcard, and namespace is not published per CoreFile
// Case where namespace contains a wildcard is handled in Get(...) method.
- if (!symbolContainsWildcard(namespace)) && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(namespace, k.Namespaces)) {
+ if (!symbolContainsWildcard(r.namespace)) && (len(k.Namespaces) > 0) && (!dnsstrings.StringInSlice(r.namespace, k.Namespaces)) {
return nil, errNsNotExposed
}
- services, pods, err := k.Get(namespace, serviceName, endpointname, port, protocol, typeName)
+ services, pods, err := k.Get(r)
if err != nil {
return nil, err
}
@@ -264,7 +277,7 @@ func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) {
return nil, errNoItems
}
- records := k.getRecordsForK8sItems(services, pods, zone)
+ records := k.getRecordsForK8sItems(services, pods, r.zone)
return records, nil
}
@@ -320,18 +333,6 @@ func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, zone
return records
}
-// Get retrieves matching data from the cache.
-func (k *Kubernetes) Get(namespace, servicename, endpointname, port, protocol, typeName string) (services []service, pods []pod, err error) {
- switch {
- case typeName == "pod":
- pods, err = k.findPods(namespace, servicename)
- return nil, pods, err
- default:
- services, err = k.findServices(namespace, servicename, endpointname, port, protocol)
- return services, nil, err
- }
-}
-
func ipFromPodName(podname string) string {
if strings.Count(podname, "-") == 3 && !strings.Contains(podname, "--") {
return strings.Replace(podname, "-", ".", -1)
@@ -362,18 +363,30 @@ func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error)
}
-func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, protocol string) ([]service, error) {
+// Get retrieves matching data from the cache.
+func (k *Kubernetes) Get(r recordRequest) (services []service, pods []pod, err error) {
+ switch {
+ case r.typeName == "pod":
+ pods, err = k.findPods(r.namespace, r.service)
+ return nil, pods, err
+ default:
+ services, err = k.findServices(r)
+ return services, nil, err
+ }
+}
+
+func (k *Kubernetes) findServices(r recordRequest) ([]service, error) {
serviceList := k.APIConn.ServiceList()
var resultItems []service
- nsWildcard := symbolContainsWildcard(namespace)
- serviceWildcard := symbolContainsWildcard(servicename)
- portWildcard := symbolContainsWildcard(port)
- protocolWildcard := symbolContainsWildcard(protocol)
+ nsWildcard := symbolContainsWildcard(r.namespace)
+ serviceWildcard := symbolContainsWildcard(r.service)
+ portWildcard := symbolContainsWildcard(r.port) || r.port == ""
+ protocolWildcard := symbolContainsWildcard(r.protocol) || r.protocol == ""
for _, svc := range serviceList {
- if !(symbolMatches(namespace, svc.Namespace, nsWildcard) && symbolMatches(servicename, svc.Name, serviceWildcard)) {
+ if !(symbolMatches(r.namespace, svc.Namespace, nsWildcard) && symbolMatches(r.service, svc.Name, serviceWildcard)) {
continue
}
// If namespace has a wildcard, filter results against Corefile namespace list.
@@ -384,7 +397,7 @@ func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, pr
s := service{name: svc.Name, namespace: svc.Namespace, addr: svc.Spec.ClusterIP}
if s.addr != api.ClusterIPNone {
for _, p := range svc.Spec.Ports {
- if !(symbolMatches(port, strings.ToLower(p.Name), portWildcard) && symbolMatches(protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) {
+ if !(symbolMatches(r.port, strings.ToLower(p.Name), portWildcard) && symbolMatches(r.protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) {
continue
}
s.ports = append(s.ports, p)
@@ -405,10 +418,10 @@ func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, pr
for _, addr := range eps.Addresses {
for _, p := range eps.Ports {
ephostname := endpointHostname(addr)
- if endpointname != "" && endpointname != ephostname {
+ if r.endpoint != "" && r.endpoint != ephostname {
continue
}
- if !(symbolMatches(port, strings.ToLower(p.Name), portWildcard) && symbolMatches(protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) {
+ if !(symbolMatches(r.port, strings.ToLower(p.Name), portWildcard) && symbolMatches(r.protocol, strings.ToLower(string(p.Protocol)), protocolWildcard)) {
continue
}
s.endpoints = append(s.endpoints, endpoint{addr: addr, port: p})
@@ -422,16 +435,10 @@ func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, pr
}
func symbolMatches(queryString, candidateString string, wildcard bool) bool {
- result := false
- switch {
- case !wildcard:
- result = (queryString == candidateString)
- case queryString == "*":
- result = true
- case queryString == "any":
- result = true
- }
- return result
+ if wildcard {
+ return true
+ }
+ return queryString == candidateString
}
// getServiceRecordForIP: Gets a service record with a cluster ip matching the ip argument
@@ -476,57 +483,3 @@ func (k *Kubernetes) getServiceRecordForIP(ip, name string) []msg.Service {
func symbolContainsWildcard(symbol string) bool {
return (strings.Contains(symbol, "*") || (symbol == "any"))
}
-
-// ValidSRV parses a server record validating _port._proto. prefix labels.
-// The valid schema is:
-// * Fist two segments must start with an "_",
-// * Second segment must be one of _tcp|_udp|_*|_any
-func ValidSRV(name string) bool {
-
- // Does it start with a "_" ?
- if len(name) > 0 && name[0] != '_' {
- return false
- }
-
- // First label
- first, end := dns.NextLabel(name, 0)
- if end {
- return false
- }
- // Second label
- off, end := dns.NextLabel(name, first)
- if end {
- return false
- }
-
- // first:off has captured _tcp. or _udp. (if present)
- second := name[first:off]
- if len(second) > 0 && second[0] != '_' {
- return false
- }
-
- // A bit convoluted to avoid strings.ToLower
- if len(second) == 5 {
- // matches _tcp
- if (second[1] == 't' || second[1] == 'T') && (second[2] == 'c' || second[2] == 'C') &&
- (second[3] == 'p' || second[3] == 'P') {
- return true
- }
- // matches _udp
- if (second[1] == 'u' || second[1] == 'U') && (second[2] == 'd' || second[2] == 'D') &&
- (second[3] == 'p' || second[3] == 'P') {
- return true
- }
- // matches _any
- if (second[1] == 'a' || second[1] == 'A') && (second[2] == 'n' || second[2] == 'N') &&
- (second[3] == 'y' || second[3] == 'Y') {
- return true
- }
- }
- // matches _*
- if len(second) == 3 && second[1] == '*' {
- return true
- }
-
- return false
-}
diff --git a/middleware/kubernetes/kubernetes_test.go b/middleware/kubernetes/kubernetes_test.go
index 53404ecf5..f7529a1d5 100644
--- a/middleware/kubernetes/kubernetes_test.go
+++ b/middleware/kubernetes/kubernetes_test.go
@@ -1,6 +1,7 @@
package kubernetes
import "testing"
+import "reflect"
// Test data for TestSymbolContainsWildcard cases.
var testdataSymbolContainsWildcard = []struct {
@@ -23,3 +24,114 @@ func TestSymbolContainsWildcard(t *testing.T) {
}
}
}
+
+func expectString(t *testing.T, function, qtype, query string, r *recordRequest, field, expected string) {
+ ref := reflect.ValueOf(r)
+ ref_f := reflect.Indirect(ref).FieldByName(field)
+ got := ref_f.String()
+ if got != expected {
+ t.Errorf("Expected %v(%v, \"%v\") to get %v == \"%v\". Instead got \"%v\".", function, query, qtype, field, expected, got)
+ }
+}
+
+func TestParseRequest(t *testing.T) {
+
+ var tcs map[string]string
+
+ k := Kubernetes{Zones: []string{"inter.webs.test"}}
+ f := "parseRequest"
+
+ // Test a valid SRV request
+ //
+ query := "_http._tcp.webs.mynamespace.svc.inter.webs.test."
+ r, e := k.parseRequest(query, "SRV")
+ if e != nil {
+ t.Errorf("Expected no error from parseRequest(%v, \"SRV\"). Instead got '%v'.", query, e)
+ }
+
+ tcs = map[string]string{
+ "port": "http",
+ "protocol": "tcp",
+ "endpoint": "",
+ "service": "webs",
+ "namespace": "mynamespace",
+ "typeName": "svc",
+ "zone": "inter.webs.test",
+ }
+ for field, expected := range tcs {
+ expectString(t, f, "SRV", query, &r, field, expected)
+ }
+
+ // Test wildcard acceptance
+ //
+ query = "*.any.*.any.svc.inter.webs.test."
+ r, e = k.parseRequest(query, "SRV")
+ if e != nil {
+ t.Errorf("Expected no error from parseRequest(\"%v\", \"SRV\"). Instead got '%v'.", query, e)
+ }
+
+ tcs = map[string]string{
+ "port": "*",
+ "protocol": "any",
+ "endpoint": "",
+ "service": "*",
+ "namespace": "any",
+ "typeName": "svc",
+ "zone": "inter.webs.test",
+ }
+ for field, expected := range tcs {
+ expectString(t, f, "SRV", query, &r, field, expected)
+ }
+
+ // Test A request of endpoint
+ //
+ query = "1-2-3-4.webs.mynamespace.svc.inter.webs.test."
+ r, e = k.parseRequest(query, "A")
+ if e != nil {
+ t.Errorf("Expected no error from parseRequest(\"%v\", \"A\"). Instead got '%v'.", query, e)
+ }
+ tcs = map[string]string{
+ "port": "",
+ "protocol": "",
+ "endpoint": "1-2-3-4",
+ "service": "webs",
+ "namespace": "mynamespace",
+ "typeName": "svc",
+ "zone": "inter.webs.test",
+ }
+ for field, expected := range tcs {
+ expectString(t, f, "A", query, &r, field, expected)
+ }
+
+ // Invalid query tests
+ //
+
+ invalidAQueries := []string{
+ "_http._tcp.webs.mynamespace.svc.inter.webs.test.", // A requests cannot have port or protocol
+ "servname.ns1.srv.inter.nets.test.", // A requests must have zone that matches corefile
+
+ }
+ for _, q := range invalidAQueries {
+ _, e = k.parseRequest(q, "A")
+ if e == nil {
+ t.Errorf("Expected error from %v(\"%v\", \"A\").", f, q)
+ }
+ }
+
+ invalidSRVQueries := []string{
+ "webs.mynamespace.svc.inter.webs.test.", // SRV requests must have port and protocol
+ "_http._pcp.webs.mynamespace.svc.inter.webs.test.", // SRV protocol must be tcp or udp
+ "_http._tcp.ep.webs.ns.svc.inter.webs.test.", // SRV requests cannot have an endpoint
+ "_*._*.webs.mynamespace.svc.inter.webs.test.", // SRV request with invalid wildcards
+ "_http._tcp",
+ "_tcp.test.",
+ ".",
+ }
+
+ for _, q := range invalidSRVQueries {
+ _, e = k.parseRequest(q, "SRV")
+ if e == nil {
+ t.Errorf("Expected error from %v(\"%v\", \"SRV\").", f, q)
+ }
+ }
+}
diff --git a/middleware/kubernetes/lookup.go b/middleware/kubernetes/lookup.go
index 61689baac..a3c9fcbc0 100644
--- a/middleware/kubernetes/lookup.go
+++ b/middleware/kubernetes/lookup.go
@@ -12,7 +12,11 @@ import (
)
func (k Kubernetes) records(state request.Request, exact bool) ([]msg.Service, error) {
- services, err := k.Records(state.Name(), exact)
+ r, err := k.parseRequest(state.Name(), state.Type())
+ if err != nil {
+ return nil, err
+ }
+ services, err := k.Records(r)
if err != nil {
return nil, err
}
diff --git a/middleware/kubernetes/nametemplate/nametemplate.go b/middleware/kubernetes/nametemplate/nametemplate.go
deleted file mode 100644
index 328572497..000000000
--- a/middleware/kubernetes/nametemplate/nametemplate.go
+++ /dev/null
@@ -1,195 +0,0 @@
-package nametemplate
-
-import (
- "errors"
- "strings"
-
- dns_strings "github.com/miekg/coredns/middleware/pkg/strings"
-)
-
-// Likely symbols that require support:
-// {id}
-// {ip}
-// {portname}
-// {protocolname}
-// {servicename}
-// {namespace}
-// {type} "svc" or "pod"
-// {zone}
-
-// SkyDNS normal services have an A-record of the form "{servicename}.{namespace}.{type}.{zone}"
-// This resolves to the cluster IP of the service.
-
-// SkyDNS headless services have an A-record of the form "{servicename}.{namespace}.{type}.{zone}"
-// This resolves to the set of IPs of the pods selected by the Service. Clients are expected to
-// consume the set or else use round-robin selection from the set.
-
-var symbols = map[string]string{
- "service": "{service}",
- "namespace": "{namespace}",
- "type": "{type}",
- "zone": "{zone}",
-}
-
-var types = []string{
- "svc",
- "pod",
-}
-
-var requiredSymbols = []string{
- "namespace",
- "service",
-}
-
-// TODO: Validate that provided NameTemplate string only contains:
-// * valid, known symbols, or
-// * static strings
-
-// TODO: Support collapsing multiple segments into a symbol. Either:
-// * all left-over segments are used as the "service" name, or
-// * some scheme like "{namespace}.{namespace}" means use
-// segments concatenated with a "." for the namespace, or
-// * {namespace2:4} means use segements 2->4 for the namespace.
-
-// TODO: possibly need to store length of segmented format to handle cases
-// where query string segments to a shorter or longer list than the template.
-// When query string segments to shorter than template:
-// * either wildcards are being used, or
-// * we are not looking up an A, AAAA, or SRV record (eg NS), or
-// * we can just short-circuit failure before hitting the k8s API.
-// Where the query string is longer than the template, need to define which
-// symbol consumes the other segments. Most likely this would be the servicename.
-// Also consider how to handle static strings in the format template.
-
-// Template holds the kubernetes template.
-type Template struct {
- formatString string
- splitFormat []string
- // Element is a map of element name :: index in the segmented record name for the named element
- Element map[string]int
-}
-
-// SetTemplate use the string s the set the template.
-func (t *Template) SetTemplate(s string) error {
- var err error
-
- t.Element = map[string]int{}
-
- t.formatString = s
- t.splitFormat = strings.Split(t.formatString, ".")
- for templateIndex, v := range t.splitFormat {
- elementPositionSet := false
- for name, symbol := range symbols {
- if v == symbol {
- t.Element[name] = templateIndex
- elementPositionSet = true
- break
- }
- }
- if !elementPositionSet {
- if strings.Contains(v, "{") {
- err = errors.New("Record name template contains the unknown symbol '" + v + "'")
- return err
- }
- }
- }
-
- if err == nil && !t.IsValid() {
- err = errors.New("Record name template does not pass NameTemplate validation")
- return err
- }
-
- return err
-}
-
-// TODO: Find a better way to pull the data segments out of the
-// query string based on the template. Perhaps it is better
-// to treat the query string segments as a reverse stack and
-// step down the stack to find the right element.
-
-// ZoneFromSegmentArray returns the zone string from the segments.
-func (t *Template) ZoneFromSegmentArray(segments []string) string {
- index, ok := t.Element["zone"]
- if !ok {
- return ""
- }
- return strings.Join(segments[index:], ".")
-}
-
-// NamespaceFromSegmentArray returns the namespace string from the segments.
-func (t *Template) NamespaceFromSegmentArray(segments []string) string {
- return t.symbolFromSegmentArray("namespace", segments)
-}
-
-// ServiceFromSegmentArray returns the service string from the segments.
-func (t *Template) ServiceFromSegmentArray(segments []string) string {
- return t.symbolFromSegmentArray("service", segments)
-}
-
-// TypeFromSegmentArray returns the type string from the segments.
-func (t *Template) TypeFromSegmentArray(segments []string) string {
- typeSegment := t.symbolFromSegmentArray("type", segments)
-
- // Limit type to known types symbols
- if dns_strings.StringInSlice(typeSegment, types) {
- return ""
- }
-
- return typeSegment
-}
-
-func (t *Template) symbolFromSegmentArray(symbol string, segments []string) string {
- index, ok := t.Element[symbol]
- if !ok {
- return ""
- }
- return segments[index]
-}
-
-// RecordNameFromNameValues returns the string produced by applying the
-// values to the NameTemplate format string.
-func (t *Template) RecordNameFromNameValues(values NameValues) string {
- recordName := make([]string, len(t.splitFormat))
- copy(recordName[:], t.splitFormat)
-
- for name, index := range t.Element {
- if index == -1 {
- continue
- }
- switch name {
- case "type":
- recordName[index] = values.TypeName
- case "service":
- recordName[index] = values.ServiceName
- case "namespace":
- recordName[index] = values.Namespace
- case "zone":
- recordName[index] = values.Zone
- }
- }
- return strings.Join(recordName, ".")
-}
-
-// IsValid returns true if the template has all the required symbols, false otherwise.
-func (t *Template) IsValid() bool {
- result := true
-
- // Ensure that all requiredSymbols are found in NameTemplate
- for _, symbol := range requiredSymbols {
- if _, ok := t.Element[symbol]; !ok {
- result = false
- break
- }
- }
-
- return result
-}
-
-// NameValues contains a number of values.
-// TODO(...): better docs.
-type NameValues struct {
- ServiceName string
- Namespace string
- TypeName string
- Zone string
-}
diff --git a/middleware/kubernetes/nametemplate/nametemplate_test.go b/middleware/kubernetes/nametemplate/nametemplate_test.go
deleted file mode 100644
index 68c67af12..000000000
--- a/middleware/kubernetes/nametemplate/nametemplate_test.go
+++ /dev/null
@@ -1,127 +0,0 @@
-package nametemplate
-
-import (
- "strings"
- "testing"
-)
-
-const (
- zone = 0
- namespace = 1
- service = 2
-)
-
-// Map of format string :: expected locations of name symbols in the format.
-// -1 value indicates that symbol does not exist in format.
-var exampleTemplates = map[string][]int{
- "{service}.{namespace}.{type}.{zone}": {3, 1, 0}, // service symbol expected @ position 0, namespace @ 1, zone @ 3
- "{namespace}.{type}.{zone}": {2, 0, -1},
- "": {-1, -1, -1},
-}
-
-func TestSetTemplate(t *testing.T) {
- for s, expectedValue := range exampleTemplates {
-
- n := new(Template)
- n.SetTemplate(s)
-
- // check the indexes resulting from calling SetTemplate() against expectedValues
- if expectedValue[zone] != -1 {
- if n.Element["zone"] != expectedValue[zone] {
- t.Errorf("Expected zone at index '%v', instead found at index '%v' for format string '%v'", expectedValue[zone], n.Element["zone"], s)
- }
- }
- }
-}
-
-func TestServiceFromSegmentArray(t *testing.T) {
- var (
- n *Template
- formatString string
- queryString string
- splitQuery []string
- expectedService string
- actualService string
- )
-
- // Case where template contains {service}
- n = new(Template)
- formatString = "{service}.{namespace}.{type}.{zone}"
- n.SetTemplate(formatString)
-
- queryString = "myservice.mynamespace.svc.coredns"
- splitQuery = strings.Split(queryString, ".")
- expectedService = "myservice"
- actualService = n.ServiceFromSegmentArray(splitQuery)
-
- if actualService != expectedService {
- t.Errorf("Expected service name '%v', instead got service name '%v' for query string '%v' and format '%v'", expectedService, actualService, queryString, formatString)
- }
-
- // Case where template does not contain {service}
- n = new(Template)
- formatString = "{namespace}.{type}.{zone}"
- n.SetTemplate(formatString)
-
- queryString = "mynamespace.svc.coredns"
- splitQuery = strings.Split(queryString, ".")
- expectedService = ""
- actualService = n.ServiceFromSegmentArray(splitQuery)
-
- if actualService != expectedService {
- t.Errorf("Expected service name '%v', instead got service name '%v' for query string '%v' and format '%v'", expectedService, actualService, queryString, formatString)
- }
-}
-
-func TestZoneFromSegmentArray(t *testing.T) {
- var (
- n *Template
- formatString string
- queryString string
- splitQuery []string
- expectedZone string
- actualZone string
- )
-
- // Case where template contains {zone}
- n = new(Template)
- formatString = "{service}.{namespace}.{type}.{zone}"
- n.SetTemplate(formatString)
-
- queryString = "myservice.mynamespace.svc.coredns"
- splitQuery = strings.Split(queryString, ".")
- expectedZone = "coredns"
- actualZone = n.ZoneFromSegmentArray(splitQuery)
-
- if actualZone != expectedZone {
- t.Errorf("Expected zone name '%v', instead got zone name '%v' for query string '%v' and format '%v'", expectedZone, actualZone, queryString, formatString)
- }
-
- // Case where template does not contain {zone}
- n = new(Template)
- formatString = "{service}.{namespace}.{type}"
- n.SetTemplate(formatString)
-
- queryString = "mynamespace.coredns.svc"
- splitQuery = strings.Split(queryString, ".")
- expectedZone = ""
- actualZone = n.ZoneFromSegmentArray(splitQuery)
-
- if actualZone != expectedZone {
- t.Errorf("Expected zone name '%v', instead got zone name '%v' for query string '%v' and format '%v'", expectedZone, actualZone, queryString, formatString)
- }
-
- // Case where zone is multiple segments
- n = new(Template)
- formatString = "{service}.{namespace}.{type}.{zone}"
- n.SetTemplate(formatString)
-
- queryString = "myservice.mynamespace.svc.coredns.cluster.local"
- splitQuery = strings.Split(queryString, ".")
- expectedZone = "coredns.cluster.local"
- actualZone = n.ZoneFromSegmentArray(splitQuery)
-
- if actualZone != expectedZone {
- t.Errorf("Expected zone name '%v', instead got zone name '%v' for query string '%v' and format '%v'", expectedZone, actualZone, queryString, formatString)
- }
-}
diff --git a/middleware/kubernetes/setup.go b/middleware/kubernetes/setup.go
index 8c733a2df..0813ef7b3 100644
--- a/middleware/kubernetes/setup.go
+++ b/middleware/kubernetes/setup.go
@@ -8,7 +8,6 @@ import (
"github.com/miekg/coredns/core/dnsserver"
"github.com/miekg/coredns/middleware"
- "github.com/miekg/coredns/middleware/kubernetes/nametemplate"
"github.com/mholt/caddy"
unversionedapi "k8s.io/client-go/1.5/pkg/api/unversioned"
@@ -52,8 +51,6 @@ func setup(c *caddy.Controller) error {
func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
k8s := &Kubernetes{ResyncPeriod: defaultResyncPeriod}
- k8s.NameTemplate = new(nametemplate.Template)
- k8s.NameTemplate.SetTemplate(defaultNameTemplate)
k8s.PodMode = PodModeDisabled
for c.Next() {
@@ -99,18 +96,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
continue
}
return nil, c.ArgErr()
-
- case "template":
- args := c.RemainingArgs()
- if len(args) > 0 {
- template := strings.Join(args, "")
- err := k8s.NameTemplate.SetTemplate(template)
- if err != nil {
- return nil, err
- }
- continue
- }
- return nil, c.ArgErr()
case "namespaces":
args := c.RemainingArgs()
if len(args) > 0 {
@@ -164,7 +149,6 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
}
const (
- defaultNameTemplate = "{service}.{namespace}.{type}.{zone}"
defaultResyncPeriod = 5 * time.Minute
defaultPodMode = PodModeDisabled
)
diff --git a/middleware/kubernetes/setup_test.go b/middleware/kubernetes/setup_test.go
index 72b60e5ce..eb327baf3 100644
--- a/middleware/kubernetes/setup_test.go
+++ b/middleware/kubernetes/setup_test.go
@@ -16,7 +16,6 @@ func TestKubernetesParse(t *testing.T) {
shouldErr bool // true if test case is exected to produce an error.
expectedErrContent string // substring from the expected error. Empty for positive cases.
expectedZoneCount int // expected count of defined zones.
- expectedNTValid bool // NameTemplate to be initialized and valid
expectedNSCount int // expected count of namespaces.
expectedResyncPeriod time.Duration // expected resync period value
expectedLabelSelector string // expected label selector value
@@ -28,7 +27,6 @@ func TestKubernetesParse(t *testing.T) {
false,
"",
1,
- true,
0,
defaultResyncPeriod,
"",
@@ -39,7 +37,6 @@ func TestKubernetesParse(t *testing.T) {
false,
"",
2,
- true,
0,
defaultResyncPeriod,
"",
@@ -51,7 +48,6 @@ func TestKubernetesParse(t *testing.T) {
false,
"",
1,
- true,
0,
defaultResyncPeriod,
"",
@@ -64,20 +60,6 @@ func TestKubernetesParse(t *testing.T) {
false,
"",
1,
- true,
- 0,
- defaultResyncPeriod,
- "",
- },
- {
- "template keyword with valid template",
- `kubernetes coredns.local {
- template {service}.{namespace}.{zone}
-}`,
- false,
- "",
- 1,
- true,
0,
defaultResyncPeriod,
"",
@@ -90,7 +72,6 @@ func TestKubernetesParse(t *testing.T) {
false,
"",
1,
- true,
1,
defaultResyncPeriod,
"",
@@ -103,7 +84,6 @@ func TestKubernetesParse(t *testing.T) {
false,
"",
1,
- true,
2,
defaultResyncPeriod,
"",
@@ -116,7 +96,6 @@ func TestKubernetesParse(t *testing.T) {
false,
"",
1,
- true,
0,
30 * time.Second,
"",
@@ -129,7 +108,6 @@ func TestKubernetesParse(t *testing.T) {
false,
"",
1,
- true,
0,
15 * time.Minute,
"",
@@ -142,7 +120,6 @@ func TestKubernetesParse(t *testing.T) {
false,
"",
1,
- true,
0,
defaultResyncPeriod,
"environment=prod",
@@ -155,7 +132,6 @@ func TestKubernetesParse(t *testing.T) {
false,
"",
1,
- true,
0,
defaultResyncPeriod,
"application=nginx,environment in (production,qa,staging)",
@@ -165,14 +141,12 @@ func TestKubernetesParse(t *testing.T) {
`kubernetes coredns.local test.local {
resyncperiod 15m
endpoint http://localhost:8080
- template {service}.{namespace}.{zone}
namespaces demo test
labels environment in (production, staging, qa),application=nginx
}`,
false,
"",
2,
- true,
2,
15 * time.Minute,
"application=nginx,environment in (production,qa,staging)",
@@ -184,7 +158,6 @@ func TestKubernetesParse(t *testing.T) {
true,
"Kubernetes setup called without keyword 'kubernetes' in Corefile",
-1,
- false,
-1,
defaultResyncPeriod,
"",
@@ -195,7 +168,6 @@ func TestKubernetesParse(t *testing.T) {
true,
"Zone name must be provided for kubernetes middleware",
-1,
- true,
0,
defaultResyncPeriod,
"",
@@ -208,34 +180,7 @@ func TestKubernetesParse(t *testing.T) {
true,
"Wrong argument count or unexpected line ending after 'endpoint'",
-1,
- true,
- -1,
- defaultResyncPeriod,
- "",
- },
- {
- "template keyword without a template value",
- `kubernetes coredns.local {
- template
-}`,
- true,
- "Wrong argument count or unexpected line ending after 'template'",
- -1,
- false,
- 0,
- defaultResyncPeriod,
- "",
- },
- {
- "template keyword with an invalid template value",
- `kubernetes coredns.local {
- template {namespace}.{zone}
-}`,
- true,
- "Record name template does not pass NameTemplate validation",
-1,
- false,
- 0,
defaultResyncPeriod,
"",
},
@@ -247,7 +192,6 @@ func TestKubernetesParse(t *testing.T) {
true,
"Parse error: Wrong argument count or unexpected line ending after 'namespaces'",
-1,
- true,
-1,
defaultResyncPeriod,
"",
@@ -260,7 +204,6 @@ func TestKubernetesParse(t *testing.T) {
true,
"Wrong argument count or unexpected line ending after 'resyncperiod'",
-1,
- true,
0,
0 * time.Minute,
"",
@@ -273,7 +216,6 @@ func TestKubernetesParse(t *testing.T) {
true,
"Unable to parse resync duration value. Value provided was ",
-1,
- true,
0,
0 * time.Second,
"",
@@ -286,7 +228,6 @@ func TestKubernetesParse(t *testing.T) {
true,
"Unable to parse resync duration value. Value provided was ",
-1,
- true,
0,
0 * time.Second,
"",
@@ -299,7 +240,6 @@ func TestKubernetesParse(t *testing.T) {
true,
"Wrong argument count or unexpected line ending after 'labels'",
-1,
- true,
0,
0 * time.Second,
"",
@@ -312,7 +252,6 @@ func TestKubernetesParse(t *testing.T) {
true,
"Unable to parse label selector. Value provided was",
-1,
- true,
0,
0 * time.Second,
"",
@@ -354,16 +293,6 @@ func TestKubernetesParse(t *testing.T) {
t.Errorf("Test %d: Expected kubernetes controller to be initialized with %d zones, instead found %d zones: '%v' for input '%s'", i, test.expectedZoneCount, foundZoneCount, k8sController.Zones, test.input)
}
- // NameTemplate
- if k8sController.NameTemplate == nil {
- t.Errorf("Test %d: Expected kubernetes controller to be initialized with a NameTemplate. Instead found '%v' for input '%s'", i, k8sController.NameTemplate, test.input)
- } else {
- foundNTValid := k8sController.NameTemplate.IsValid()
- if foundNTValid != test.expectedNTValid {
- t.Errorf("Test %d: Expected NameTemplate validity to be '%v', instead found '%v' for input '%s'", i, test.expectedNTValid, foundNTValid, test.input)
- }
- }
-
// Namespaces
foundNSCount := len(k8sController.Namespaces)
if foundNSCount != test.expectedNSCount {
diff --git a/test/kubernetes_test.go b/test/kubernetes_test.go
index 824f8eadc..e93bea973 100644
--- a/test/kubernetes_test.go
+++ b/test/kubernetes_test.go
@@ -105,7 +105,7 @@ var dnsTestCases = []test.Case{
},
//TODO: Fix below to all use test.SRV not test.A!
{
- Qname: "_*._*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Qname: "*._TcP.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local. 303 IN SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."),
@@ -113,12 +113,12 @@ var dnsTestCases = []test.Case{
},
},
{
- Qname: "_*._*.bogusservice.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Qname: "*.*.bogusservice.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeNameError,
Answer: []dns.RR{},
},
{
- Qname: "_*._*.svc-1-a.*.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Qname: "*.any.svc-1-a.*.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local. 303 IN SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."),
@@ -126,7 +126,7 @@ var dnsTestCases = []test.Case{
},
},
{
- Qname: "_*._*.svc-1-a.any.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Qname: "ANY.*.svc-1-a.any.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local. 303 IN SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."),
@@ -134,17 +134,17 @@ var dnsTestCases = []test.Case{
},
},
{
- Qname: "_*._*.bogusservice.*.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Qname: "*.*.bogusservice.*.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeNameError,
Answer: []dns.RR{},
},
{
- Qname: "_*._*.bogusservice.any.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Qname: "*.*.bogusservice.any.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeNameError,
Answer: []dns.RR{},
},
{
- Qname: "_c-port._*.*.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Qname: "_c-port._UDP.*.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.SRV("_c-port._udp.svc-c.test-1.svc.cluster.local. 303 IN SRV 10 100 1234 svc-c.test-1.svc.cluster.local."),
@@ -153,7 +153,7 @@ var dnsTestCases = []test.Case{
},
},
{
- Qname: "_*._tcp.any.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Qname: "*._tcp.any.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeSuccess,
Answer: []dns.RR{
test.SRV("_http._tcp.svc-1-a.test-1.svc.cluster.local. 303 IN SRV 10 100 80 svc-1-a.test-1.svc.cluster.local."),
@@ -162,12 +162,12 @@ var dnsTestCases = []test.Case{
},
},
{
- Qname: "_*._*.any.test-2.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Qname: "*.*.any.test-2.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeNameError,
Answer: []dns.RR{},
},
{
- Qname: "_*._*.*.test-2.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Qname: "*.*.*.test-2.svc.cluster.local.", Qtype: dns.TypeSRV,
Rcode: dns.RcodeNameError,
Answer: []dns.RR{},
},
@@ -180,18 +180,18 @@ var dnsTestCases = []test.Case{
},
},
{
- Qname: "_*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
- Rcode: dns.RcodeNameError,
+ Qname: "*.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Rcode: dns.RcodeServerFailure,
Answer: []dns.RR{},
},
{
- Qname: "_*._not-udp-or-tcp.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
- Rcode: dns.RcodeNameError,
+ Qname: "*._not-udp-or-tcp.svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
+ Rcode: dns.RcodeServerFailure,
Answer: []dns.RR{},
},
{
Qname: "svc-1-a.test-1.svc.cluster.local.", Qtype: dns.TypeSRV,
- Rcode: dns.RcodeNameError,
+ Rcode: dns.RcodeServerFailure,
Answer: []dns.RR{},
},
{