Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

basic_auth: add support for per-route filter #33335

Merged
merged 21 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,11 @@ message BasicAuth {
string forward_username_header = 2
[(validate.rules).string = {well_known_regex: HTTP_HEADER_NAME strict: false}];
}

// Extra settings that may be added to per-route configuration for
// a virtual host or a cluster.
message BasicAuthPerRoute {
// Username-password pairs for this route.
config.core.v3.DataSource users = 1
[(validate.rules).message = {required: true}, (udpa.annotations.sensitive) = true];
}
4 changes: 4 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,10 @@ new_features:
Added maximum gRPC message size that is allowed to be received in Envoy gRPC. If a message over this limit is received,
the gRPC stream is terminated with the RESOURCE_EXHAUSTED error. This limit is applied to individual messages in the
streaming response and not the total size of streaming response. Defaults to 0, which means unlimited.
- area: filters
change: |
Added :ref:`per-route configuration support to the Basic Auth filter
<envoy_v3_api_msg_extensions.filters.http.basic_auth.v3.BasicAuthPerRoute>`.

deprecated:
- area: listener
Expand Down
44 changes: 40 additions & 4 deletions docs/root/configuration/http/http_filters/basic_auth_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,49 @@ An example configuration of the filter may look like the following:

.. code-block:: yaml

users:
inline_string: |-
user1:{SHA}hashed_user1_password
user2:{SHA}hashed_user2_password
http_filters:
- name: envoy.filters.http.basic_auth
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuth
users:
inline_string: |-
user1:{SHA}hashed_user1_password
user2:{SHA}hashed_user2_password

Note that only SHA format is currently supported. Other formats may be added in the future.

Per-Route Configuration
-----------------------

An example configuration of the route filter may look like the following:

.. code-block:: yaml

route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { path: "/admin" }
route: { cluster: some_service }
typed_per_filter_config:
envoy.filters.http.basic_auth:
"@type": type.googleapis.com/envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute
users:
inline_string: |-
admin:{SHA}hashed_admin_password
- match: { prefix: "/static" }
route: { cluster: some_service }
typed_per_filter_config:
envoy.filters.http.basic_auth:
"@type": type.googleapis.com/envoy.config.route.v3.FilterConfig
disabled: true
- match: { prefix: "/" }
route: { cluster: some_service }

In this example we customize users for ``/admin`` route, and disable authentication for ``/static`` prefixed routes.

Statistics
----------

Expand Down
1 change: 1 addition & 0 deletions source/extensions/extensions_metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ envoy.filters.http.basic_auth:
status: alpha
type_urls:
- envoy.extensions.filters.http.basic_auth.v3.BasicAuth
- envoy.extensions.filters.http.basic_auth.v3.BasicAuthPerRoute
envoy.filters.http.buffer:
categories:
- envoy.filters.http
Expand Down
1 change: 1 addition & 0 deletions source/extensions/filters/http/basic_auth/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ envoy_cc_library(
"//source/common/http:header_utility_lib",
"//source/common/protobuf:utility_lib",
"//source/extensions/filters/http/common:pass_through_filter_lib",
"@envoy_api//envoy/extensions/filters/http/basic_auth/v3:pkg_cc_proto",
],
)

Expand Down
29 changes: 19 additions & 10 deletions source/extensions/filters/http/basic_auth/basic_auth_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "source/common/common/base64.h"
#include "source/common/http/header_utility.h"
#include "source/common/http/headers.h"
#include "source/common/http/utility.h"

