Add insecure A records for pods (#475)
This commit is contained in:
parent
b10a4f9075
commit
0ee88d3007
4 changed files with 111 additions and 17 deletions
|
@ -43,21 +43,36 @@ This is the default kubernetes setup, with everything specified in full:
|
||||||
# Kubernetes data API resync period
|
# Kubernetes data API resync period
|
||||||
# Example values: 60s, 5m, 1h
|
# Example values: 60s, 5m, 1h
|
||||||
resyncperiod 5m
|
resyncperiod 5m
|
||||||
|
|
||||||
# Use url for k8s API endpoint
|
# Use url for k8s API endpoint
|
||||||
endpoint https://k8sendpoint:8080
|
endpoint https://k8sendpoint:8080
|
||||||
|
|
||||||
# The tls cert, key and the CA cert filenames
|
# The tls cert, key and the CA cert filenames
|
||||||
tls cert key cacert
|
tls cert key cacert
|
||||||
|
|
||||||
# Assemble k8s record names with the template
|
# Assemble k8s record names with the template
|
||||||
template {service}.{namespace}.{type}.{zone}
|
template {service}.{namespace}.{type}.{zone}
|
||||||
|
|
||||||
# Only expose the k8s namespace "demo"
|
# Only expose the k8s namespace "demo"
|
||||||
namespaces demo
|
namespaces demo
|
||||||
|
|
||||||
# Only expose the records for kubernetes objects
|
# Only expose the records for kubernetes objects
|
||||||
# that match this label selector. The label
|
# that match this label selector. The label
|
||||||
# selector syntax is described in the kubernetes
|
# selector syntax is described in the kubernetes
|
||||||
# API documentation: http://kubernetes.io/docs/user-guide/labels/
|
# API documentation: http://kubernetes.io/docs/user-guide/labels/
|
||||||
# Example selector below only exposes objects tagged as
|
# Example selector below only exposes objects tagged as
|
||||||
# "application=nginx" in the staging or qa environments.
|
# "application=nginx" in the staging or qa environments.
|
||||||
labels environment in (staging, qa),application=nginx
|
#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
|
||||||
}
|
}
|
||||||
# Perform DNS response caching for the coredns.local zone
|
# Perform DNS response caching for the coredns.local zone
|
||||||
# Cache timeout is specified by an integer in seconds
|
# Cache timeout is specified by an integer in seconds
|
||||||
|
@ -72,6 +87,7 @@ Defaults:
|
||||||
* The `labels` keyword is only used when filtering results based on kubernetes label selector syntax
|
* 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:
|
is required. The label selector syntax is described in the kubernetes API documentation at:
|
||||||
http://kubernetes.io/docs/user-guide/labels/
|
http://kubernetes.io/docs/user-guide/labels/
|
||||||
|
* If the `pods` keyword is omitted, all pod type requests will result in NXDOMAIN
|
||||||
|
|
||||||
### Template Syntax
|
### Template Syntax
|
||||||
Record name templates can be constructed using the symbolic elements:
|
Record name templates can be constructed using the symbolic elements:
|
||||||
|
|
|
@ -42,8 +42,14 @@ type Kubernetes struct {
|
||||||
Namespaces []string
|
Namespaces []string
|
||||||
LabelSelector *unversionedapi.LabelSelector
|
LabelSelector *unversionedapi.LabelSelector
|
||||||
Selector *labels.Selector
|
Selector *labels.Selector
|
||||||
|
PodMode string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
PodModeDisabled = "disabled" // default. pod requests are ignored
|
||||||
|
PodModeInsecure = "insecure" // ALL pod requests are answered without verfying they exist
|
||||||
|
)
|
||||||
|
|
||||||
type endpoint struct {
|
type endpoint struct {
|
||||||
addr api.EndpointAddress
|
addr api.EndpointAddress
|
||||||
port api.EndpointPort
|
port api.EndpointPort
|
||||||
|
@ -57,6 +63,12 @@ type service struct {
|
||||||
endpoints []endpoint
|
endpoints []endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pod struct {
|
||||||
|
name string
|
||||||
|
namespace string
|
||||||
|
addr string
|
||||||
|
}
|
||||||
|
|
||||||
var errNoItems = errors.New("no items found")
|
var errNoItems = errors.New("no items found")
|
||||||
var errNsNotExposed = errors.New("namespace is not exposed")
|
var errNsNotExposed = errors.New("namespace is not exposed")
|
||||||
var errInvalidRequest = errors.New("invalid query name")
|
var errInvalidRequest = errors.New("invalid query name")
|
||||||
|
@ -221,12 +233,9 @@ func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) {
|
||||||
return nil, errNoItems
|
return nil, errNoItems
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implementation above globbed together segments for the serviceName if
|
serviceName = serviceSegments[0]
|
||||||
// multiple segments remained. Determine how to do similar globbing using
|
namespace = serviceSegments[1]
|
||||||
// the template-based implementation.
|
typeName = serviceSegments[2]
|
||||||
namespace = k.NameTemplate.NamespaceFromSegmentArray(serviceSegments)
|
|
||||||
serviceName = k.NameTemplate.ServiceFromSegmentArray(serviceSegments)
|
|
||||||
typeName = k.NameTemplate.TypeFromSegmentArray(serviceSegments)
|
|
||||||
|
|
||||||
if namespace == "" {
|
if namespace == "" {
|
||||||
err := errors.New("Parsing query string did not produce a namespace value. Assuming wildcard namespace.")
|
err := errors.New("Parsing query string did not produce a namespace value. Assuming wildcard namespace.")
|
||||||
|
@ -246,16 +255,16 @@ func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) {
|
||||||
return nil, errNsNotExposed
|
return nil, errNsNotExposed
|
||||||
}
|
}
|
||||||
|
|
||||||
k8sItems, err := k.Get(namespace, serviceName, endpointname, port, protocol, typeName)
|
services, pods, err := k.Get(namespace, serviceName, endpointname, port, protocol, typeName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(k8sItems) == 0 {
|
if len(services) == 0 && len(pods) == 0 {
|
||||||
// Did not find item in k8s
|
// Did not find item in k8s
|
||||||
return nil, errNoItems
|
return nil, errNoItems
|
||||||
}
|
}
|
||||||
|
|
||||||
records := k.getRecordsForServiceItems(k8sItems, zone)
|
records := k.getRecordsForK8sItems(services, pods, zone)
|
||||||
return records, nil
|
return records, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,10 +281,10 @@ func endpointHostname(addr api.EndpointAddress) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kubernetes) getRecordsForServiceItems(serviceItems []service, zone string) []msg.Service {
|
func (k *Kubernetes) getRecordsForK8sItems(services []service, pods []pod, zone string) []msg.Service {
|
||||||
var records []msg.Service
|
var records []msg.Service
|
||||||
|
|
||||||
for _, svc := range serviceItems {
|
for _, svc := range services {
|
||||||
|
|
||||||
key := svc.name + "." + svc.namespace + ".svc." + zone
|
key := svc.name + "." + svc.namespace + ".svc." + zone
|
||||||
|
|
||||||
|
@ -299,20 +308,61 @@ func (k *Kubernetes) getRecordsForServiceItems(serviceItems []service, zone stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, p := range pods {
|
||||||
|
key := p.name + "." + p.namespace + ".pod." + zone
|
||||||
|
s := msg.Service{
|
||||||
|
Key: msg.Path(strings.ToLower(key), "coredns"),
|
||||||
|
Host: p.addr,
|
||||||
|
}
|
||||||
|
records = append(records, s)
|
||||||
|
}
|
||||||
|
|
||||||
return records
|
return records
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get performs the call to the Kubernetes http API.
|
// Get retrieves matching data from the cache.
|
||||||
func (k *Kubernetes) Get(namespace, servicename, endpointname, port, protocol, typeName string) (services []service, err error) {
|
func (k *Kubernetes) Get(namespace, servicename, endpointname, port, protocol, typeName string) (services []service, pods []pod, err error) {
|
||||||
switch {
|
switch {
|
||||||
case typeName == "pod":
|
case typeName == "pod":
|
||||||
return nil, fmt.Errorf("%v not implemented", typeName)
|
pods, err = k.findPods(namespace, servicename)
|
||||||
|
return nil, pods, err
|
||||||
default:
|
default:
|
||||||
return k.getServices(namespace, servicename, endpointname, port, protocol)
|
services, err = k.findServices(namespace, servicename, endpointname, port, protocol)
|
||||||
|
return services, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kubernetes) getServices(namespace, servicename, endpointname, port, protocol string) ([]service, error) {
|
func ipFromPodName(podname string) string {
|
||||||
|
if strings.Count(podname, "-") == 3 && !strings.Contains(podname, "--") {
|
||||||
|
return strings.Replace(podname, "-", ".", -1)
|
||||||
|
}
|
||||||
|
return strings.Replace(podname, "-", ":", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Kubernetes) findPods(namespace, podname string) (pods []pod, err error) {
|
||||||
|
if k.PodMode == PodModeDisabled {
|
||||||
|
return pods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ip string
|
||||||
|
if strings.Count(podname, "-") == 3 && !strings.Contains(podname, "--") {
|
||||||
|
ip = strings.Replace(podname, "-", ".", -1)
|
||||||
|
} else {
|
||||||
|
ip = strings.Replace(podname, "-", ":", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if k.PodMode == PodModeInsecure {
|
||||||
|
s := pod{name: podname, namespace: namespace, addr: ip}
|
||||||
|
pods = append(pods, s)
|
||||||
|
return pods, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement cache verified pod responses
|
||||||
|
return pods, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Kubernetes) findServices(namespace, servicename, endpointname, port, protocol string) ([]service, error) {
|
||||||
serviceList := k.APIConn.ServiceList()
|
serviceList := k.APIConn.ServiceList()
|
||||||
|
|
||||||
var resultItems []service
|
var resultItems []service
|
||||||
|
|
|
@ -54,6 +54,7 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
|
||||||
k8s := &Kubernetes{ResyncPeriod: defaultResyncPeriod}
|
k8s := &Kubernetes{ResyncPeriod: defaultResyncPeriod}
|
||||||
k8s.NameTemplate = new(nametemplate.Template)
|
k8s.NameTemplate = new(nametemplate.Template)
|
||||||
k8s.NameTemplate.SetTemplate(defaultNameTemplate)
|
k8s.NameTemplate.SetTemplate(defaultNameTemplate)
|
||||||
|
k8s.PodMode = PodModeDisabled
|
||||||
|
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
if c.Val() == "kubernetes" {
|
if c.Val() == "kubernetes" {
|
||||||
|
@ -86,6 +87,19 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
|
||||||
|
|
||||||
for c.NextBlock() {
|
for c.NextBlock() {
|
||||||
switch c.Val() {
|
switch c.Val() {
|
||||||
|
case "pods":
|
||||||
|
args := c.RemainingArgs()
|
||||||
|
if len(args) == 1 {
|
||||||
|
switch args[0] {
|
||||||
|
case PodModeDisabled, PodModeInsecure:
|
||||||
|
k8s.PodMode = args[0]
|
||||||
|
default:
|
||||||
|
return nil, errors.New("pods must be one of: disabled, insecure")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, c.ArgErr()
|
||||||
|
|
||||||
case "template":
|
case "template":
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
|
@ -152,4 +166,5 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) {
|
||||||
const (
|
const (
|
||||||
defaultNameTemplate = "{service}.{namespace}.{type}.{zone}"
|
defaultNameTemplate = "{service}.{namespace}.{type}.{zone}"
|
||||||
defaultResyncPeriod = 5 * time.Minute
|
defaultResyncPeriod = 5 * time.Minute
|
||||||
|
defaultPodMode = PodModeDisabled
|
||||||
)
|
)
|
||||||
|
|
|
@ -194,6 +194,18 @@ var dnsTestCases = []test.Case{
|
||||||
Rcode: dns.RcodeNameError,
|
Rcode: dns.RcodeNameError,
|
||||||
Answer: []dns.RR{},
|
Answer: []dns.RR{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Qname: "10-20-0-101.test-1.pod.cluster.local.", Qtype: dns.TypeA,
|
||||||
|
Rcode: dns.RcodeSuccess,
|
||||||
|
Answer: []dns.RR{
|
||||||
|
test.A("10-20-0-101.test-1.pod.cluster.local. 0 IN A 10.20.0.101"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Qname: "10-20-0-101.test-X.pod.cluster.local.", Qtype: dns.TypeA,
|
||||||
|
Rcode: dns.RcodeNameError,
|
||||||
|
Answer: []dns.RR{},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Qname: "123.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
Qname: "123.0.0.10.in-addr.arpa.", Qtype: dns.TypePTR,
|
||||||
Rcode: dns.RcodeSuccess,
|
Rcode: dns.RcodeSuccess,
|
||||||
|
@ -238,6 +250,7 @@ func TestKubernetesIntegration(t *testing.T) {
|
||||||
#tls admin.pem admin-key.pem ca.pem
|
#tls admin.pem admin-key.pem ca.pem
|
||||||
#tls k8s_auth/client2.crt k8s_auth/client2.key k8s_auth/ca2.crt
|
#tls k8s_auth/client2.crt k8s_auth/client2.key k8s_auth/ca2.crt
|
||||||
namespaces test-1
|
namespaces test-1
|
||||||
|
pods insecure
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
server, udp := createTestServer(t, corefile)
|
server, udp := createTestServer(t, corefile)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue