Skip to content

Commit d164a21

Browse files
authored
string matcher: add optional context (#38924)
Adding an optional parameter to string matcher API to support use cases where the matching can be backed with per-stream context. A use case for example is described in #38862. Fixes #38862 Signed-off-by: Ohad Vano <[email protected]>
1 parent 865d9d7 commit d164a21

File tree

11 files changed

+229
-55
lines changed

11 files changed

+229
-55
lines changed

changelogs/current.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ minor_behavior_changes:
3535
AWS request signing and AWS Lambda extensions will now no longer return empty credentials (and fail to sign) when
3636
credentials are still pending from the async credential providers. If all providers are unable to retrieve credentials
3737
then the original behaviour with a signing failure will occur.
38+
- area: string_matcher
39+
change: |
40+
The string matcher API extended with another method that allows passing a context for the matching operation.
41+
The initial supporting use case is passing ``StreamInfo`` in the context when performing SAN matching.
3842
- area: formatter
3943
change: |
4044
The formatter ``%CEL%`` and ``%METADATA%`` will be treated as built-in formatters and could be used directly in the

contrib/hyperscan/matching/input_matchers/source/matcher.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class Matcher : public Envoy::Regex::CompiledMatcher, public Envoy::Matcher::Inp
3939

4040
// Envoy::Regex::CompiledMatcher
4141
bool match(absl::string_view value) const override;
42+
bool match(absl::string_view value, const Context&) const override { return match(value); }
4243
std::string replaceAll(absl::string_view value, absl::string_view substitution) const override;
4344

4445
// Envoy::Matcher::InputMatcher

envoy/common/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ envoy_cc_library(
7777
envoy_cc_library(
7878
name = "matchers_interface",
7979
hdrs = ["matchers.h"],
80+
deps = [
81+
":optref_lib",
82+
"//envoy/stream_info:stream_info_interface",
83+
],
8084
)
8185

8286
envoy_cc_library(

envoy/common/matchers.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
#include <memory>
44

5+
#include "envoy/common/optref.h"
56
#include "envoy/common/pure.h"
7+
#include "envoy/stream_info/stream_info.h"
68

79
#include "absl/strings/string_view.h"
810

@@ -16,10 +18,20 @@ class StringMatcher {
1618
public:
1719
virtual ~StringMatcher() = default;
1820

21+
struct Context {
22+
OptRef<const StreamInfo::StreamInfo> stream_info_;
23+
};
24+
1925
/**
2026
* Return whether a passed string value matches.
2127
*/
2228
virtual bool match(const absl::string_view value) const PURE;
29+
30+
/**
31+
* Return whether a passed string value matches with context.
32+
* Because most implementations don't use the context, provides a default implementation.
33+
*/
34+
virtual bool match(const absl::string_view value, const Context&) const { return match(value); }
2335
};
2436

2537
using StringMatcherPtr = std::unique_ptr<const StringMatcher>;

source/common/common/matchers.h

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ class ExactStringMatcher {
9898
: exact_(exact), ignore_case_(ignore_case) {}
9999

100100
// StringMatcher
101-
bool match(const absl::string_view value) const {
101+
bool match(const absl::string_view value, OptRef<const StringMatcher::Context>) const {
102102
return ignore_case_ ? absl::EqualsIgnoreCase(value, exact_) : value == exact_;
103103
}
104104

@@ -116,7 +116,7 @@ class PrefixStringMatcher {
116116
: prefix_(prefix), ignore_case_(ignore_case) {}
117117

118118
// StringMatcher
119-
bool match(const absl::string_view value) const {
119+
bool match(const absl::string_view value, OptRef<const StringMatcher::Context>) const {
120120
return ignore_case_ ? absl::StartsWithIgnoreCase(value, prefix_)
121121
: absl::StartsWith(value, prefix_);
122122
}
@@ -136,7 +136,7 @@ class SuffixStringMatcher {
136136
: suffix_(suffix), ignore_case_(ignore_case) {}
137137

138138
// StringMatcher
139-
bool match(const absl::string_view value) const {
139+
bool match(const absl::string_view value, OptRef<const StringMatcher::Context>) const {
140140
return ignore_case_ ? absl::EndsWithIgnoreCase(value, suffix_) : absl::EndsWith(value, suffix_);
141141
}
142142

@@ -161,7 +161,9 @@ class RegexStringMatcher {
161161
RegexStringMatcher(RegexStringMatcher&& other) noexcept { regex_ = std::move(other.regex_); }
162162

163163
// StringMatcher
164-
bool match(const absl::string_view value) const { return regex_->match(value); }
164+
bool match(const absl::string_view value, OptRef<const StringMatcher::Context>) const {
165+
return regex_->match(value);
166+
}
165167

166168
const std::string& stringRepresentation() const { return regex_->pattern(); }
167169

@@ -177,7 +179,7 @@ class ContainsStringMatcher {
177179
ignore_case_(ignore_case) {}
178180

179181
// StringMatcher
180-
bool match(const absl::string_view value) const {
182+
bool match(const absl::string_view value, OptRef<const StringMatcher::Context>) const {
181183
return ignore_case_ ? absl::StrContains(absl::AsciiStrToLower(value), contents_)
182184
: absl::StrContains(value, contents_);
183185
}
@@ -204,7 +206,13 @@ class CustomStringMatcher {
204206
: custom_(getExtensionStringMatcher(custom, context)) {}
205207

206208
// StringMatcher
207-
bool match(const absl::string_view value) const { return custom_->match(value); }
209+
bool match(const absl::string_view value, OptRef<const StringMatcher::Context> context) const {
210+
if (context) {
211+
return custom_->match(value, context.ref());
212+
}
213+
214+
return custom_->match(value);
215+
}
208216

209217
const std::string& stringRepresentation() const { return EMPTY_STRING; }
210218

@@ -232,11 +240,9 @@ class StringMatcherImpl : public ValueMatcher, public StringMatcher {
232240
}
233241

234242
// StringMatcher
235-
bool match(const absl::string_view value) const override {
236-
// Implementing polymorphism for match(absl::string_value) on the different
237-
// types that can be in the matcher_ variant.
238-
auto call_match = [value](const auto& obj) -> bool { return obj.match(value); };
239-
return absl::visit(call_match, matcher_);
243+
bool match(const absl::string_view value) const override { return doMatch(value, absl::nullopt); }
244+
bool match(const absl::string_view value, const StringMatcher::Context& context) const override {
245+
return doMatch(value, makeOptRef(context));
240246
}
241247

242248
// ValueMatcher
@@ -311,6 +317,16 @@ class StringMatcherImpl : public ValueMatcher, public StringMatcher {
311317
}
312318
}
313319

320+
bool doMatch(const absl::string_view value, OptRef<const StringMatcher::Context> context) const {
321+
// Implementing polymorphism for match(absl::string_value) on the different
322+
// types that can be in the matcher_ variant.
323+
auto call_match = [value, context](const auto& obj) -> bool {
324+
return obj.match(value, context);
325+
};
326+
327+
return absl::visit(call_match, matcher_);
328+
}
329+
314330
StringMatcherVariant matcher_;
315331
};
316332

source/common/tls/cert_validator/default_validator.cc

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ absl::StatusOr<int> DefaultCertValidator::initializeSslContexts(std::vector<SSL_
203203
bool DefaultCertValidator::verifyCertAndUpdateStatus(
204204
X509* leaf_cert, absl::string_view sni,
205205
const Network::TransportSocketOptions* transport_socket_options,
206+
const CertValidator::ExtraValidationContext& validation_context,
206207
Envoy::Ssl::ClientValidationStatus& detailed_status, std::string* error_details,
207208
uint8_t* out_alert) {
208209

@@ -218,9 +219,13 @@ bool DefaultCertValidator::verifyCertAndUpdateStatus(
218219
match_sni_san.emplace_back(std::make_unique<DnsExactStringSanMatcher>(sni));
219220
match_san_override = match_sni_san;
220221
}
221-
Envoy::Ssl::ClientValidationStatus validated = verifyCertificate(
222-
leaf_cert, verify_san_override.value_or(std::vector<std::string>()),
223-
match_san_override.value_or(subject_alt_name_matchers_), error_details, out_alert);
222+
Envoy::Ssl::ClientValidationStatus validated =
223+
verifyCertificate(leaf_cert, verify_san_override.value_or(std::vector<std::string>()),
224+
match_san_override.value_or(subject_alt_name_matchers_),
225+
validation_context.callbacks != nullptr
226+
? makeOptRef(validation_context.callbacks->connection().streamInfo())
227+
: absl::nullopt,
228+
error_details, out_alert);
224229

225230
if (detailed_status == Envoy::Ssl::ClientValidationStatus::NotValidated ||
226231
validated != Envoy::Ssl::ClientValidationStatus::NotValidated) {
@@ -241,6 +246,7 @@ bool DefaultCertValidator::verifyCertAndUpdateStatus(
241246
Envoy::Ssl::ClientValidationStatus
242247
DefaultCertValidator::verifyCertificate(X509* cert, const std::vector<std::string>& verify_san_list,
243248
const std::vector<SanMatcherPtr>& subject_alt_name_matchers,
249+
OptRef<const StreamInfo::StreamInfo> stream_info,
244250
std::string* error_details, uint8_t* out_alert) {
245251
Envoy::Ssl::ClientValidationStatus validated = Envoy::Ssl::ClientValidationStatus::NotValidated;
246252
if (!verify_san_list.empty()) {
@@ -257,7 +263,7 @@ DefaultCertValidator::verifyCertificate(X509* cert, const std::vector<std::strin
257263
}
258264

259265
if (!subject_alt_name_matchers.empty()) {
260-
if (!matchSubjectAltName(cert, subject_alt_name_matchers)) {
266+
if (!matchSubjectAltName(cert, stream_info, subject_alt_name_matchers)) {
261267
const char* error = "verify cert failed: SAN matcher";
262268
if (error_details != nullptr) {
263269
*error_details = error;
@@ -299,7 +305,7 @@ DefaultCertValidator::verifyCertificate(X509* cert, const std::vector<std::strin
299305
ValidationResults DefaultCertValidator::doVerifyCertChain(
300306
STACK_OF(X509)& cert_chain, Ssl::ValidateResultCallbackPtr /*callback*/,
301307
const Network::TransportSocketOptionsConstSharedPtr& transport_socket_options, SSL_CTX& ssl_ctx,
302-
const CertValidator::ExtraValidationContext& /*validation_context*/, bool is_server,
308+
const CertValidator::ExtraValidationContext& context, bool is_server,
303309
absl::string_view host_name) {
304310
if (sk_X509_num(&cert_chain) == 0) {
305311
stats_.fail_verify_error_.inc();
@@ -352,7 +358,7 @@ ValidationResults DefaultCertValidator::doVerifyCertChain(
352358
std::string error_details;
353359
uint8_t tls_alert = SSL_AD_CERTIFICATE_UNKNOWN;
354360
const bool succeeded =
355-
verifyCertAndUpdateStatus(leaf_cert, host_name, transport_socket_options.get(),
361+
verifyCertAndUpdateStatus(leaf_cert, host_name, transport_socket_options.get(), context,
356362
detailed_status, &error_details, &tls_alert);
357363
return succeeded ? ValidationResults{ValidationResults::ValidationStatus::Successful,
358364
detailed_status, absl::nullopt, absl::nullopt}
@@ -380,15 +386,20 @@ bool DefaultCertValidator::verifySubjectAltName(X509* cert,
380386
}
381387

382388
bool DefaultCertValidator::matchSubjectAltName(
383-
X509* cert, const std::vector<SanMatcherPtr>& subject_alt_name_matchers) {
389+
X509* cert, OptRef<const StreamInfo::StreamInfo> stream_info,
390+
const std::vector<SanMatcherPtr>& subject_alt_name_matchers) {
384391
bssl::UniquePtr<GENERAL_NAMES> san_names(
385392
static_cast<GENERAL_NAMES*>(X509_get_ext_d2i(cert, NID_subject_alt_name, nullptr, nullptr)));
386393
if (san_names == nullptr) {
387394
return false;
388395
}
389396
for (const auto& config_san_matcher : subject_alt_name_matchers) {
390397
for (const GENERAL_NAME* general_name : san_names.get()) {
391-
if (config_san_matcher->match(general_name)) {
398+
if (stream_info) {
399+
if (config_san_matcher->match(general_name, stream_info.ref())) {
400+
return true;
401+
}
402+
} else if (config_san_matcher->match(general_name)) {
392403
return true;
393404
}
394405
}

source/common/tls/cert_validator/default_validator.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ class DefaultCertValidator : public CertValidator, Logger::Loggable<Logger::Id::
6262
Envoy::Ssl::ClientValidationStatus
6363
verifyCertificate(X509* cert, const std::vector<std::string>& verify_san_list,
6464
const std::vector<SanMatcherPtr>& subject_alt_name_matchers,
65-
std::string* error_details, uint8_t* out_alert);
65+
OptRef<const StreamInfo::StreamInfo> stream_info, std::string* error_details,
66+
uint8_t* out_alert);
6667

6768
/**
6869
* Verifies certificate hash for pinning. The hash is a hex-encoded SHA-256 of the DER-encoded
@@ -100,12 +101,13 @@ class DefaultCertValidator : public CertValidator, Logger::Loggable<Logger::Id::
100101
* @param subject_alt_name_matchers the configured matchers to match
101102
* @return true if the verification succeeds
102103
*/
103-
static bool matchSubjectAltName(X509* cert,
104+
static bool matchSubjectAltName(X509* cert, OptRef<const StreamInfo::StreamInfo> stream_info,
104105
const std::vector<SanMatcherPtr>& subject_alt_name_matchers);
105106

106107
private:
107108
bool verifyCertAndUpdateStatus(X509* leaf_cert, absl::string_view sni,
108109
const Network::TransportSocketOptions* transport_socket_options,
110+
const CertValidator::ExtraValidationContext& validation_context,
109111
Envoy::Ssl::ClientValidationStatus& detailed_status,
110112
std::string* error_details, uint8_t* out_alert);
111113

source/common/tls/cert_validator/san_matcher.cc

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Extensions {
1414
namespace TransportSockets {
1515
namespace Tls {
1616

17-
bool StringSanMatcher::match(const GENERAL_NAME* general_name) const {
17+
bool StringSanMatcher::typeCompatible(const GENERAL_NAME* general_name) const {
1818
if (general_name->type != general_name_type_) {
1919
return false;
2020
}
@@ -23,9 +23,28 @@ bool StringSanMatcher::match(const GENERAL_NAME* general_name) const {
2323
return false;
2424
}
2525
}
26+
27+
return true;
28+
}
29+
30+
bool StringSanMatcher::match(const GENERAL_NAME* general_name) const {
31+
if (!typeCompatible(general_name)) {
32+
return false;
33+
}
34+
2635
return matcher_.match(Utility::generalNameAsString(general_name));
2736
}
2837

38+
bool StringSanMatcher::match(const GENERAL_NAME* general_name,
39+
const StreamInfo::StreamInfo& stream_info) const {
40+
if (!typeCompatible(general_name)) {
41+
return false;
42+
}
43+
44+
Matchers::StringMatcher::Context context{makeOptRef(stream_info)};
45+
return matcher_.match(Utility::generalNameAsString(general_name), context);
46+
}
47+
2948
bool DnsExactStringSanMatcher::match(const GENERAL_NAME* general_name) const {
3049
if (general_name->type != GEN_DNS) {
3150
return false;

source/common/tls/cert_validator/san_matcher.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ namespace Ssl {
2424
class SanMatcher {
2525
public:
2626
virtual bool match(GENERAL_NAME const*) const PURE;
27+
virtual bool match(GENERAL_NAME const* general_name, const StreamInfo::StreamInfo&) const {
28+
return match(general_name);
29+
}
2730
virtual ~SanMatcher() = default;
2831
};
2932

@@ -40,6 +43,8 @@ using Ssl::SanMatcherPtr;
4043
class StringSanMatcher : public SanMatcher {
4144
public:
4245
bool match(const GENERAL_NAME* general_name) const override;
46+
bool match(const GENERAL_NAME* general_name,
47+
const StreamInfo::StreamInfo& stream_info) const override;
4348
~StringSanMatcher() override = default;
4449

4550
StringSanMatcher(int general_name_type, envoy::type::matcher::v3::StringMatcher matcher,
@@ -55,6 +60,8 @@ class StringSanMatcher : public SanMatcher {
5560
}
5661

5762
private:
63+
bool typeCompatible(const GENERAL_NAME* general_name) const;
64+
5865
const int general_name_type_;
5966
const Envoy::Matchers::StringMatcherImpl matcher_;
6067
bssl::UniquePtr<ASN1_OBJECT> general_name_oid_;

0 commit comments

Comments
 (0)