Skip to content

Commit 202ecf9

Browse files
author
Julien Pivotto
authored
Add basic authentication (prometheus#1683)
* Add basic authentication Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
1 parent b42819b commit 202ecf9

37 files changed

Lines changed: 5132 additions & 52 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* [CHANGE]
44
* [FEATURE]
5+
* [FEATURE] Add basic authentication #1673
56
* [ENHANCEMENT] Add model_name and stepping to node_cpu_info metric #1617
67
* [ENHANCEMENT] Add metrics for IO errors and retires on Darwin. #1636
78
* [BUGFIX] collector/systemd: use regexp to extract systemd version #1647

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/soundcloud/go-runit v0.0.0-20150630195641-06ad41a06c4a
2424
go.uber.org/atomic v1.5.1 // indirect
2525
go.uber.org/multierr v1.4.0 // indirect
26+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
2627
golang.org/x/lint v0.0.0-20200130185559-910be7a94367 // indirect
2728
golang.org/x/sys v0.0.0-20200217220822-9197077df867
2829
golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJ
208208
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
209209
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
210210
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
211+
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223 h1:F9x/1yl3T2AeKLr2AMdilSD8+f9bvMnNN8VS5iDtovc=
211212
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
212213
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
213214
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
@@ -338,6 +339,7 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf
338339
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
339340
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
340341
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
342+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
341343
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
342344
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
343345
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=

https/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,27 @@ tls_config:
2525
2626
# CA certificate for client certificate authentication to the server
2727
[ 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.
32+
basic_auth_users:
33+
[ <username>: <password> ... ]
2834
```
35+
36+
## About bcrypt
37+
38+
There 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+
43+
That command will prompt you for a password and output the hashed password,
44+
which will look something like:
45+
`$2y$10$X0h1gDsPszWURQaxFh.zoubFi6DXncSjhoQNJgRrnGs7EsimhC7zG`
46+
47+
The cost (10 in the example) influences the time it takes for computing the
48+
hash. A higher cost will en up slowing down the authentication process.
49+
Depending on the machine, a cost of 10 will take about ~70ms where a cost of
50+
18 can take up to a few seconds. That hash will be computed on every
51+
password-protected request.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
tls_config :
2+
cert_file : "testdata/server.crt"
3+
key_file : "testdata/server.key"
4+
basic_auth_users:
5+
john: doe
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
tls_config :
2+
cert_filse: "testdata/server.crt"
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
tls_config :
22
cert_file : ""
3-
key_file : ""
3+
key_file : ""
4+
client_auth_type: "x"
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
tls_config :
2+
cert_file : "testdata/server.crt"
3+
key_file : "testdata/server.key"
4+
basic_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
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
basic_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

https/tls_config.go

Lines changed: 77 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,20 @@ import (
2020
"io/ioutil"
2121
"net/http"
2222

23+
"github.com/go-kit/kit/log"
24+
"github.com/go-kit/kit/log/level"
2325
"github.com/pkg/errors"
26+
config_util "github.com/prometheus/common/config"
2427
"gopkg.in/yaml.v2"
2528
)
2629

30+
var (
31+
errNoTLSConfig = errors.New("TLS config is not present")
32+
)
33+
2734
type 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"`
2937
}
3038

3139
type TLSStruct struct {
@@ -35,13 +43,18 @@ type TLSStruct struct {
3543
ClientCAs string `yaml:"client_ca_file"`
3644
}
3745

38-
func getTLSConfig(configPath string) (*tls.Config, error) {
46+
func getConfig(configPath string) (*Config, error) {
3947
content, err := ioutil.ReadFile(configPath)
4048
if err != nil {
4149
return nil, err
4250
}
4351
c := &Config{}
44-
err = yaml.Unmarshal(content, c)
52+
err = yaml.UnmarshalStrict(content, c)
53+
return c, err
54+
}
55+
56+
func getTLSConfig(configPath string) (*tls.Config, error) {
57+
c, err := getConfig(configPath)
4558
if err != nil {
4659
return nil, err
4760
}
@@ -50,14 +63,18 @@ func getTLSConfig(configPath string) (*tls.Config, error) {
5063

5164
// ConfigToTLSConfig generates the golang tls.Config from the TLSStruct config.
5265
func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) {
53-
cfg := &tls.Config{
54-
MinVersion: tls.VersionTLS12,
66+
if c.TLSCertPath == "" && c.TLSKeyPath == "" && c.ClientAuth == "" && c.ClientCAs == "" {
67+
return nil, errNoTLSConfig
5568
}
56-
if len(c.TLSCertPath) == 0 {
57-
return nil, errors.New("missing TLSCertPath")
69+
70+
if c.TLSCertPath == "" {
71+
return nil, errors.New("missing cert_file")
5872
}
59-
if len(c.TLSKeyPath) == 0 {
60-
return nil, errors.New("missing TLSKeyPath")
73+
if c.TLSKeyPath == "" {
74+
return nil, errors.New("missing key_file")
75+
}
76+
cfg := &tls.Config{
77+
MinVersion: tls.VersionTLS12,
6178
}
6279
loadCert := func() (*tls.Certificate, error) {
6380
cert, err := tls.LoadX509KeyPair(c.TLSCertPath, c.TLSKeyPath)
@@ -74,7 +91,7 @@ func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) {
7491
return loadCert()
7592
}
7693

77-
if len(c.ClientCAs) > 0 {
94+
if c.ClientCAs != "" {
7895
clientCAPool := x509.NewCertPool()
7996
clientCAFile, err := ioutil.ReadFile(c.ClientCAs)
8097
if err != nil {
@@ -83,40 +100,67 @@ func ConfigToTLSConfig(c *TLSStruct) (*tls.Config, error) {
83100
clientCAPool.AppendCertsFromPEM(clientCAFile)
84101
cfg.ClientCAs = clientCAPool
85102
}
86-
if len(c.ClientAuth) > 0 {
87-
switch s := (c.ClientAuth); s {
88-
case "NoClientCert":
89-
cfg.ClientAuth = tls.NoClientCert
90-
case "RequestClientCert":
91-
cfg.ClientAuth = tls.RequestClientCert
92-
case "RequireClientCert":
93-
cfg.ClientAuth = tls.RequireAnyClientCert
94-
case "VerifyClientCertIfGiven":
95-
cfg.ClientAuth = tls.VerifyClientCertIfGiven
96-
case "RequireAndVerifyClientCert":
97-
cfg.ClientAuth = tls.RequireAndVerifyClientCert
98-
case "":
99-
cfg.ClientAuth = tls.NoClientCert
100-
default:
101-
return nil, errors.New("Invalid ClientAuth: " + s)
102-
}
103+
104+
switch c.ClientAuth {
105+
case "RequestClientCert":
106+
cfg.ClientAuth = tls.RequestClientCert
107+
case "RequireClientCert":
108+
cfg.ClientAuth = tls.RequireAnyClientCert
109+
case "VerifyClientCertIfGiven":
110+
cfg.ClientAuth = tls.VerifyClientCertIfGiven
111+
case "RequireAndVerifyClientCert":
112+
cfg.ClientAuth = tls.RequireAndVerifyClientCert
113+
case "", "NoClientCert":
114+
cfg.ClientAuth = tls.NoClientCert
115+
default:
116+
return nil, errors.New("Invalid ClientAuth: " + c.ClientAuth)
103117
}
104-
if len(c.ClientCAs) > 0 && cfg.ClientAuth == tls.NoClientCert {
118+
119+
if c.ClientCAs != "" && cfg.ClientAuth == tls.NoClientCert {
105120
return nil, errors.New("Client CA's have been configured without a Client Auth Policy")
106121
}
122+
107123
return cfg, nil
108124
}
109125

110126
// Listen starts the server on the given address. If tlsConfigPath isn't empty the server connection will be started using TLS.
111-
func Listen(server *http.Server, tlsConfigPath string) error {
112-
if (tlsConfigPath) == "" {
127+
func Listen(server *http.Server, tlsConfigPath string, logger log.Logger) error {
128+
if tlsConfigPath == "" {
129+
level.Info(logger).Log("msg", "TLS is disabled and it cannot be enabled on the fly.")
113130
return server.ListenAndServe()
114131
}
115-
var err error
116-
server.TLSConfig, err = getTLSConfig(tlsConfigPath)
117-
if err != nil {
132+
133+
if err := validateUsers(tlsConfigPath); err != nil {
118134
return err
119135
}
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+
120164
// Set the GetConfigForClient method of the HTTPS server so that the config
121165
// and certs are reloaded on new connections.
122166
server.TLSConfig.GetConfigForClient = func(*tls.ClientHelloInfo) (*tls.Config, error) {

0 commit comments

Comments
 (0)