aboutsummaryrefslogtreecommitdiff
path: root/plugin/pkg/tls/tls.go
blob: 41eff4bc0d72f72370b570da80b697fd21a81c3b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package tls

import (
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"time"
)

func setTLSDefaults(ctls *tls.Config) {
	ctls.MinVersion = tls.VersionTLS12
	ctls.MaxVersion = tls.VersionTLS13
	ctls.CipherSuites = []uint16{
		tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
		tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
		tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
		tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
		tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
		tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
	}
}

// NewTLSConfigFromArgs returns a TLS config based upon the passed
// in list of arguments. Typically these come straight from the
// Corefile.
// no args
//   - creates a Config with no cert and using system CAs
//   - use for a client that talks to a server with a public signed cert (CA installed in system)
//   - the client will not be authenticated by the server since there is no cert
//
// one arg: the path to CA PEM file
//   - creates a Config with no cert using a specific CA
//   - use for a client that talks to a server with a private signed cert (CA not installed in system)
//   - the client will not be authenticated by the server since there is no cert
//
// two args: path to cert PEM file, the path to private key PEM file
//   - creates a Config with a cert, using system CAs to validate the other end
//   - use for:
//   - a server; or,
//   - a client that talks to a server with a public cert and needs certificate-based authentication
//   - the other end will authenticate this end via the provided cert
//   - the cert of the other end will be verified via system CAs
//
// three args: path to cert PEM file, path to client private key PEM file, path to CA PEM file
//   - creates a Config with the cert, using specified CA to validate the other end
//   - use for:
//   - a server; or,
//   - a client that talks to a server with a privately signed cert and needs certificate-based
//     authentication
//   - the other end will authenticate this end via the provided cert
//   - this end will verify the other end's cert using the specified CA
func NewTLSConfigFromArgs(args ...string) (*tls.Config, error) {
	var err error
	var c *tls.Config
	switch len(args) {
	case 0:
		// No client cert, use system CA
		c, err = NewTLSClientConfig("")
	case 1:
		// No client cert, use specified CA
		c, err = NewTLSClientConfig(args[0])
	case 2:
		// Client cert, use system CA
		c, err = NewTLSConfig(args[0], args[1], "")
	case 3:
		// Client cert, use specified CA
		c, err = NewTLSConfig(args[0], args[1], args[2])
	default:
		err = fmt.Errorf("maximum of three arguments allowed for TLS config, found %d", len(args))
	}
	if err != nil {
		return nil, err
	}
	return c, nil
}

// NewTLSConfig returns a TLS config that includes a certificate
// Use for server TLS config or when using a client certificate
// If caPath is empty, system CAs will be used
func NewTLSConfig(certPath, keyPath, caPath string) (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(certPath, keyPath)
	if err != nil {
		return nil, fmt.Errorf("could not load TLS cert: %s", err)
	}

	roots, err := loadRoots(caPath)
	if err != nil {
		return nil, err
	}

	tlsConfig := &tls.Config{Certificates: []tls.Certificate{cert}, RootCAs: roots}
	setTLSDefaults(tlsConfig)

	return tlsConfig, nil
}

// NewTLSClientConfig returns a TLS config for a client connection
// If caPath is empty, system CAs will be used
func NewTLSClientConfig(caPath string) (*tls.Config, error) {
	roots, err := loadRoots(caPath)
	if err != nil {
		return nil, err
	}

	tlsConfig := &tls.Config{RootCAs: roots}
	setTLSDefaults(tlsConfig)

	return tlsConfig, nil
}

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

	roots := x509.NewCertPool()
	pem, err := os.ReadFile(filepath.Clean(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
}

// NewHTTPSTransport returns an HTTP transport configured using tls.Config
func NewHTTPSTransport(cc *tls.Config) *http.Transport {
	tr := &http.Transport{
		Proxy: http.ProxyFromEnvironment,
		Dial: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).Dial,
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig:     cc,
		MaxIdleConnsPerHost: 25,
	}

	return tr
}