namespace Envoy {
namespace Extensions {
Expand All @@ -31,18 +32,16 @@ FilterConfig::FilterConfig(UserMap&& users, const std::string& forward_username_
: users_(std::move(users)), forward_username_header_(forward_username_header),
stats_(generateStats(stats_prefix + "basic_auth.", scope)) {}

bool FilterConfig::validateUser(absl::string_view username, absl::string_view password) const {
auto user = users_.find(username);
if (user == users_.end()) {
return false;
}

return computeSHA1(password) == user->second.hash;
}

BasicAuthFilter::BasicAuthFilter(FilterConfigConstSharedPtr config) : config_(std::move(config)) {}

Http::FilterHeadersStatus BasicAuthFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool) {
const auto* route_specific_settings =
Http::Utility::resolveMostSpecificPerFilterConfig<FilterConfigPerRoute>(decoder_callbacks_);
const UserMap* users = &config_->users();
if (route_specific_settings != nullptr) {
users = &route_specific_settings->users();
Comment on lines +40 to +42
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Prefer reference_wrapper because it never be null. But pointer is also OK if you prefer it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not prefer pointers. I used the pointer to reassign users inside the if statement, and I couldn't do that with reference_wrapper.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think reference_wraper also could do the reassign? But anyway, it's fine.

}

auto auth_header = headers.get(Http::CustomHeaders::get().Authorization);

if (auth_header.empty()) {
Expand Down Expand Up @@ -72,7 +71,7 @@ Http::FilterHeadersStatus BasicAuthFilter::decodeHeaders(Http::RequestHeaderMap&
absl::string_view username = decoded_view.substr(0, colon_pos);
absl::string_view password = decoded_view.substr(colon_pos + 1);

if (!config_->validateUser(username, password)) {
if (!validateUser(*users, username, password)) {
return onDenied("User authentication failed. Invalid username/password combination.",
"invalid_credential_for_basic_auth");
}
Expand All @@ -85,6 +84,16 @@ Http::FilterHeadersStatus BasicAuthFilter::decodeHeaders(Http::RequestHeaderMap&
return Http::FilterHeadersStatus::Continue;
}

bool BasicAuthFilter::validateUser(const UserMap& users, absl::string_view username,
absl::string_view password) const {
auto user = users.find(username);
if (user == users.end()) {
return false;
}

return computeSHA1(password) == user->second.hash;
}

Http::FilterHeadersStatus BasicAuthFilter::onDenied(absl::string_view body,
absl::string_view response_code_details) {
config_->stats().denied_.inc();
Expand Down
19 changes: 18 additions & 1 deletion source/extensions/filters/http/basic_auth/basic_auth_filter.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#pragma once

#include "envoy/extensions/filters/http/basic_auth/v3/basic_auth.pb.h"
#include "envoy/stats/stats_macros.h"

#include "source/common/common/logger.h"
Expand Down Expand Up @@ -46,8 +47,8 @@ class FilterConfig {
FilterConfig(UserMap&& users, const std::string& forward_username_header,
const std::string& stats_prefix, Stats::Scope& scope);
const BasicAuthStats& stats() const { return stats_; }
bool validateUser(absl::string_view username, absl::string_view password) const;
const std::string& forwardUsernameHeader() const { return forward_username_header_; }
const UserMap& users() const { return users_; }

private:
static BasicAuthStats generateStats(const std::string& prefix, Stats::Scope& scope) {
Expand All @@ -59,6 +60,20 @@ class FilterConfig {
BasicAuthStats stats_;
};
using FilterConfigConstSharedPtr = std::shared_ptr<const FilterConfig>;
using FilterConfigSharedPtr = std::shared_ptr<FilterConfig>;

/**
* Per route settings for BasicAuth. Allows customizing users on a virtualhost\route\weighted
* cluster level.
*/
class FilterConfigPerRoute : public Router::RouteSpecificFilterConfig {
public:
FilterConfigPerRoute(UserMap&& users) : users_(std::move(users)) {}
const UserMap& users() const { return users_; }

private:
const UserMap users_;
};

// The Envoy filter to process HTTP basic auth.
class BasicAuthFilter : public Http::PassThroughDecoderFilter,
Expand All @@ -68,6 +83,8 @@ class BasicAuthFilter : public Http::PassThroughDecoderFilter,

// Http::StreamDecoderFilter
Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override;
bool validateUser(const UserMap& users, absl::string_view username,
absl::string_view password) const;

private:
Http::FilterHeadersStatus onDenied(absl::string_view body,
Expand Down
10 changes: 10 additions & 0 deletions source/extensions/filters/http/basic_auth/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ namespace HttpFilters {
namespace BasicAuth {

using envoy::extensions::filters::http::basic_auth::v3::BasicAuth;
using envoy::extensions::filters::http::basic_auth::v3::BasicAuthPerRoute;

namespace {

Expand Down Expand Up @@ -73,6 +74,15 @@ Http::FilterFactoryCb BasicAuthFilterFactory::createFilterFactoryFromProtoTyped(
};
}

Router::RouteSpecificFilterConfigConstSharedPtr
BasicAuthFilterFactory::createRouteSpecificFilterConfigTyped(
const BasicAuthPerRoute& proto_config, Server::Configuration::ServerFactoryContext& context,
ProtobufMessage::ValidationVisitor&) {
UserMap users = readHtpasswd(THROW_OR_RETURN_VALUE(
Config::DataSource::read(proto_config.users(), true, context.api()), std::string));
return std::make_unique<FilterConfigPerRoute>(std::move(users));
}

REGISTER_FACTORY(BasicAuthFilterFactory, Server::Configuration::NamedHttpFilterConfigFactory);

} // namespace BasicAuth
Expand Down
8 changes: 7 additions & 1 deletion source/extensions/filters/http/basic_auth/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@ namespace HttpFilters {
namespace BasicAuth {

class BasicAuthFilterFactory
: public Common::FactoryBase<envoy::extensions::filters::http::basic_auth::v3::BasicAuth> {
: public Common::FactoryBase<
envoy::extensions::filters::http::basic_auth::v3::BasicAuth,
envoy::extensions::filters::http::basic_auth::v3::BasicAuthPerRoute> {
public:
BasicAuthFilterFactory() : FactoryBase("envoy.filters.http.basic_auth") {}

private:
Http::FilterFactoryCb createFilterFactoryFromProtoTyped(
const envoy::extensions::filters::http::basic_auth::v3::BasicAuth& config,
const std::string& stats_prefix, Server::Configuration::FactoryContext& context) override;
Router::RouteSpecificFilterConfigConstSharedPtr createRouteSpecificFilterConfigTyped(
const envoy::extensions::filters::http::basic_auth::v3::BasicAuthPerRoute& proto_config,
Server::Configuration::ServerFactoryContext& context,
ProtobufMessage::ValidationVisitor&) override;
};

} // namespace BasicAuth
Expand Down
3 changes: 3 additions & 0 deletions test/extensions/filters/http/basic_auth/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ envoy_extension_cc_test(
deps = [
"//source/extensions/filters/http/basic_auth:config",
"//test/mocks/server:server_mocks",
"@envoy_api//envoy/extensions/filters/http/basic_auth/v3:pkg_cc_proto",
],
)

Expand All @@ -41,5 +42,7 @@ envoy_extension_cc_test(
"//source/extensions/filters/http/basic_auth:config",
"//test/integration:http_protocol_integration_lib",
"//test/test_common:utility_lib",
"@envoy_api//envoy/config/route/v3:pkg_cc_proto",
"@envoy_api//envoy/extensions/filters/http/basic_auth/v3:pkg_cc_proto",
],
)
Loading