Skip to content

Commit

Permalink
Support for https for etcd connection
Browse files Browse the repository at this point in the history
This commit adds ability to use TLS transport for etcd.
New logic is applied when the etcd URL has https:// scheme.
TLS parameters are passed in the environment variables:

ETCD_CA_FILE - path to CA certificate. If not specified, then
system-provided certificates are used.

ETCD_CERT_FILE - client certificate
ETCD_KEY_FILE - client key file
- either both of none of this two must be specified

ETCD_TLS_SERVER_NAME - expected CN of the certificate. Useful when
URL points to a different domain from that in server certificate

ETCD_TLS_INSECURE - if set to "1" (or "true" or "yes") makes client
bypass server certificate validation.

Also for unification with other providers and rest of connection
settings, etcd URL is no longer specified in the command line, but
rather in ETCD_URLS environment variable (defaults to
http://localhost:2379). More than one comma-separated URL can be
specified. All of the URLs must start with either http:// or https://

Also, now it possible to communicate with etcd through proxy specified
in standard environment variables
  • Loading branch information
Stan Lagun committed Aug 24, 2017
1 parent 54e10fc commit a928724
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 13 deletions.
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func main() {
case "inmemory":
p, err = provider.NewInMemoryProvider(provider.InMemoryWithDomain(domainFilter), provider.InMemoryWithLogging()), nil
case "coredns", "skydns":
p, err = provider.NewCoreDNSProvider(domainFilter, cfg.ETCD, cfg.DryRun)
p, err = provider.NewCoreDNSProvider(domainFilter, cfg.DryRun)
default:
log.Fatalf("unknown dns provider: %s", cfg.Provider)
}
Expand Down
3 changes: 0 additions & 3 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ type Config struct {
LogFormat string
MetricsAddress string
Debug bool
ETCD string
}

var defaultConfig = &Config{
Expand All @@ -74,7 +73,6 @@ var defaultConfig = &Config{
LogFormat: "text",
MetricsAddress: ":7979",
Debug: false,
ETCD: "http://localhost:2379",
}

// NewConfig returns new Config object
Expand Down Expand Up @@ -104,7 +102,6 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
app.Flag("azure-config-file", "When using the Azure provider, specify the Azure configuration file (required when --provider=azure").Default(defaultConfig.AzureConfigFile).StringVar(&cfg.AzureConfigFile)
app.Flag("azure-resource-group", "When using the Azure provider, override the Azure resource group to use (optional)").Default(defaultConfig.AzureResourceGroup).StringVar(&cfg.AzureResourceGroup)
app.Flag("etcd", "ETCD cluster URI(s) for coredns/skydns provider").Default(defaultConfig.ETCD).StringVar(&cfg.ETCD)

// Flags related to policies
app.Flag("policy", "Modify how DNS records are sychronized between sources and providers (default: sync, options: sync, upsert-only)").Default(defaultConfig.Policy).EnumVar(&cfg.Policy, "sync", "upsert-only")
Expand Down
4 changes: 0 additions & 4 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ var (
LogFormat: "text",
MetricsAddress: ":7979",
Debug: false,
ETCD: "http://localhost:2379",
}

overriddenConfig = &Config{
Expand All @@ -73,7 +72,6 @@ var (
LogFormat: "json",
MetricsAddress: "127.0.0.1:9099",
Debug: true,
ETCD: "http://host:3378,http://host:3379",
}
)

Expand Down Expand Up @@ -119,7 +117,6 @@ func TestParseFlags(t *testing.T) {
"--log-format=json",
"--metrics-address=127.0.0.1:9099",
"--debug",
"--etcd=http://host:3378,http://host:3379",
},
envVars: map[string]string{},
expected: overriddenConfig,
Expand Down Expand Up @@ -149,7 +146,6 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_LOG_FORMAT": "json",
"EXTERNAL_DNS_METRICS_ADDRESS": "127.0.0.1:9099",
"EXTERNAL_DNS_DEBUG": "1",
"EXTERNAL_DNS_ETCD": "http://host:3378,http://host:3379",
},
expected: overriddenConfig,
},
Expand Down
107 changes: 102 additions & 5 deletions provider/coredns.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,16 @@ package provider

