catalog

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 5, 2023 License: Apache-2.0 Imports: 21 Imported by: 0

README

consul_catalog

Coverage Status

Name

consul_catalog - enables serving A resources for tagged consul services.

Description

This plugin reads services from the Consul Catalog, and serves A records from them when tagged with coredns.enabled. A list of services can also be served from Consul's KV.

⚠️ While running in my home cluster for 3+ years, this is still unstable, alpha software with limited test coverage and a very narrow feature set (that's not likely to change, though).

Installation

Grab a build from the releases, or follow these instructions (tl;dr; add consul_catalog:github.com/unRob/consul_catalog to CoreDNS' plugin.cfg and make coredns). An example Ansible role for installing on a Ubiquiti Edgerouter can be found at unRob/nidito.

Syntax

consul_catalog [TAGS...]

TAG defaults to coredns.enabled, and only services tagged with this exact value will be served by this plugin.

consul_catalog [TAGS...] {
    # the hostname and port to reach consul at
    endpoint URL
    # to enable tls encryption, might need your cluster's CA certificates installed!
    scheme https
    # a consul ACL token
    token TOKEN

    # ACL configuration
    acl_metadata_tag META_TAG
    acl_zone ZONE_NAME ZONE_CIDR [ZONE_CIDR...]

    # Service proxy allows static services to target a Catalog service
    service_proxy PROXY_TAG PROXY_SERVICE

    # Services can have multiple names
    alias_metadata_tag META_TAG_NAME

    # Or be fetched from the KV store at a path or prefix (ending in /)
    static_entries_path CONSUL_KV_PATH
    static_entries_prefix CONSUL_KV_PREFIX/

    # finally, records served can be attached with a default ttl
    ttl TTL
}
  • endpoint (default consul.service.consul:8500) specifies the host and port where to find consul catalog.
  • token specifies the token to authenticate with the consul service, having at least .
  • acl_metadata_tag (default: coredns-acl) specifies the Consul Metadata tag to read ACL rules from. An ACL rule looks like: allow network1; deny network2. Rules are interpreted in order of appearance. If specified, requests will only receive answers when their IP address corresponds to any of the allowed acl_zones' CIDR ranges for a service.
  • acl_zone adds an ACL zone named ZONE_NAME with corresponding ZONE_CIDR range(s).
  • service_proxy If specified, services tagged with PROXY_TAG will respond with the address for PROXY_SERVICE instead.
  • alias_metadata_tag (default: coredns-alias) specifies the Consul Metadata tag to read aliases to setup for service. Aliases are semicolon separated dns prefixes that reply with the same target as the original service. For example: coredns-alias = "*.myservice; client.myservice". Aliases that begin with *., are treated as a wildcard prefix that will match any sub-domains of the zone (and/or dots after the *. prefix).
  • static_entries_path If specified, consul's kv store will be queried at CONSUL_KV_PATH and specified entries will be served before querying for catalog records. The value at CONSUL_KV_PATH must contain json following this schema:
    {
        "staticService": { // matches staticService.{coredns_zone}
            "target": "serviceA", // the name of a service registered with consul
            "acl": ["allow network1", "deny network2"], // a list of ACL rules
            "aliases": ["*.static"] // a list of other names that should also reply with this service's info
        },
        "myServiceProxyService": {
            "target": "@service_proxy", // a run-time alias for service_proxy's PROXY_SERVICE
            "acl": ["allow network1"]
        },
        "my-a-record": {
          "addresses": ["127.0.0.1"], // static addresses for this name; no `target` is provided,
          "acl": ["allow network1"]
        }
    }
    
  • static_entries_prefix If specified, consul's kv store will be queried for all keys under CONSUL_KV_PREFIX and found entries will be served before querying for catalog records. The keys at CONSUL_KV_PREFIX must contain json-encoded values following this schema:
    {
        "target": "serviceC", // the name of a service registered with consul
        "acl": ["allow network1", "deny network2"], // a list of ACL rules
        "aliases": ["qa.business", "demo.business"], // test in prod or live a lie
        // "addresses": ["127.0.0.1"] // static addresses for this name, if no `target` was provided
    }
    
  • ttl (default: 5m) specifies the TTL in golang duration strings returned for matching service queries.

Ready

This plugin reports readiness to the ready plugin. This will happen after it has synced to the Consul Catalog API.

Examples

Handle all the queries in the example.com zone, first by looking into hosts, then consul, and finally a zone file. Queries for services in the catalog at consul.service.consul:8500 with a coredns.enabled tag will be answered with the addresses for $SERVICE_NAME.services.consul. If the service also includes a traefik.enabled tag, queries will be answered with the addresses for traefik.service.consul.


example.com {
    hosts {
        10.0.0.42 fourtytwo.example.com
        fallthrough
    }

    consul_catalog coredns.enabled {
        address localhost:8501
        scheme https
        token CONSUL_ACL_TOKEN

        // Enable ACL
        acl_metada_tag coredns-consul
        // A service with `coredns-acl = "trusted" will only reply to clients in the listed cidr ranges
        acl_zone trusted 10.0.0.0/24 10.0.10.0/24 176.16.0.0/24
        acl_zone guests 192.168.10.0/24
        acl_zone iot 192.168.20.0/24
        acl_zone public 0.0.0.0/0

        static_entries_path dns-records
        static_entries_prefix dns/records/

        ttl 10m
    }

    # if a SOA is specified in this file, it'll be added
    # to responses from consul services
    file zones/example.com
}

consul {
    # Forward all requests to consul
    forward . 10.0.0.42:8600 10.0.0.43:8600 10.0.0.44:8600 {
        policy sequential
    }
}

. {
    forward . 1.1.1.1 8.8.8.8
    errors
    cache
}

Consul configuration

Services registered in the catalog and tagged with TAG will be served by this plugin. If acl_metadata_tag was configured in coredns, services must also provide that key as part of it's metadata.

Consul ACL policy
// Catalog access requires reading service and node data
service_prefix "" {
  policy = "read"
}

node_prefix "" {
  policy = "read"
}

// When using static_entries_(path|prefix), access to the given path/prefix should be granted
// for a path:
key "dns-records" {
  policy = "read"
}

// for a prefix:
key_prefix "dns/records" {
  policy = "read"
}

Documentation

Overview

Copyright © 2022 Roberto Hidalgo <coredns-consul@un.rob.mx> Contributions by Charles Powell, 2023 SPDX-License-Identifier: Apache-2.0

Copyright © 2022 Roberto Hidalgo <coredns-consul@un.rob.mx> Contributions by Charles Powell, 2023 SPDX-License-Identifier: Apache-2.0

Copyright © 2022 Roberto Hidalgo <coredns-consul@un.rob.mx> SPDX-License-Identifier: Apache-2.0

Copyright © 2022 Roberto Hidalgo <coredns-consul@un.rob.mx> SPDX-License-Identifier: Apache-2.0

Copyright © 2022 Roberto Hidalgo <coredns-consul@un.rob.mx> SPDX-License-Identifier: Apache-2.0

Copyright © 2022 Roberto Hidalgo <coredns-consul@un.rob.mx> Contributions by Charles Powell, 2023 SPDX-License-Identifier: Apache-2.0

Copyright © 2022 Roberto Hidalgo <coredns-consul@un.rob.mx> Contributions by Charles Powell, 2023 SPDX-License-Identifier: Apache-2.0

Index

Constants

View Source
const ServiceProxyTag = "@service_proxy"

Variables

View Source
var (
	// RequestBlockCount is the number of DNS requests being blocked.
	RequestBlockCount = promauto.NewCounterVec(prometheus.CounterOpts{
		Namespace: plugin.Namespace,
		Subsystem: pluginName,
		Name:      "blocked_requests_total",
		Help:      "Counter of DNS requests being blocked.",
	}, []string{"server", "view"})
	// RequestACLDeniedCount is the number of DNS requests being filtered.
	RequestACLDeniedCount = promauto.NewCounterVec(prometheus.CounterOpts{
		Namespace: plugin.Namespace,
		Subsystem: pluginName,
		Name:      "denied_requests_total",
		Help:      "Counter of DNS requests being filtered.",
	}, []string{"server", "view"})
	// RequestServedCount is the number of DNS requests being Allowed.
	RequestServedCount = promauto.NewCounterVec(prometheus.CounterOpts{
		Namespace: plugin.Namespace,
		Subsystem: pluginName,
		Name:      "served_requests_total",
		Help:      "Counter of DNS requests being served by plugin.",
	}, []string{"server", "view", "source"})
	// RequestDropCount is the number of DNS requests being dropped.
	RequestDropCount = promauto.NewCounterVec(prometheus.CounterOpts{
		Namespace: plugin.Namespace,
		Subsystem: pluginName,
		Name:      "dropped_requests_total",
		Help:      "Counter of DNS requests being dropped.",
	}, []string{"server", "view"})
)
View Source
var DefaultLookup = func(ctx context.Context, state request.Request, target string) (*dns.Msg, error) {
	recursor := upstream.New()
	req := state.NewWithQuestion(target, dns.TypeA)
	return recursor.Lookup(ctx, req, target, dns.TypeA)
}
View Source
var Log = clog.NewWithPlugin(pluginName)

Functions

func CreateClient

func CreateClient(scheme, endpoint, token string) (catalog Client, kv KVClient, err error)

CreateClient initializes the consul catalog client.

func ProxiedAddressesByProximity added in v0.1.1

func ProxiedAddressesByProximity(source net.IP, svc *Service, target *Service, header dns.RR_Header) []dns.RR

Types

type Catalog

type Catalog struct {
	sync.RWMutex
	Endpoint     string
	Scheme       string
	FQDN         []string
	TTL          uint32
	Token        string
	ProxyService string
	ProxyTag     string
	Networks     map[string][]*net.IPNet
	ACLTag       string
	AliasTag     string
	Next         plugin.Handler
	Zone         string

	Sources []*Watch
	// contains filtered or unexported fields
}

Catalog holds published Consul Catalog services.

func New

func New() *Catalog

New returns a Catalog plugin.

func (*Catalog) LastUpdated

func (c *Catalog) LastUpdated() time.Time

LastUpdated returns the last time services changed.

func (*Catalog) Name

func (c *Catalog) Name() string

Name implements plugin.Handler.

func (*Catalog) Ready

func (c *Catalog) Ready() bool

Ready implements ready.Readiness.

func (*Catalog) ReloadAll

func (c *Catalog) ReloadAll() error

func (*Catalog) ServeDNS

func (c *Catalog) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error)

ServeDNS implements plugin.Handler.

func (*Catalog) ServiceFor

func (c *Catalog) ServiceFor(name string) *Service

func (*Catalog) Services

func (c *Catalog) Services() ServiceMap

Services returns a map of services to their target.

func (*Catalog) SetClients

func (c *Catalog) SetClients(client Client, kv KVClient)

SetClient sets a consul client for a catalog.

type Client

type Client interface {
	Service(string, string, *api.QueryOptions) ([]*api.CatalogService, *api.QueryMeta, error)
	Services(*api.QueryOptions) (map[string][]string, *api.QueryMeta, error)
}

Client is implemented by github.com/hashicorp/consul/api.Catalog.

type KVClient

type KVClient interface {
	Get(key string, opts *api.QueryOptions) (*api.KVPair, *api.QueryMeta, error)
	List(prefix string, opts *api.QueryOptions) (api.KVPairs, *api.QueryMeta, error)
}

KVClient is implemented by github.com/hashicorp/consul/api.Catalog.

type Service

type Service struct {
	Name      string
	Target    string
	ACL       []*ServiceACL
	Addresses []net.IP
}

Service has a target and ACL rules.

func NewService

func NewService(name, target string) *Service

func (Service) RespondsTo

func (s Service) RespondsTo(ip net.IP) bool

RespondsTo returns if a service is allowed to talk to an IP.

type ServiceACL

type ServiceACL struct {
	Action   string
	Networks []*net.IPNet
}

ServiceACL holds an action and corresponding network range.

type ServiceMap

type ServiceMap map[string]*Service

func (ServiceMap) Find

func (s ServiceMap) Find(query string) *Service

type StaticEntries

type StaticEntries map[string]*StaticEntry

type StaticEntry

type StaticEntry struct {
	Target    string   `json:"target"`
	Addresses []string `json:"addresses"`
	ACL       []string `json:"acl"`
	Aliases   []string `json:"aliases"`
}

StaticEntry represents a consul value, json encoded.

type WatcKVPrefix

type WatcKVPrefix struct {
	Prefix string
	// contains filtered or unexported fields
}

func (*WatcKVPrefix) Fetch

func (src *WatcKVPrefix) Fetch(catalog *Catalog, qo *api.QueryOptions) (uint64, error)

func (*WatcKVPrefix) Name

func (src *WatcKVPrefix) Name() string

func (*WatcKVPrefix) Process

func (src *WatcKVPrefix) Process(catalog *Catalog) (ServiceMap, []string, error)

type Watch

type Watch struct {
	sync.RWMutex
	LastIndex uint64
	// contains filtered or unexported fields
}

func NewWatch

func NewWatch(impl WatchType) *Watch

func (*Watch) Get

func (w *Watch) Get(name string) *Service

func (*Watch) Known

func (w *Watch) Known() ServiceMap

func (*Watch) Name

func (w *Watch) Name() string

func (*Watch) Ready

func (w *Watch) Ready() bool

func (*Watch) Resolve

func (w *Watch) Resolve(catalog *Catalog) (bool, error)

type WatchConsulCatalog

type WatchConsulCatalog struct {
	Tag string
	// contains filtered or unexported fields
}

func (*WatchConsulCatalog) Fetch

func (src *WatchConsulCatalog) Fetch(catalog *Catalog, qo *api.QueryOptions) (uint64, error)

func (*WatchConsulCatalog) Name

func (src *WatchConsulCatalog) Name() string

func (*WatchConsulCatalog) Process

func (src *WatchConsulCatalog) Process(catalog *Catalog) (ServiceMap, []string, error)

type WatchKVPath

type WatchKVPath struct {
	Key string
	// contains filtered or unexported fields
}

func (*WatchKVPath) Fetch

func (src *WatchKVPath) Fetch(catalog *Catalog, qo *api.QueryOptions) (uint64, error)

func (*WatchKVPath) Name

func (src *WatchKVPath) Name() string

func (*WatchKVPath) Process

func (src *WatchKVPath) Process(catalog *Catalog) (ServiceMap, []string, error)

type WatchType

type WatchType interface {
	Name() string
	Fetch(*Catalog, *api.QueryOptions) (uint64, error)
	Process(*Catalog) (ServiceMap, []string, error)
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL