mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-31 10:13:14 -04:00 
			
		
		
		
	Add insecure A records for pods (#475)
This commit is contained in:
		
				
					committed by
					
						 Miek Gieben
						Miek Gieben
					
				
			
			
				
	
			
			
			
						parent
						
							b10a4f9075
						
					
				
				
					commit
					0ee88d3007
				
			| @@ -43,21 +43,36 @@ This is the default kubernetes setup, with everything specified in full: | ||||
|         # Kubernetes data API resync period | ||||
|         # Example values: 60s, 5m, 1h | ||||
|         resyncperiod 5m | ||||
| 		 | ||||
|         # Use url for k8s API endpoint | ||||
|         endpoint https://k8sendpoint:8080 | ||||
| 		 | ||||
|         # 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 | ||||
| 		 | ||||
|         # Only expose the records for kubernetes objects | ||||
|         # that match this label selector. The label | ||||
|         # selector syntax is described in the kubernetes | ||||
|         # API documentation: http://kubernetes.io/docs/user-guide/labels/ | ||||
|         # Example selector below only exposes objects tagged as | ||||
|         # "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 | ||||
|     # 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 | ||||
|   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: | ||||
|   | ||||
| @@ -42,8 +42,14 @@ type Kubernetes struct { | ||||
| 	Namespaces    []string | ||||
| 	LabelSelector *unversionedapi.LabelSelector | ||||
| 	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 { | ||||
| 	addr api.EndpointAddress | ||||
| 	port api.EndpointPort | ||||
| @@ -57,6 +63,12 @@ type service struct { | ||||
| 	endpoints []endpoint | ||||
| } | ||||
|  | ||||
| type pod struct { | ||||
| 	name      string | ||||
| 	namespace string | ||||
| 	addr      string | ||||
| } | ||||
|  | ||||
| var errNoItems = errors.New("no items found") | ||||
| var errNsNotExposed = errors.New("namespace is not exposed") | ||||
| 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 | ||||
| 	} | ||||
|  | ||||
| 	// TODO: Implementation above globbed together segments for the serviceName if | ||||
| 	//       multiple segments remained. Determine how to do similar globbing using | ||||
| 	//		 the template-based implementation. | ||||
| 	namespace = k.NameTemplate.NamespaceFromSegmentArray(serviceSegments) | ||||
| 	serviceName = k.NameTemplate.ServiceFromSegmentArray(serviceSegments) | ||||
| 	typeName = k.NameTemplate.TypeFromSegmentArray(serviceSegments) | ||||
| 	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.") | ||||
| @@ -246,16 +255,16 @@ func (k *Kubernetes) Records(name string, exact bool) ([]msg.Service, error) { | ||||
| 		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 { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if len(k8sItems) == 0 { | ||||
| 	if len(services) == 0 && len(pods) == 0 { | ||||
| 		// Did not find item in k8s | ||||
| 		return nil, errNoItems | ||||
| 	} | ||||
|  | ||||
| 	records := k.getRecordsForServiceItems(k8sItems, zone) | ||||
| 	records := k.getRecordsForK8sItems(services, pods, zone) | ||||
| 	return records, nil | ||||
| } | ||||
|  | ||||
| @@ -272,10 +281,10 @@ func endpointHostname(addr api.EndpointAddress) string { | ||||
| 	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 | ||||
|  | ||||
| 	for _, svc := range serviceItems { | ||||
| 	for _, svc := range services { | ||||
|  | ||||
| 		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 | ||||
| } | ||||
|  | ||||
| // Get performs the call to the Kubernetes http API. | ||||
| func (k *Kubernetes) Get(namespace, servicename, endpointname, port, protocol, typeName string) (services []service, err error) { | ||||
| // 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": | ||||
| 		return nil, fmt.Errorf("%v not implemented", typeName) | ||||
| 		pods, err = k.findPods(namespace, servicename) | ||||
| 		return nil, pods, err | ||||
| 	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() | ||||
|  | ||||
| 	var resultItems []service | ||||
|   | ||||
| @@ -54,6 +54,7 @@ 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() { | ||||
| 		if c.Val() == "kubernetes" { | ||||
| @@ -86,6 +87,19 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | ||||
|  | ||||
| 			for c.NextBlock() { | ||||
| 				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": | ||||
| 					args := c.RemainingArgs() | ||||
| 					if len(args) > 0 { | ||||
| @@ -152,4 +166,5 @@ func kubernetesParse(c *caddy.Controller) (*Kubernetes, error) { | ||||
| const ( | ||||
| 	defaultNameTemplate = "{service}.{namespace}.{type}.{zone}" | ||||
| 	defaultResyncPeriod = 5 * time.Minute | ||||
| 	defaultPodMode      = PodModeDisabled | ||||
| ) | ||||
|   | ||||
| @@ -194,6 +194,18 @@ var dnsTestCases = []test.Case{ | ||||
| 		Rcode:  dns.RcodeNameError, | ||||
| 		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, | ||||
| 		Rcode:  dns.RcodeSuccess, | ||||
| @@ -238,6 +250,7 @@ func TestKubernetesIntegration(t *testing.T) { | ||||
| 		#tls admin.pem admin-key.pem ca.pem | ||||
| 		#tls k8s_auth/client2.crt k8s_auth/client2.key k8s_auth/ca2.crt | ||||
| 		namespaces test-1 | ||||
| 		pods insecure | ||||
|     } | ||||
| ` | ||||
| 	server, udp := createTestServer(t, corefile) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user