import (
"container/list"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"os"
"strings"
"time"

Expand Down Expand Up @@ -135,19 +142,109 @@ func (c etcdClient) DeleteService(key string) error {

}

// loads TLS artifacts and builds tls.Clonfig object
func newTLSConfig(certPath, keyPath, caPath, serverName string, insecure bool) (*tls.Config, error) {
if certPath != "" && keyPath == "" || certPath == "" && keyPath != "" {
return nil, errors.New("either both cert and key or none must be provided")
}
var certificates []tls.Certificate
if certPath != "" {
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
if err != nil {
return nil, fmt.Errorf("could not load TLS cert: %s", err)
}
certificates = append(certificates, cert)
}
roots, err := loadRoots(caPath)
if err != nil {
return nil, err
}

return &tls.Config{
Certificates: certificates,
RootCAs: roots,
InsecureSkipVerify: insecure,
ServerName: serverName,
}, nil
}

// loads CA cert
func loadRoots(caPath string) (*x509.CertPool, error) {
if caPath == "" {
return nil, nil
}

roots := x509.NewCertPool()
pem, err := ioutil.ReadFile(caPath)
if err != nil {
return nil, fmt.Errorf("error reading %s: %s", caPath, err)
}
ok := roots.AppendCertsFromPEM(pem)
if !ok {
return nil, fmt.Errorf("could not read root certs: %s", err)
}
return roots, nil
}

// constructs http.Transport object for https protocol
func newHTTPSTransport(cc *tls.Config) *http.Transport {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: cc,
}
}

// builds etcd client config depending on connection scheme and TLS parameters
func getETCDConfig() (*etcd.Config, error) {
etcdURLsStr := os.Getenv("ETCD_URLS")
if etcdURLsStr == "" {
etcdURLsStr = "http://localhost:2379"
}
etcdURLs := strings.Split(etcdURLsStr, ",")
firstURL := strings.ToLower(etcdURLs[0])
if strings.HasPrefix(firstURL, "http://") {
return &etcd.Config{Endpoints: etcdURLs}, nil
} else if strings.HasPrefix(firstURL, "https://") {
caFile := os.Getenv("ETCD_CA_FILE")
certFile := os.Getenv("ETCD_CERT_FILE")
keyFile := os.Getenv("ETCD_KEY_FILE")
serverName := os.Getenv("ETCD_TLS_SERVER_NAME")
isInsecureStr := strings.ToLower(os.Getenv("ETCD_TLS_INSECURE"))
isInsecure := isInsecureStr == "true" || isInsecureStr == "yes" || isInsecureStr == "1"
tlsConfig, err := newTLSConfig(certFile, keyFile, caFile, serverName, isInsecure)
if err != nil {
return nil, err
}
return &etcd.Config{
Endpoints: etcdURLs,
Transport: newHTTPSTransport(tlsConfig),
}, nil
} else {
return nil, fmt.Errorf("etcd URLs must start with either http:// or https://: %s (%s)", etcdURLsStr, firstURL)
}
}

//newETCDClient is an etcd client constructor
func newETCDClient(etcdURIs string) (skyDNSClient, error) {
cfg := etcd.Config{Endpoints: strings.Split(etcdURIs, ",")}
c, err := etcd.New(cfg)
func newETCDClient() (skyDNSClient, error) {
cfg, err := getETCDConfig()
if err != nil {
return nil, err
}
c, err := etcd.New(*cfg)
if err != nil {
return nil, err
}
return etcdClient{etcd.NewKeysAPI(c)}, nil
}

// NewCoreDNSProvider is a CoreDNS provider constructor
func NewCoreDNSProvider(domainFilter DomainFilter, etcdURIs string, dryRun bool) (Provider, error) {
client, err := newETCDClient(etcdURIs)
func NewCoreDNSProvider(domainFilter DomainFilter, dryRun bool) (Provider, error) {
client, err := newETCDClient()
if err != nil {
return nil, err
}
Expand Down

0 comments on commit a928724

Please sign in to comment.