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

test, oauth2: Make sure config test runs field validation #13496

Merged
merged 12 commits into from
Oct 16, 2020
112 changes: 81 additions & 31 deletions docs/root/configuration/http/http_filters/oauth2_filter.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,82 @@ OAuth2
Example configuration
---------------------

.. code-block::

http_filters:
- name: oauth2
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2
token_endpoint:
cluster: oauth
uri: oauth.com/token
timeout: 3s
authorization_endpoint: https://oauth.com/oauth/authorize/
redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback"
redirect_path_matcher:
path:
exact: /callback
signout_path:
path:
exact: /signout
credentials:
client_id: foo
token_secret:
name: token
hmac_secret:
name: hmac
The following is an example configuring the filter.

.. validated-code-block:: yaml
:type-name: envoy.extensions.filters.http.oauth2.v3alpha.OAuth2

config:
token_endpoint:
cluster: oauth
uri: oauth.com/token
timeout: 3s
- name: envoy.router
authorization_endpoint: https://oauth.com/oauth/authorize/
redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback"
redirect_path_matcher:
path:
exact: /callback
signout_path:
path:
exact: /signout
credentials:
client_id: foo
token_secret:
name: token
sds_config:
path: "/etc/envoy/token-secret.yaml"
hmac_secret:
name: hmac
sds_config:
path: "/etc/envoy/hmac.yaml"

And the below code block is an example of how we employ it as one of
:ref:`HttpConnectionManager HTTP filters
<envoy_v3_api_field_extensions.filters.network.http_connection_manager.v3.HttpConnectionManager.http_filters>`

.. code-block:: yaml

static_resources:
listeners:
- name:
address:
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
http_filters:
- name: envoy.filters.http.oauth2
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.oauth2.v3alpha.OAuth2
config:
token_endpoint:
cluster: oauth
uri: oauth.com/token
timeout: 3s
authorization_endpoint: https://oauth.com/oauth/authorize/
redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback"
redirect_path_matcher:
path:
exact: /callback
signout_path:
path:
exact: /signout
credentials:
client_id: foo
token_secret:
name: token
sds_config:
path: "/etc/envoy/token-secret.yaml"
hmac_secret:
name: hmac
sds_config:
path: "/etc/envoy/hmac.yaml"
- name: envoy.router

clusters:
- name: service
...
# ...
- name: auth
connect_timeout: 5s
type: LOGICAL_DNS
Expand All @@ -53,21 +99,25 @@ Example configuration
endpoints:
- lb_endpoints:
- endpoint:
address: { socket_address: { address: auth.example.com, port_value: 443 }}
tls_context: { sni: auth.example.com }
address:
socket_address:
address: auth.example.com
port_value: 443
tls_context:
sni: auth.example.com

Notes
-----

This module does not currently provide much Cross-Site-Request-Forgery protection for the redirect loop
to the OAuth server and back.
This module does not currently provide much Cross-Site-Request-Forgery protection for the redirect
loop to the OAuth server and back.

The service must be served over HTTPS for this filter to work, as the cookies use `;secure`.

Statistics
----------

The OAuth filter outputs statistics in the *<stat_prefix>.* namespace.
The OAuth2 filter outputs statistics in the *<stat_prefix>.* namespace.

.. csv-table::
:header: Name, Type, Description
Expand Down
26 changes: 17 additions & 9 deletions source/extensions/filters/http/oauth2/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,32 @@ Http::FilterFactoryCb OAuth2Config::createFilterFactoryFromProtoTyped(
const auto& token_secret = credentials.token_secret();
const auto& hmac_secret = credentials.hmac_secret();

auto& secret_manager = context.clusterManager().clusterManagerFactory().secretManager();
auto& cluster_manager = context.clusterManager();
auto& secret_manager = cluster_manager.clusterManagerFactory().secretManager();
auto& transport_socket_factory = context.getTransportSocketFactoryContext();
auto secret_provider_token_secret =
secretsProvider(token_secret, secret_manager, transport_socket_factory);
if (secret_provider_token_secret == nullptr) {
throw EnvoyException("invalid token secret configuration");
}
auto secret_provider_hmac_secret =
secretsProvider(hmac_secret, secret_manager, transport_socket_factory);
if (secret_provider_hmac_secret == nullptr) {
throw EnvoyException("invalid HMAC secret configuration");
}

auto secret_reader = std::make_shared<SDSSecretReader>(
secret_provider_token_secret, secret_provider_hmac_secret, context.api());
auto config = std::make_shared<FilterConfig>(proto_config, context.clusterManager(),
secret_reader, context.scope(), stats_prefix);
auto config = std::make_shared<FilterConfig>(proto_config, cluster_manager, secret_reader,
context.scope(), stats_prefix);

return [&context, config](Http::FilterChainFactoryCallbacks& callbacks) -> void {
std::unique_ptr<OAuth2Client> oauth_client =
std::make_unique<OAuth2ClientImpl>(context.clusterManager(), config->oauthTokenEndpoint());
callbacks.addStreamDecoderFilter(
std::make_shared<OAuth2Filter>(config, std::move(oauth_client), context.timeSource()));
};
return
[&context, config, &cluster_manager](Http::FilterChainFactoryCallbacks& callbacks) -> void {
std::unique_ptr<OAuth2Client> oauth_client =
std::make_unique<OAuth2ClientImpl>(cluster_manager, config->oauthTokenEndpoint());
callbacks.addStreamDecoderFilter(
std::make_shared<OAuth2Filter>(config, std::move(oauth_client), context.timeSource()));
};
}

