aboutsummaryrefslogtreecommitdiff
path: root/https
diff options
context:
space:
mode:
authorJulien Pivotto <roidelapluie@inuits.eu>2020-05-01 14:26:51 +0200
committerGitHub <noreply@github.com>2020-05-01 14:26:51 +0200
commit202ecf9c9d1d1960cc9cac24838d13e9cff5edca (patch)
tree114dd7799aad5b6d8241bcf4756565ed16aa7f59 /https
parentb42819b69d4d6f81c9b13ab6c7cefcb7aa6a395a (diff)
downloadprometheus_node_collector-202ecf9c9d1d1960cc9cac24838d13e9cff5edca.tar.bz2
prometheus_node_collector-202ecf9c9d1d1960cc9cac24838d13e9cff5edca.tar.xz
prometheus_node_collector-202ecf9c9d1d1960cc9cac24838d13e9cff5edca.zip
Add basic authentication (#1683)
* Add basic authentication Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
Diffstat (limited to 'https')
-rw-r--r--https/README.md23
-rw-r--r--https/testdata/tls_config_auth_user_list_invalid.bad.yml5
-rw-r--r--https/testdata/tls_config_junk_key.yml2
-rw-r--r--https/testdata/tls_config_noAuth_certPath_keyPath_empty.bad.yml3
-rw-r--r--https/testdata/tls_config_users.good.yml8
-rw-r--r--https/testdata/tls_config_users_noTLS.good.yml5
-rw-r--r--https/tls_config.go110
-rw-r--r--https/tls_config_test.go117
-rw-r--r--https/users.go73
9 files changed, 297 insertions, 49 deletions
diff --git a/https/README.md b/https/README.md
index e8e4504..70b321e 100644
--- a/https/README.md
+++ b/https/README.md
@@ -25,4 +25,27 @@ tls_config:
25 25
26 # CA certificate for client certificate authentication to the server 26 # CA certificate for client certificate authentication to the server
27 [ client_ca_file: <filename> ] 27 [ client_ca_file: <filename> ]
28
29# List of usernames and hashed passwords that have full access to the web
30# server via basic authentication. If empty, no basic authentication is
31# required. Passwords are hashed with bcrypt.
32basic_auth_users:
33 [ <username>: <password> ... ]
28``` 34```
35
36## About bcrypt
37
38There are several tools out there to generate bcrypt passwords, e.g.
39[htpasswd](https://httpd.apache.org/docs/2.4/programs/htpasswd.html):
40
41`htpasswd -nBC 10 "" | tr -d ':\n`
42
43That command will prompt you for a password and output the hashed password,
44which will look something like:
45`$2y$10$X0h1gDsPszWURQaxFh.zoubFi6DXncSjhoQNJgRrnGs7EsimhC7zG`
46
47The cost (10 in the example) influences the time it takes for computing the
48hash. A higher cost will en up slowing down the authentication process.
49Depending on the machine, a cost of 10 will take about ~70ms where a cost of
5018 can take up to a few seconds. That hash will be computed on every
51password-protected request.
diff --git a/https/testdata/tls_config_auth_user_list_invalid.bad.yml b/https/testdata/tls_config_auth_user_list_invalid.bad.yml
new file mode 100644
index 0000000..90c1d95
--- /dev/null
+++ b/https/testdata/tls_config_auth_user_list_invalid.bad.yml
@@ -0,0 +1,5 @@
1tls_config :
2 cert_file : "testdata/server.crt"
3 key_file : "testdata/server.key"
4basic_auth_users:
5 john: doe
diff --git a/https/testdata/tls_config_junk_key.yml b/https/testdata/tls_config_junk_key.yml
new file mode 100644
index 0000000..acb2cc3
--- /dev/null
+++ b/https/testdata/tls_config_junk_key.yml
@@ -0,0 +1,2 @@
1tls_config :
2 cert_filse: "testdata/server.crt"
diff --git a/https/testdata/tls_config_noAuth_certPath_keyPath_empty.bad.yml b/https/testdata/tls_config_noAuth_certPath_keyPath_empty.bad.yml
index 2ed9195..1511b5a 100644
--- a/https/testdata/tls_config_noAuth_certPath_keyPath_empty.bad.yml
+++ b/https/testdata/tls_config_noAuth_certPath_keyPath_empty.bad.yml
@@ -1,3 +1,4 @@
1tls_config : 1tls_config :
2 cert_file : "" 2 cert_file : ""
3 key_file : "" \ No newline at end of file 3 key_file : ""
4 client_auth_type: "x"
diff --git a/https/testdata/tls_config_users.good.yml b/https/testdata/tls_config_users.good.yml
new file mode 100644
index 0000000..278177d
--- /dev/null
+++ b/https/testdata/tls_config_users.good.yml
@@ -0,0 +1,8 @@
1tls_config :
2 cert_file : "testdata/server.crt"
3 key_file : "testdata/server.key"
4basic_auth_users:
5 alice: $2y$12$1DpfPeqF9HzHJt.EWswy1exHluGfbhnn3yXhR7Xes6m3WJqFg0Wby
6 bob: $2y$18$4VeFDzXIoPHKnKTU3O3GH.N.vZu06CVqczYZ8WvfzrddFU6tGqjR.
7 carol: $2y$10$qRTBuFoULoYNA7AQ/F3ck.trZBPyjV64.oA4ZsSBCIWvXuvQlQTuu
8 dave: $2y$10$2UXri9cIDdgeKjBo4Rlpx.U3ZLDV8X1IxKmsfOvhcM5oXQt/mLmXq
diff --git a/https/testdata/tls_config_users_noTLS.good.yml b/https/testdata/tls_config_users_noTLS.good.yml
new file mode 100644
index 0000000..d3a7987
--- /dev/null
+++ b/https/testdata/tls_config_users_noTLS.good.yml
@@ -0,0 +1,5 @@
1basic_auth_users:
2 alice: $2y$12$1DpfPeqF9HzHJt.EWswy1exHluGfbhnn3yXhR7Xes6m3WJqFg0Wby
3 bob: $2y$18$4VeFDzXIoPHKnKTU3O3GH.N.vZu06CVqczYZ8WvfzrddFU6tGqjR.
4 carol: $2y$10$qRTBuFoULoYNA7AQ/F3ck.trZBPyjV64.oA4ZsSBCIWvXuvQlQTuu
5 dave: $2y$10$2UXri9cIDdgeKjBo4Rlpx.U3ZLDV8X1IxKmsfOvhcM5oXQt/mLmXq
diff --git a/https/tls_config.go b/https/tls_config.go
index 4b29862..44e57e9 100644
--- a/https/tls_config.go
+++ b/https/tls_config.go
@@ -20,12 +20,20 @@ import (
20 "io/ioutil" 20 "io/ioutil"
21 "net/http" 21 "net/http"
22 22
23 "github.com/go-kit/kit/log"
24 "github.com/go-kit/kit/log/level"
23 "github.com/pkg/errors" 25 "github.com/pkg/errors"
26 config_util "github.com/prometheus/common/config"
24 "gopkg.in/yaml.v2" 27 "gopkg.in/yaml.v2"
25) 28)
26 29
30var (
31 errNoTLSConfig = errors.New("TLS config is not present")
32)
33
27type Config struct { 34type Config struct {
28 TLSConfig TLSStruct `yaml:"tls_config"` 35 TLSConfig TLSStruct `yaml:"tls_config"`
36 Users map[string]config_util.Secret `yaml:"basic_auth_users"`
29} 37}
30 38
31type TLSStruct struct { 39type TLSStruct struct {
@@ -35,13 +43,18 @@ type TLSStruct struct {
35 ClientCAs string `yaml:"client_ca_file"` 43 ClientCAs string `yaml:"client_ca_file"`
36} 44}
37 45
38func getTLSConfig(configPath string) (*tls.Config, error) { 46func getConfig(configPath string) (*Config, error) {
39 content, err := ioutil.ReadFile(configPath) 47 content, err := ioutil.ReadFile(configPath)
40 if err != nil { 48 if err != nil {
41 return nil, err 49 return nil, err
42 } 50 }
43 c := &Config{} 51 c := &Config{}
44 err = yaml.Unmarshal(content, c) 52 err = yaml.UnmarshalStrict(content, c)
53 return c, err
54}
55
56func getTLSConfig(configPath string) (*tls.Config, error) {
57 c, err := getConfig(configPath)
45 if err != nil { 58 if err != nil {
46 return nil, err 59 return nil, err
47 } 60 }
@@ -50,14 +63,18 @@ func getTLSConfig(configPath string) (*tls.Config, error) {
50 63
51// ConfigToTLSConfig generates the golang tls.Config from the TLSStruct config. 64// ConfigToTLSConfig generates the golang tls.Config from the TLSStruct config.
52func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) { 65func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) {
53 cfg := &tls.Config{ 66 if c.TLSCertPath == "" && c.TLSKeyPath == "" && c.ClientAuth == "" && c.ClientCAs == "" {
54 MinVersion: tls.VersionTLS12, 67 return nil, errNoTLSConfig
55 } 68 }
56 if len(c.TLSCertPath) == 0 { 69
57 return nil, errors.New("missing TLSCertPath") 70 if c.TLSCertPath == "" {
71 return nil, errors.New("missing cert_file")
58 } 72 }
59 if len(c.TLSKeyPath) == 0 { 73 if c.TLSKeyPath == "" {
60 return nil, errors.New("missing TLSKeyPath") 74 return nil, errors.New("missing key_file")
75 }
76 cfg := &tls.Config{
77 MinVersion: tls.VersionTLS12,
61 } 78 }
62 loadCert := func() (*tls.Certificate, error) { 79 loadCert := func() (*tls.Certificate, error) {
63 cert, err := tls.LoadX509KeyPair(c.TLSCertPath, c.TLSKeyPath) 80 cert, err := tls.LoadX509KeyPair(c.TLSCertPath, c.TLSKeyPath)
@@ -74,7 +91,7 @@ func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) {
74 return loadCert() 91 return loadCert()
75 } 92 }
76 93
77 if len(c.ClientCAs) > 0 { 94 if c.ClientCAs != "" {
78 clientCAPool := x509.NewCertPool() 95 clientCAPool := x509.NewCertPool()
79 clientCAFile, err := ioutil.ReadFile(c.ClientCAs) 96 clientCAFile, err := ioutil.ReadFile(c.ClientCAs)
80 if err != nil { 97 if err != nil {
@@ -83,40 +100,67 @@ func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) {
83 clientCAPool.AppendCertsFromPEM(clientCAFile) 100 clientCAPool.AppendCertsFromPEM(clientCAFile)
84 cfg.ClientCAs = clientCAPool 101 cfg.ClientCAs = clientCAPool
85 } 102 }
86 if len(c.ClientAuth) > 0 { 103
87 switch s := (c.ClientAuth); s { 104 switch c.ClientAuth {
88 case "NoClientCert": 105 case "RequestClientCert":
89 cfg.ClientAuth = tls.NoClientCert 106 cfg.ClientAuth = tls.RequestClientCert
90 case "RequestClientCert": 107 case "RequireClientCert":
91 cfg.ClientAuth = tls.RequestClientCert 108 cfg.ClientAuth = tls.RequireAnyClientCert
92 case "RequireClientCert": 109 case "VerifyClientCertIfGiven":
93 cfg.ClientAuth = tls.RequireAnyClientCert 110 cfg.ClientAuth = tls.VerifyClientCertIfGiven
94 case "VerifyClientCertIfGiven": 111 case "RequireAndVerifyClientCert":
95 cfg.ClientAuth = tls.VerifyClientCertIfGiven 112 cfg.ClientAuth = tls.RequireAndVerifyClientCert
96 case "RequireAndVerifyClientCert": 113 case "", "NoClientCert":
97 cfg.ClientAuth = tls.RequireAndVerifyClientCert 114 cfg.ClientAuth = tls.NoClientCert
98 case "": 115 default:
99 cfg.ClientAuth = tls.NoClientCert 116 return nil, errors.New("Invalid ClientAuth: " + c.ClientAuth)
100 default:
101 return nil, errors.New("Invalid ClientAuth: " + s)
102 }
103 } 117 }
104 if len(c.ClientCAs) > 0 && cfg.ClientAuth == tls.NoClientCert { 118
119 if c.ClientCAs != "" && cfg.ClientAuth == tls.NoClientCert {
105 return nil, errors.New("Client CA's have been configured without a Client Auth Policy") 120 return nil, errors.New("Client CA's have been configured without a Client Auth Policy")
106 } 121 }
122
107 return cfg, nil 123 return cfg, nil
108} 124}
109 125
110// Listen starts the server on the given address. If tlsConfigPath isn't empty the server connection will be started using TLS. 126// Listen starts the server on the given address. If tlsConfigPath isn't empty the server connection will be started using TLS.
111func Listen(server *http.Server, tlsConfigPath string) error { 127func Listen(server *http.Server, tlsConfigPath string, logger log.Logger) error {
112 if (tlsConfigPath) == "" { 128 if tlsConfigPath == "" {
129 level.Info(logger).Log("msg", "TLS is disabled and it cannot be enabled on the fly.")
113 return server.ListenAndServe() 130 return server.ListenAndServe()
114 } 131 }
115 var err error 132
116 server.TLSConfig, err = getTLSConfig(tlsConfigPath) 133 if err := validateUsers(tlsConfigPath); err != nil {
117 if err != nil {
118 return err 134 return err
119 } 135 }
136
137 // Setup basic authentication.
138 var handler http.Handler = http.DefaultServeMux
139 if server.Handler != nil {
140 handler = server.Handler
141 }
142 server.Handler = &userAuthRoundtrip{
143 tlsConfigPath: tlsConfigPath,
144 logger: logger,
145 handler: handler,
146 }
147
148 config, err := getTLSConfig(tlsConfigPath)
149 switch err {
150 case nil:
151 // Valid TLS config.
152 level.Info(logger).Log("msg", "TLS is enabled and it cannot be disabled on the fly.")
153 case errNoTLSConfig:
154 // No TLS config, back to plain HTTP.
155 level.Info(logger).Log("msg", "TLS is disabled and it cannot be enabled on the fly.")
156 return server.ListenAndServe()
157 default:
158 // Invalid TLS config.
159 return err
160 }
161
162 server.TLSConfig = config
163
120 // Set the GetConfigForClient method of the HTTPS server so that the config 164 // Set the GetConfigForClient method of the HTTPS server so that the config
121 // and certs are reloaded on new connections. 165 // and certs are reloaded on new connections.
122 server.TLSConfig.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) { 166 server.TLSConfig.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) {
diff --git a/https/tls_config_test.go b/https/tls_config_test.go
index 4b1b4e0..07f412a 100644
--- a/https/tls_config_test.go
+++ b/https/tls_config_test.go
@@ -28,7 +28,8 @@ import (
28) 28)
29 29
30var ( 30var (
31 port = getPort() 31 port = getPort()
32 testlogger = &testLogger{}
32 33
33 ErrorMap = map[string]*regexp.Regexp{ 34 ErrorMap = map[string]*regexp.Regexp{
34 "HTTP Response to HTTPS": regexp.MustCompile(`server gave HTTP response to HTTPS client`), 35 "HTTP Response to HTTPS": regexp.MustCompile(`server gave HTTP response to HTTPS client`),
@@ -38,12 +39,21 @@ var (
38 "Invalid ClientAuth": regexp.MustCompile(`invalid ClientAuth`), 39 "Invalid ClientAuth": regexp.MustCompile(`invalid ClientAuth`),
39 "TLS handshake": regexp.MustCompile(`tls`), 40 "TLS handshake": regexp.MustCompile(`tls`),
40 "HTTP Request to HTTPS server": regexp.MustCompile(`HTTP`), 41 "HTTP Request to HTTPS server": regexp.MustCompile(`HTTP`),
41 "Invalid CertPath": regexp.MustCompile(`missing TLSCertPath`), 42 "Invalid CertPath": regexp.MustCompile(`missing cert_file`),
42 "Invalid KeyPath": regexp.MustCompile(`missing TLSKeyPath`), 43 "Invalid KeyPath": regexp.MustCompile(`missing key_file`),
43 "ClientCA set without policy": regexp.MustCompile(`Client CA's have been configured without a Client Auth Policy`), 44 "ClientCA set without policy": regexp.MustCompile(`Client CA's have been configured without a Client Auth Policy`),
45 "Bad password": regexp.MustCompile(`hashedSecret too short to be a bcrypted password`),
46 "Unauthorized": regexp.MustCompile(`Unauthorized`),
47 "Forbidden": regexp.MustCompile(`Forbidden`),
44 } 48 }
45) 49)
46 50
51type testLogger struct{}
52
53func (t *testLogger) Log(keyvals ...interface{}) error {
54 return nil
55}
56
47func getPort() string { 57func getPort() string {
48 listener, err := net.Listen("tcp", ":0") 58 listener, err := net.Listen("tcp", ":0")
49 if err != nil { 59 if err != nil {
@@ -61,6 +71,8 @@ type TestInputs struct {
61 YAMLConfigPath string 71 YAMLConfigPath string
62 ExpectedError *regexp.Regexp 72 ExpectedError *regexp.Regexp
63 UseTLSClient bool 73 UseTLSClient bool
74 Username string
75 Password string
64} 76}
65 77
66func TestYAMLFiles(t *testing.T) { 78func TestYAMLFiles(t *testing.T) {
@@ -73,7 +85,7 @@ func TestYAMLFiles(t *testing.T) {
73 { 85 {
74 Name: `empty config yml`, 86 Name: `empty config yml`,
75 YAMLConfigPath: "testdata/tls_config_empty.yml", 87 YAMLConfigPath: "testdata/tls_config_empty.yml",
76 ExpectedError: ErrorMap["Invalid CertPath"], 88 ExpectedError: nil,
77 }, 89 },
78 { 90 {
79 Name: `invalid config yml (invalid structure)`, 91 Name: `invalid config yml (invalid structure)`,
@@ -81,6 +93,11 @@ func TestYAMLFiles(t *testing.T) {
81 ExpectedError: ErrorMap["YAML error"], 93 ExpectedError: ErrorMap["YAML error"],
82 }, 94 },
83 { 95 {
96 Name: `invalid config yml (invalid key)`,
97 YAMLConfigPath: "testdata/tls_config_junk_key.yml",
98 ExpectedError: ErrorMap["YAML error"],
99 },
100 {
84 Name: `invalid config yml (cert path empty)`, 101 Name: `invalid config yml (cert path empty)`,
85 YAMLConfigPath: "testdata/tls_config_noAuth_certPath_empty.bad.yml", 102 YAMLConfigPath: "testdata/tls_config_noAuth_certPath_empty.bad.yml",
86 ExpectedError: ErrorMap["Invalid CertPath"], 103 ExpectedError: ErrorMap["Invalid CertPath"],
@@ -120,6 +137,11 @@ func TestYAMLFiles(t *testing.T) {
120 YAMLConfigPath: "testdata/tls_config_auth_clientCAs_invalid.bad.yml", 137 YAMLConfigPath: "testdata/tls_config_auth_clientCAs_invalid.bad.yml",
121 ExpectedError: ErrorMap["No such file"], 138 ExpectedError: ErrorMap["No such file"],
122 }, 139 },
140 {
141 Name: `invalid config yml (invalid user list)`,
142 YAMLConfigPath: "testdata/tls_config_auth_user_list_invalid.bad.yml",
143 ExpectedError: ErrorMap["Bad password"],
144 },
123 } 145 }
124 for _, testInputs := range testTables { 146 for _, testInputs := range testTables {
125 t.Run(testInputs.Name, testInputs.Test) 147 t.Run(testInputs.Name, testInputs.Test)
@@ -189,7 +211,7 @@ func TestConfigReloading(t *testing.T) {
189 recordConnectionError(errors.New("Panic starting server")) 211 recordConnectionError(errors.New("Panic starting server"))
190 } 212 }
191 }() 213 }()
192 err := Listen(server, badYAMLPath) 214 err := Listen(server, badYAMLPath, testlogger)
193 recordConnectionError(err) 215 recordConnectionError(err)
194 }() 216 }()
195 217
@@ -266,21 +288,28 @@ func (test *TestInputs) Test(t *testing.T) {
266 recordConnectionError(errors.New("Panic starting server")) 288 recordConnectionError(errors.New("Panic starting server"))
267 } 289 }
268 }() 290 }()
269 err := Listen(server, test.YAMLConfigPath) 291 err := Listen(server, test.YAMLConfigPath, testlogger)
270 recordConnectionError(err) 292 recordConnectionError(err)
271 }() 293 }()
272 294
273 var ClientConnection func() (*http.Response, error) 295 ClientConnection := func() (*http.Response, error) {
274 if test.UseTLSClient { 296 var client *http.Client
275 ClientConnection = func() (*http.Response, error) { 297 var proto string
276 client := getTLSClient() 298 if test.UseTLSClient {
277 return client.Get("https://localhost" + port) 299 client = getTLSClient()
300 proto = "https"
301 } else {
302 client = http.DefaultClient
303 proto = "http"
278 } 304 }
279 } else { 305 req, err := http.NewRequest("GET", proto+"://localhost"+port, nil)
280 ClientConnection = func() (*http.Response, error) { 306 if err != nil {
281 client := http.DefaultClient 307 t.Error(err)
282 return client.Get("http://localhost" + port)
283 } 308 }
309 if test.Username != "" {
310 req.SetBasicAuth(test.Username, test.Password)
311 }
312 return client.Do(req)
284 } 313 }
285 go func() { 314 go func() {
286 time.Sleep(250 * time.Millisecond) 315 time.Sleep(250 * time.Millisecond)
@@ -360,3 +389,61 @@ func swapFileContents(file1, file2 string) error {
360 } 389 }
361 return nil 390 return nil
362} 391}
392
393func TestUsers(t *testing.T) {
394 testTables := []*TestInputs{
395 {
396 Name: `without basic auth`,
397 YAMLConfigPath: "testdata/tls_config_users_noTLS.good.yml",
398 ExpectedError: ErrorMap["Unauthorized"],
399 },
400 {
401 Name: `with correct basic auth`,
402 YAMLConfigPath: "testdata/tls_config_users_noTLS.good.yml",
403 Username: "dave",
404 Password: "dave123",
405 ExpectedError: nil,
406 },
407 {
408 Name: `without basic auth and TLS`,
409 YAMLConfigPath: "testdata/tls_config_users.good.yml",
410 UseTLSClient: true,
411 ExpectedError: ErrorMap["Unauthorized"],
412 },
413 {
414 Name: `with correct basic auth and TLS`,
415 YAMLConfigPath: "testdata/tls_config_users.good.yml",
416 UseTLSClient: true,
417 Username: "dave",
418 Password: "dave123",
419 ExpectedError: nil,
420 },
421 {
422 Name: `with another correct basic auth and TLS`,
423 YAMLConfigPath: "testdata/tls_config_users.good.yml",
424 UseTLSClient: true,
425 Username: "carol",
426 Password: "carol123",
427 ExpectedError: nil,
428 },
429 {
430 Name: `with bad password and TLS`,
431 YAMLConfigPath: "testdata/tls_config_users.good.yml",
432 UseTLSClient: true,
433 Username: "dave",
434 Password: "bad",
435 ExpectedError: ErrorMap["Forbidden"],
436 },
437 {
438 Name: `with bad username and TLS`,
439 YAMLConfigPath: "testdata/tls_config_users.good.yml",
440 UseTLSClient: true,
441 Username: "nonexistent",
442 Password: "nonexistent",
443 ExpectedError: ErrorMap["Forbidden"],
444 },
445 }
446 for _, testInputs := range testTables {
447 t.Run(testInputs.Name, testInputs.Test)
448 }
449}
diff --git a/https/users.go b/https/users.go
new file mode 100644
index 0000000..170c87b
--- /dev/null
+++ b/https/users.go
@@ -0,0 +1,73 @@
1// Copyright 2020 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package https
15
16import (
17 "net/http"
18
19 "github.com/go-kit/kit/log"
20 "golang.org/x/crypto/bcrypt"
21)
22
23func validateUsers(configPath string) error {
24 c, err := getConfig(configPath)
25 if err != nil {
26 return err
27 }
28
29 for _, p := range c.Users {
30 _, err = bcrypt.Cost([]byte(p))
31 if err != nil {
32 return err
33 }
34 }
35
36 return nil
37}
38
39type userAuthRoundtrip struct {
40 tlsConfigPath string
41 handler http.Handler
42 logger log.Logger
43}
44
45func (u *userAuthRoundtrip) ServeHTTP(w http.ResponseWriter, r *http.Request) {
46 c, err := getConfig(u.tlsConfigPath)
47 if err != nil {
48 u.logger.Log("msg", "Unable to parse configuration", "err", err)
49 http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
50 return
51 }
52
53 if len(c.Users) == 0 {
54 u.handler.ServeHTTP(w, r)
55 return
56 }
57
58 user, pass, ok := r.BasicAuth()
59 if !ok {
60 w.Header().Set("WWW-Authenticate", "Basic")
61 http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
62 return
63 }
64
65 if hashedPassword, ok := c.Users[user]; ok {
66 if err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(pass)); err == nil {
67 u.handler.ServeHTTP(w, r)
68 return
69 }
70 }
71
72 http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
73}