diff --git a/web/basic_auth/bcrypt.go b/web/basic_auth/bcrypt.go new file mode 100644 index 00000000..dc460d28 --- /dev/null +++ b/web/basic_auth/bcrypt.go @@ -0,0 +1,37 @@ +// Copyright 2024 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build !nobcrypt +// +build !nobcrypt + +package basic_auth + +import ( + config_util "github.com/prometheus/common/config" + "golang.org/x/crypto/bcrypt" +) + +func Validate(users map[string]config_util.Secret) error { + for _, p := range users { + _, err := bcrypt.Cost([]byte(p)) + if err != nil { + return err + } + } + + return nil +} + +func CompareAndHash(hashedPassword, pass []byte) error { + return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(pass)) +} diff --git a/web/basic_auth/no_bcrypt.go b/web/basic_auth/no_bcrypt.go new file mode 100644 index 00000000..3c74826a --- /dev/null +++ b/web/basic_auth/no_bcrypt.go @@ -0,0 +1,35 @@ +// Copyright 2024 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build nobcrypt +// +build nobcrypt + +package basic_auth + +import ( + "fmt" + "log/slog" + + config_util "github.com/prometheus/common/config" +) + +func Validate(users map[string]config_util.Secret) error { + if len(users) > 0 { + slog.Info("basic auth via bcrypt hashes not implemented") + } + return nil +} + +func CompareAndHash(hashedPassword, pass []byte) error { + return fmt.Errorf("basic auth via bcrypt hashes not implemented") +} diff --git a/web/handler.go b/web/handler.go index 51da762c..f2a6aaf7 100644 --- a/web/handler.go +++ b/web/handler.go @@ -23,7 +23,7 @@ import ( "strings" "sync" - "golang.org/x/crypto/bcrypt" + "github.com/prometheus/exporter-toolkit/web/basic_auth" ) // extraHTTPHeaders is a map of HTTP headers that can be added to HTTP @@ -43,14 +43,7 @@ func validateUsers(configPath string) error { return err } - for _, p := range c.Users { - _, err = bcrypt.Cost([]byte(p)) - if err != nil { - return err - } - } - - return nil + return basic_auth.Validate(c.Users) } // validateHeaderConfig checks that the provided header configuration is correct. @@ -125,7 +118,7 @@ func (u *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !ok { // This user, hashedPassword, password is not cached. u.bcryptMtx.Lock() - err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(pass)) + err := basic_auth.CompareAndHash([]byte(hashedPassword), []byte(pass)) u.bcryptMtx.Unlock() authOk = validUser && err == nil diff --git a/web/handler_test.go b/web/handler_test.go index 80d594a9..28643624 100644 --- a/web/handler_test.go +++ b/web/handler_test.go @@ -11,6 +11,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.14 && !nobcrypt +// +build go1.14,!nobcrypt + package web import ( diff --git a/web/tls_config_test.go b/web/tls_config_test.go index b28c6671..e354e7bd 100644 --- a/web/tls_config_test.go +++ b/web/tls_config_test.go @@ -172,11 +172,6 @@ func TestYAMLFiles(t *testing.T) { YAMLConfigPath: "testdata/web_config_auth_clientCAs_invalid.bad.yml", ExpectedError: ErrorMap["No such file"], }, - { - Name: `invalid config yml (invalid user list)`, - YAMLConfigPath: "testdata/web_config_auth_user_list_invalid.bad.yml", - ExpectedError: ErrorMap["Bad password"], - }, { Name: `invalid config yml (bad cipher)`, YAMLConfigPath: "testdata/web_config_noAuth_inventedCiphers.bad.yml", @@ -635,61 +630,3 @@ func swapFileContents(file1, file2 string) error { } return nil } - -func TestUsers(t *testing.T) { - testTables := []*TestInputs{ - { - Name: `without basic auth`, - YAMLConfigPath: "testdata/web_config_users_noTLS.good.yml", - ExpectedError: ErrorMap["Unauthorized"], - }, - { - Name: `with correct basic auth`, - YAMLConfigPath: "testdata/web_config_users_noTLS.good.yml", - Username: "dave", - Password: "dave123", - ExpectedError: nil, - }, - { - Name: `without basic auth and TLS`, - YAMLConfigPath: "testdata/web_config_users.good.yml", - UseTLSClient: true, - ExpectedError: ErrorMap["Unauthorized"], - }, - { - Name: `with correct basic auth and TLS`, - YAMLConfigPath: "testdata/web_config_users.good.yml", - UseTLSClient: true, - Username: "dave", - Password: "dave123", - ExpectedError: nil, - }, - { - Name: `with another correct basic auth and TLS`, - YAMLConfigPath: "testdata/web_config_users.good.yml", - UseTLSClient: true, - Username: "carol", - Password: "carol123", - ExpectedError: nil, - }, - { - Name: `with bad password and TLS`, - YAMLConfigPath: "testdata/web_config_users.good.yml", - UseTLSClient: true, - Username: "dave", - Password: "bad", - ExpectedError: ErrorMap["Unauthorized"], - }, - { - Name: `with bad username and TLS`, - YAMLConfigPath: "testdata/web_config_users.good.yml", - UseTLSClient: true, - Username: "nonexistent", - Password: "nonexistent", - ExpectedError: ErrorMap["Unauthorized"], - }, - } - for _, testInputs := range testTables { - t.Run(testInputs.Name, testInputs.Test) - } -} diff --git a/web/user_auth_test.go b/web/user_auth_test.go new file mode 100644 index 00000000..705c0dcb --- /dev/null +++ b/web/user_auth_test.go @@ -0,0 +1,91 @@ +// Copyright 2019 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build go1.14 && !nobcrypt +// +build go1.14,!nobcrypt + +package web + +import "testing" + +func TestYAMLFilesUsers(t *testing.T) { + testTables := []*TestInputs{ + { + Name: `invalid config yml (invalid user list)`, + YAMLConfigPath: "testdata/web_config_auth_user_list_invalid.bad.yml", + ExpectedError: ErrorMap["Bad password"], + }, + } + for _, testInputs := range testTables { + t.Run("run/"+testInputs.Name, testInputs.Test) + t.Run("validate/"+testInputs.Name, testInputs.TestValidate) + } +} + +func TestUsers(t *testing.T) { + testTables := []*TestInputs{ + { + Name: `without basic auth`, + YAMLConfigPath: "testdata/web_config_users_noTLS.good.yml", + ExpectedError: ErrorMap["Unauthorized"], + }, + { + Name: `with correct basic auth`, + YAMLConfigPath: "testdata/web_config_users_noTLS.good.yml", + Username: "dave", + Password: "dave123", + ExpectedError: nil, + }, + { + Name: `without basic auth and TLS`, + YAMLConfigPath: "testdata/web_config_users.good.yml", + UseTLSClient: true, + ExpectedError: ErrorMap["Unauthorized"], + }, + { + Name: `with correct basic auth and TLS`, + YAMLConfigPath: "testdata/web_config_users.good.yml", + UseTLSClient: true, + Username: "dave", + Password: "dave123", + ExpectedError: nil, + }, + { + Name: `with another correct basic auth and TLS`, + YAMLConfigPath: "testdata/web_config_users.good.yml", + UseTLSClient: true, + Username: "carol", + Password: "carol123", + ExpectedError: nil, + }, + { + Name: `with bad password and TLS`, + YAMLConfigPath: "testdata/web_config_users.good.yml", + UseTLSClient: true, + Username: "dave", + Password: "bad", + ExpectedError: ErrorMap["Unauthorized"], + }, + { + Name: `with bad username and TLS`, + YAMLConfigPath: "testdata/web_config_users.good.yml", + UseTLSClient: true, + Username: "nonexistent", + Password: "nonexistent", + ExpectedError: ErrorMap["Unauthorized"], + }, + } + for _, testInputs := range testTables { + t.Run(testInputs.Name, testInputs.Test) + } +}