From ea2279d49f66e9a74975b0c1aa5a432ae7820d07 Mon Sep 17 00:00:00 2001 From: Jan Fajerski Date: Wed, 2 Oct 2024 19:22:47 +0200 Subject: [PATCH] basic auth: add option to exclude bcrypt at build time This add the option to exclude bcrypt during the build process by passing `-tags nobcrypt`. Currently this disables user authentication via basic_auth. If authorized users are configured but the server is build with `nobcrypt` the endpoint will respond with Unauthorized, even if the correct credentials are send. The goal is to be able to exclude bcrypt for compliance reasons. In the future we could add basic_auth using other hash functions. Signed-off-by: Jan Fajerski --- web/basic_auth/bcrypt.go | 37 +++++++++++++++ web/basic_auth/no_bcrypt.go | 35 ++++++++++++++ web/handler.go | 13 ++---- web/handler_test.go | 3 ++ web/tls_config_test.go | 63 ------------------------- web/user_auth_test.go | 91 +++++++++++++++++++++++++++++++++++++ 6 files changed, 169 insertions(+), 73 deletions(-) create mode 100644 web/basic_auth/bcrypt.go create mode 100644 web/basic_auth/no_bcrypt.go create mode 100644 web/user_auth_test.go diff --git a/web/basic_auth/bcrypt.go b/web/basic_auth/bcrypt.go new file mode 100644 index 000000000..dc460d28f --- /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 000000000..3c74826a8 --- /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 51da762c9..f2a6aaf71 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 80d594a9e..286436249 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 b28c66711..e354e7bd2 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 000000000..705c0dcb9 --- /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) + } +}