/*
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/filters/http/well_known_names.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class HttpFilterNameValues {
// AWS Lambda filter
const std::string AwsLambda = "envoy.filters.http.aws_lambda";
// OAuth filter
const std::string OAuth = "envoy.filters.http.oauth";
const std::string OAuth = "envoy.filters.http.oauth2";
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this safe to change? I think we prefer to use the proto type to identify the filter to create, so I think yes?

Copy link
Member Author

@dio dio Oct 12, 2020

Choose a reason for hiding this comment

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

Yes. This is for consistency since we print the name of extensions at startup, i.e. [info][main] [external/envoy/source/server/server.cc:309]: envoy.filters.http: ...

};

using HttpFilterNames = ConstSingleton<HttpFilterNameValues>;
Expand Down
105 changes: 87 additions & 18 deletions test/extensions/filters/http/oauth2/config_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,102 @@ namespace Oauth2 {
using testing::NiceMock;
using testing::Return;

namespace {

// This loads one of the secrets in credentials, and fails the other one.
void expectInvalidSecretConfig(const std::string& failed_secret_name,
const std::string& exception_message) {
const std::string yaml = R"EOF(
config:
token_endpoint:
cluster: foo
uri: oauth.com/token
timeout: 3s
credentials:
client_id: "secret"
token_secret:
name: token
hmac_secret:
name: hmac
authorization_endpoint: https://oauth.com/oauth/authorize/
redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback"
redirect_path_matcher:
path:
exact: /callback
signout_path:
path:
exact: /signout
)EOF";

OAuth2Config factory;
ProtobufTypes::MessagePtr proto_config = factory.createEmptyConfigProto();
TestUtility::loadFromYaml(yaml, *proto_config);
NiceMock<Server::Configuration::MockFactoryContext> context;

auto& secret_manager = context.cluster_manager_.cluster_manager_factory_.secretManager();
ON_CALL(secret_manager,
findStaticGenericSecretProvider(failed_secret_name == "token" ? "hmac" : "token"))
.WillByDefault(Return(std::make_shared<Secret::GenericSecretConfigProviderImpl>(
envoy::extensions::transport_sockets::tls::v3::GenericSecret())));

EXPECT_THROW_WITH_MESSAGE(factory.createFilterFactoryFromProto(*proto_config, "stats", context),
EnvoyException, exception_message);
}

} // namespace

TEST(ConfigTest, CreateFilter) {
const std::string yaml = R"EOF(
config:
token_endpoint:
cluster: foo
uri: oauth.com/token
timeout: 3s
authorization_endpoint: https://oauth.com/oauth/authorize/
redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback"
signout_path:
path:
exact: /signout
token_endpoint:
cluster: foo
uri: oauth.com/token
timeout: 3s
credentials:
client_id: "secret"
token_secret:
name: token
hmac_secret:
name: hmac
authorization_endpoint: https://oauth.com/oauth/authorize/
redirect_uri: "%REQ(:x-forwarded-proto)%://%REQ(:authority)%/callback"
redirect_path_matcher:
path:
exact: /callback
signout_path:
path:
exact: /signout
)EOF";

envoy::extensions::filters::http::oauth2::v3alpha::OAuth2 proto_config;
MessageUtil::loadFromYaml(yaml, proto_config, ProtobufMessage::getStrictValidationVisitor());
NiceMock<Server::Configuration::MockFactoryContext> factory_context;
auto& secret_manager = factory_context.cluster_manager_.cluster_manager_factory_.secretManager();
OAuth2Config factory;
ProtobufTypes::MessagePtr proto_config = factory.createEmptyConfigProto();
TestUtility::loadFromYaml(yaml, *proto_config);
Server::Configuration::MockFactoryContext context;

// This returns non-nullptr for token_secret and hmac_secret.
auto& secret_manager = context.cluster_manager_.cluster_manager_factory_.secretManager();
ON_CALL(secret_manager, findStaticGenericSecretProvider(_))
.WillByDefault(Return(std::make_shared<Secret::GenericSecretConfigProviderImpl>(
envoy::extensions::transport_sockets::tls::v3::GenericSecret())));

OAuth2Config config;
auto cb = config.createFilterFactoryFromProtoTyped(proto_config, "whatever", factory_context);
EXPECT_CALL(context, messageValidationVisitor());
EXPECT_CALL(context, clusterManager());
EXPECT_CALL(context, scope());
EXPECT_CALL(context, timeSource());
EXPECT_CALL(context, api());
EXPECT_CALL(context, getTransportSocketFactoryContext());
Http::FilterFactoryCb cb = factory.createFilterFactoryFromProto(*proto_config, "stats", context);
Http::MockFilterChainFactoryCallbacks filter_callback;
EXPECT_CALL(filter_callback, addStreamDecoderFilter(_));
cb(filter_callback);
}

TEST(ConfigTest, InvalidTokenSecret) {
expectInvalidSecretConfig("token", "invalid token secret configuration");
}

NiceMock<Http::MockFilterChainFactoryCallbacks> filter_callbacks;
cb(filter_callbacks);
TEST(ConfigTest, InvalidHmacSecret) {
expectInvalidSecretConfig("hmac", "invalid HMAC secret configuration");
}

TEST(ConfigTest, CreateFilterMissingConfig) {
Expand All @@ -65,4 +134,4 @@ TEST(ConfigTest, CreateFilterMissingConfig) {
} // namespace Oauth2
} // namespace HttpFilters
} // namespace Extensions
} // namespace Envoy
} // namespace Envoy