-
Notifications
You must be signed in to change notification settings - Fork 22
/
listener.go
245 lines (212 loc) · 6.97 KB
/
listener.go
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
package listenerutil
import (
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io/ioutil"
"net"
"os"
osuser "os/user"
"strconv"
"github.com/hashicorp/go-secure-stdlib/reloadutil"
"github.com/hashicorp/go-secure-stdlib/tlsutil"
"github.com/jefferai/isbadcipher"
"github.com/mitchellh/cli"
)
type Listener struct {
net.Listener
Config ListenerConfig
}
type UnixSocketsConfig struct {
User string `hcl:"user"`
Mode string `hcl:"mode"`
Group string `hcl:"group"`
}
// rmListener is an implementation of net.Listener that forwards most
// calls to the listener but also removes a file as part of the close. We
// use this to cleanup the unix domain socket on close.
type rmListener struct {
net.Listener
Path string
}
func (l *rmListener) Close() error {
// Close the listener itself
if err := l.Listener.Close(); err != nil {
return err
}
// Remove the file
return os.Remove(l.Path)
}
func UnixSocketListener(path string, unixSocketsConfig *UnixSocketsConfig) (net.Listener, error) {
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to remove socket file: %v", err)
}
ln, err := net.Listen("unix", path)
if err != nil {
return nil, err
}
if unixSocketsConfig != nil {
err = setFilePermissions(path, unixSocketsConfig.User, unixSocketsConfig.Group, unixSocketsConfig.Mode)
if err != nil {
return nil, fmt.Errorf("failed to set file system permissions on the socket file: %s", err)
}
}
// Wrap the listener in rmListener so that the Unix domain socket file is
// removed on close.
return &rmListener{
Listener: ln,
Path: path,
}, nil
}
func TLSConfig(
l *ListenerConfig,
props map[string]string,
ui cli.Ui) (*tls.Config, reloadutil.ReloadFunc, error) {
props["tls"] = "disabled"
if l.TLSDisable {
return nil, nil, nil
}
cg := reloadutil.NewCertificateGetter(l.TLSCertFile, l.TLSKeyFile, "")
if err := cg.Reload(); err != nil {
// We try the key without a passphrase first and if we get an incorrect
// passphrase response, try again after prompting for a passphrase
if errors.Is(err, x509.IncorrectPasswordError) {
var passphrase string
passphrase, err = ui.AskSecret(fmt.Sprintf("Enter passphrase for %s:", l.TLSKeyFile))
if err == nil {
cg = reloadutil.NewCertificateGetter(l.TLSCertFile, l.TLSKeyFile, passphrase)
if err = cg.Reload(); err == nil {
goto PASSPHRASECORRECT
}
}
}
return nil, nil, fmt.Errorf("error loading TLS cert: %w", err)
}
PASSPHRASECORRECT:
tlsConf := &tls.Config{
GetCertificate: cg.GetCertificate,
NextProtos: []string{"h2", "http/1.1"},
ClientAuth: tls.RequestClientCert,
PreferServerCipherSuites: l.TLSPreferServerCipherSuites,
}
if l.TLSMinVersion == "" {
l.TLSMinVersion = "tls12"
}
if l.TLSMaxVersion == "" {
l.TLSMaxVersion = "tls13"
}
var ok bool
tlsConf.MinVersion, ok = tlsutil.TLSLookup[l.TLSMinVersion]
if !ok {
return nil, nil, fmt.Errorf("'tls_min_version' value %q not supported, please specify one of [tls10,tls11,tls12,tls13]", l.TLSMinVersion)
}
tlsConf.MaxVersion, ok = tlsutil.TLSLookup[l.TLSMaxVersion]
if !ok {
return nil, nil, fmt.Errorf("'tls_max_version' value %q not supported, please specify one of [tls10,tls11,tls12,tls13]", l.TLSMaxVersion)
}
if tlsConf.MaxVersion < tlsConf.MinVersion {
return nil, nil, fmt.Errorf("'tls_max_version' must be greater than or equal to 'tls_min_version'")
}
if len(l.TLSCipherSuites) > 0 {
// HTTP/2 with TLS 1.2 blacklists several cipher suites.
// https://tools.ietf.org/html/rfc7540#appendix-A
//
// Since the CLI (net/http) automatically uses HTTP/2 with TLS 1.2,
// we check here if all or some specified cipher suites are blacklisted.
badCiphers := []string{}
for _, cipher := range l.TLSCipherSuites {
if isbadcipher.IsBadCipher(cipher) {
// Get the name of the current cipher.
cipherStr, err := tlsutil.GetCipherName(cipher)
if err != nil {
return nil, nil, fmt.Errorf("invalid value for 'tls_cipher_suites': %w", err)
}
badCiphers = append(badCiphers, cipherStr)
}
}
if len(badCiphers) == len(l.TLSCipherSuites) {
ui.Warn(`WARNING! All cipher suites defined by 'tls_cipher_suites' are blacklisted by the
HTTP/2 specification. HTTP/2 communication with TLS 1.2 will not work as intended
and the application may be unavailable.
Please see https://tools.ietf.org/html/rfc7540#appendix-A for further information.`)
} else if len(badCiphers) > 0 {
ui.Warn(fmt.Sprintf(`WARNING! The following cipher suites defined by 'tls_cipher_suites' are
blacklisted by the HTTP/2 specification:
%v
Please see https://tools.ietf.org/html/rfc7540#appendix-A for further information.`, badCiphers))
}
tlsConf.CipherSuites = l.TLSCipherSuites
}
if l.TLSRequireAndVerifyClientCert {
tlsConf.ClientAuth = tls.RequireAndVerifyClientCert
if l.TLSClientCAFile != "" {
caPool := x509.NewCertPool()
data, err := ioutil.ReadFile(l.TLSClientCAFile)
if err != nil {
return nil, nil, fmt.Errorf("failed to read tls_client_ca_file: %w", err)
}
if !caPool.AppendCertsFromPEM(data) {
return nil, nil, fmt.Errorf("failed to parse CA certificate in tls_client_ca_file")
}
tlsConf.ClientCAs = caPool
}
}
if l.TLSDisableClientCerts {
if l.TLSRequireAndVerifyClientCert {
return nil, nil, fmt.Errorf("'tls_disable_client_certs' and 'tls_require_and_verify_client_cert' are mutually exclusive")
}
tlsConf.ClientAuth = tls.NoClientCert
}
props["tls"] = "enabled"
return tlsConf, cg.Reload, nil
}
// setFilePermissions handles configuring ownership and permissions
// settings on a given file. All permission/ownership settings are
// optional. If no user or group is specified, the current user/group
// will be used. Mode is optional, and has no default (the operation is
// not performed if absent). User may be specified by name or ID, but
// group may only be specified by ID.
func setFilePermissions(path string, user, group, mode string) error {
var err error
uid, gid := os.Getuid(), os.Getgid()
if user != "" {
if uid, err = strconv.Atoi(user); err == nil {
goto GROUP
}
// Try looking up the user by name
u, err := osuser.Lookup(user)
if err != nil {
return fmt.Errorf("failed to look up user %q: %v", user, err)
}
uid, _ = strconv.Atoi(u.Uid)
}
GROUP:
if group != "" {
if gid, err = strconv.Atoi(group); err == nil {
goto OWN
}
// Try looking up the user by name
g, err := osuser.LookupGroup(group)
if err != nil {
return fmt.Errorf("failed to look up group %q: %v", user, err)
}
gid, _ = strconv.Atoi(g.Gid)
}
OWN:
if err := os.Chown(path, uid, gid); err != nil {
return fmt.Errorf("failed setting ownership to %d:%d on %q: %v",
uid, gid, path, err)
}
if mode != "" {
mode, err := strconv.ParseUint(mode, 8, 32)
if err != nil {
return fmt.Errorf("invalid mode specified: %v", mode)
}
if err := os.Chmod(path, os.FileMode(mode)); err != nil {
return fmt.Errorf("failed setting permissions to %d on %q: %v",
mode, path, err)
}
}
return nil
}