1
#include "cilium/network_policy.h"
2

            
3
#include <fmt/format.h>
4
#include <openssl/mem.h>
5

            
6
#include <algorithm>
7
#include <functional>
8
#include <memory>
9
#include <string>
10
#include <utility>
11
#include <vector>
12

            
13
#include "envoy/common/exception.h"
14
#include "envoy/common/matchers.h"
15
#include "envoy/common/optref.h"
16
#include "envoy/config/core/v3/address.pb.h"
17
#include "envoy/config/core/v3/base.pb.h"
18
#include "envoy/config/subscription.h"
19
#include "envoy/http/header_map.h"
20
#include "envoy/init/manager.h"
21
#include "envoy/network/address.h"
22
#include "envoy/server/factory_context.h"
23
#include "envoy/ssl/context.h"
24
#include "envoy/ssl/context_config.h"
25
#include "envoy/stats/scope.h"
26
#include "envoy/stats/stats_macros.h"
27
#include "envoy/thread_local/thread_local.h"
28
#include "envoy/type/matcher/v3/metadata.pb.h"
29

            
30
#include "source/common/common/assert.h"
31
#include "source/common/common/logger.h"
32
#include "source/common/common/matchers.h"
33
#include "source/common/common/thread.h"
34
#include "source/common/http/header_utility.h"
35
#include "source/common/init/manager_impl.h"
36
#include "source/common/init/target_impl.h"
37
#include "source/common/init/watcher_impl.h"
38
#include "source/common/network/utility.h"
39
#include "source/common/protobuf/protobuf.h"
40
#include "source/common/protobuf/utility.h"
41
#include "source/extensions/config_subscription/grpc/grpc_subscription_impl.h"
42
#include "source/server/transport_socket_config_impl.h"
43

            
44
#include "absl/container/btree_set.h"
45
#include "absl/container/flat_hash_set.h"
46
#include "absl/status/status.h"
47
#include "absl/strings/string_view.h"
48
#include "cilium/accesslog.h"
49
#include "cilium/api/npds.pb.h"
50
#include "cilium/conntrack.h"
51
#include "cilium/grpc_subscription.h"
52
#include "cilium/ipcache.h"
53
#include "cilium/secret_watcher.h"
54

            
55
namespace Envoy {
56
namespace Cilium {
57

            
58
uint64_t NetworkPolicyMapImpl::instance_id_ = 0;
59

            
60
198
IpAddressPair::IpAddressPair(const cilium::NetworkPolicy& proto) {
61
247
  for (const auto& ip_addr : proto.endpoint_ips()) {
62
247
    auto ip = Network::Utility::parseInternetAddressNoThrow(ip_addr);
63
247
    if (ip) {
64
246
      switch (ip->ip()->version()) {
65
198
      case Network::Address::IpVersion::v4:
66
198
        ipv4_ = std::move(ip);
67
198
        break;
68
48
      case Network::Address::IpVersion::v6:
69
48
        ipv6_ = std::move(ip);
70
48
        break;
71
246
      }
72
246
    }
73
247
  }
74
198
}
75

            
76
class HeaderMatch : public Logger::Loggable<Logger::Id::config> {
77
public:
78
  HeaderMatch(const NetworkPolicyMapImpl& parent, const cilium::HeaderMatch& config)
79
455
      : name_(config.name()), value_(config.value()), match_action_(config.match_action()),
80
455
        mismatch_action_(config.mismatch_action()) {
81
455
    if (config.value_sds_secret().length() > 0) {
82
78
      secret_ = std::make_unique<SecretWatcher>(parent, config.value_sds_secret());
83
78
    }
84
455
  }
85

            
86
14
  void logRejected(Cilium::AccessLog::Entry& log_entry, absl::string_view value) const {
87
14
    log_entry.addRejected(name_.get(), !secret_ ? value : "[redacted]");
88
14
  }
89

            
90
36
  void logMissing(Cilium::AccessLog::Entry& log_entry, absl::string_view value) const {
91
36
    log_entry.addMissing(name_.get(), !secret_ ? value : "[redacted]");
92
36
  }
93

            
94
  // Returns 'true' if matching can continue
95
59
  bool allowed(Envoy::Http::RequestHeaderMap& headers, Cilium::AccessLog::Entry& log_entry) const {
96
59
    bool matches = false;
97
59
    const std::string* match_value = &value_;
98
59
    const auto header_value = Http::HeaderUtility::getAllOfHeaderAsString(headers, name_);
99

            
100
    // Get secret value?
101
59
    if (secret_) {
102
18
      auto* secret_value = secret_->value();
103
18
      if (secret_value) {
104
15
        match_value = secret_value;
105
16
      } else if (value_.length() == 0) {
106
        // fail if secret has no value and the inline value to match is also empty
107
3
        ENVOY_LOG(info, "Cilium HeaderMatch missing SDS secret value for header {}", name_);
108
3
        return false;
109
3
      }
110
18
    }
111

            
112
    // Perform presence match if the value to match is empty
113
56
    bool is_present_match = match_value->length() == 0;
114
56
    if (is_present_match) {
115
24
      matches = header_value.result().has_value();
116
32
    } else if (header_value.result().has_value()) {
117
12
      const absl::string_view val = header_value.result().value();
118
12
      if (val.length() == match_value->length()) {
119
        // Use constant time comparison for security reason
120
6
        matches = CRYPTO_memcmp(val.data(), match_value->data(), match_value->length()) == 0;
121
6
      }
122
12
    }
123

            
124
56
    if (matches) {
125
      // Match action
126
10
      switch (match_action_) {
127
4
      case cilium::HeaderMatch::CONTINUE_ON_MATCH:
128
4
        return true;
129
4
      case cilium::HeaderMatch::FAIL_ON_MATCH:
130
4
      default: // fail closed if unknown action
131
4
        logRejected(log_entry, *match_value);
132
4
        return false;
133
2
      case cilium::HeaderMatch::DELETE_ON_MATCH:
134
2
        logRejected(log_entry, *match_value);
135
2
        headers.remove(name_);
136
2
        return true;
137
10
      }
138
46
    } else {
139
      // Mismatch action
140
46
      switch (mismatch_action_) {
141
      case cilium::HeaderMatch::FAIL_ON_MISMATCH:
142
      default:
143
        logMissing(log_entry, *match_value);
144
        return false;
145
18
      case cilium::HeaderMatch::CONTINUE_ON_MISMATCH:
146
18
        logMissing(log_entry, *match_value);
147
18
        return true;
148
7
      case cilium::HeaderMatch::ADD_ON_MISMATCH:
149
7
        headers.addCopy(name_, *match_value);
150
7
        logMissing(log_entry, *match_value);
151
7
        return true;
152
10
      case cilium::HeaderMatch::DELETE_ON_MISMATCH:
153
10
        if (is_present_match) {
154
          // presence match failed, nothing to do
155
          return true;
156
        }
157
10
        if (!header_value.result().has_value()) {
158
6
          return true; // nothing to remove
159
6
        }
160

            
161
        // Remove the header with an incorrect value
162
4
        headers.remove(name_);
163
4
        logRejected(log_entry, header_value.result().value());
164
4
        return true;
165
11
      case cilium::HeaderMatch::REPLACE_ON_MISMATCH:
166
        // Log the wrong value as rejected, if the header existed with a wrong value
167
11
        if (header_value.result().has_value()) {
168
4
          logRejected(log_entry, header_value.result().value());
169
4
        }
170
        // Set the expected value
171
11
        headers.setCopy(name_, *match_value);
172
        // Log the expected value as missing
173
11
        logMissing(log_entry, *match_value);
174
11
        return true;
175
46
      }
176
46
    }
177
    IS_ENVOY_BUG("HeaderMatch reached unreachable return");
178
    return false;
179
56
  }
180

            
181
  void toString(int indent, std::string& res) const {
182
    res.append(indent - 2, ' ').append("- name: \"").append(name_.get()).append("\"\n");
183
    if (value_.length() > 0) {
184
      res.append(indent, ' ').append("value: \"").append(value_).append("\"\n");
185
    }
186
    if (secret_) {
187
      res.append(indent, ' ').append("secret: \"").append(secret_->name()).append("\"\n");
188
    }
189
    const char* match_actions[] = {"CONTINUE", "FAIL", "DELETE", "UNKNOWN"};
190
    res.append(indent, ' ')
191
        .append("match_action: ")
192
        .append(match_actions[std::max(int(match_action_), 3)])
193
        .append("\n");
194

            
195
    const char* mismatch_actions[] = {"FAIL", "CONTINUE", "ADD", "DELETE", "REPLACE", "UNKNOWN"};
196
    res.append(indent, ' ')
197
        .append("mismatch_action: ")
198
        .append(mismatch_actions[std::max(int(mismatch_action_), 5)])
199
        .append("\n");
200
  }
201

            
202
  const Http::LowerCaseString name_;
203
  std::string value_;
204
  cilium::HeaderMatch::MatchAction match_action_;
205
  cilium::HeaderMatch::MismatchAction mismatch_action_;
206
  SecretWatcherPtr secret_;
207
};
208

            
209
class HttpNetworkPolicyRule : public Logger::Loggable<Logger::Id::config> {
210
public:
211
  HttpNetworkPolicyRule(const NetworkPolicyMapImpl& parent,
212
753
                        const cilium::HttpNetworkPolicyRule& rule) {
213
753
    ENVOY_LOG(trace, "Cilium L7 HttpNetworkPolicyRule():");
214
753
    headers_.reserve(rule.headers().size());
215
871
    for (const auto& header : rule.headers()) {
216
871
      headers_.emplace_back(Http::HeaderUtility::createHeaderData(
217
871
          header, parent.transportFactoryContext().serverFactoryContext()));
218

            
219
871
      auto value = header.has_range_match()   ? fmt::format("[{}-{})", header.range_match().start(),
220
                                                            header.range_match().end())
221
871
                   : header.has_exact_match() ? "<VALUE>"
222
871
                   : header.has_present_match()    ? "<PRESENT>"
223
242
                   : header.has_safe_regex_match() ? "<REGEX>"
224
242
                                                   : "<UNKNOWN>";
225
871
      ENVOY_LOG(trace, "Cilium L7 HttpNetworkPolicyRule(): HeaderData {}={}", header.name(), value);
226
871
    }
227
753
    header_matches_.reserve(rule.header_matches().size());
228
753
    for (const auto& config : rule.header_matches()) {
229
455
      header_matches_.emplace_back(parent, config);
230
455
      const auto& header_match = header_matches_.back();
231
455
      ENVOY_LOG(trace,
232
455
                "Cilium L7 HttpNetworkPolicyRule(): HeaderMatch {}={} (match: {}, mismatch: {})",
233
455
                header_match.name_.get(),
234
455
                header_match.secret_ ? fmt::format("<SECRET {}>", header_match.secret_->name())
235
455
                : !header_match.value_.empty() ? header_match.value_
236
455
                                               : "<PRESENT>",
237
455
                cilium::HeaderMatch::MatchAction_Name(header_match.match_action_),
238
455
                cilium::HeaderMatch::MismatchAction_Name(header_match.mismatch_action_));
239
455
    }
240
753
  }
241

            
242
347
  bool allowed(const Envoy::Http::RequestHeaderMap& headers) const {
243
    // Empty set matches any headers.
244
347
    return Http::HeaderUtility::matchHeaders(headers, headers_);
245
347
  }
246

            
247
  // Should only be called after 'allowed' returns 'true'.
248
  // Returns 'true' if matching can continue
249
  bool headerMatches(Envoy::Http::RequestHeaderMap& headers,
250
38
                     Cilium::AccessLog::Entry& log_entry) const {
251
38
    bool accepted = true;
252
59
    for (const auto& header_match : header_matches_) {
253
59
      if (!header_match.allowed(headers, log_entry)) {
254
7
        accepted = false;
255
7
      }
256
59
    }
257
38
    return accepted;
258
38
  }
259

            
260
19
  void toString(int indent, std::string& res) const {
261
19
    bool first = true;
262
19
    if (!headers_.empty()) {
263
19
      if (first) {
264
19
        first = false;
265
19
        res.append(indent - 2, ' ').append("- ");
266
19
      } else {
267
        res.append(indent, ' ');
268
      }
269
19
      res.append("headers:\n");
270
19
      for (auto& h : headers_) {
271
19
        if (const auto v = dynamic_cast<Http::HeaderUtility::HeaderDataBaseImpl*>(h.get())) {
272
19
          res.append(indent, ' ').append("- name: \"").append(v->name_).append("\"\n");
273
19
        }
274

            
275
19
        if (const auto v = dynamic_cast<Http::HeaderUtility::HeaderDataExactMatch*>(h.get())) {
276
15
          res.append(indent + 2, ' ').append("value: \"").append(v->expected_value_).append("\"\n");
277
15
        } else if (const auto v =
278
4
                       dynamic_cast<Http::HeaderUtility::HeaderDataRegexMatch*>(h.get())) {
279
4
          res.append(indent + 2, ' ').append("regex: ").append("<hidden>\n");
280
4
        } else if (const auto v =
281
                       dynamic_cast<Http::HeaderUtility::HeaderDataRangeMatch*>(h.get())) {
282
          res.append(indent + 2, ' ')
283
              .append("range: ")
284
              .append(fmt::format("[{}-{})\n", v->range_start_, v->range_end_));
285
        } else if (const auto v =
286
                       dynamic_cast<Http::HeaderUtility::HeaderDataPresentMatch*>(h.get())) {
287
          res.append(indent + 2, ' ')
288
              .append("present: ")
289
              .append(v->present_ ? "true\n" : "false\n");
290
        } else if (const auto v =
291
                       dynamic_cast<Http::HeaderUtility::HeaderDataPrefixMatch*>(h.get())) {
292
          res.append(indent + 2, ' ').append("prefix: \"").append(v->prefix_).append("\"\n");
293
        } else if (const auto v =
294
                       dynamic_cast<Http::HeaderUtility::HeaderDataSuffixMatch*>(h.get())) {
295
          res.append(indent + 2, ' ').append("suffix: \"").append(v->suffix_).append("\"\n");
296
        } else if (const auto v =
297
                       dynamic_cast<Http::HeaderUtility::HeaderDataContainsMatch*>(h.get())) {
298
          res.append(indent + 2, ' ')
299
              .append("contains: \"")
300
              .append(v->expected_substr_)
301
              .append("\"\n");
302
        } else if (const auto v =
303
                       dynamic_cast<Http::HeaderUtility::HeaderDataStringMatch*>(h.get())) {
304
          res.append(indent + 2, ' ').append("string_match: ").append("<hidden>\n");
305
        }
306

            
307
19
        if (const auto v = dynamic_cast<Http::HeaderUtility::HeaderDataBaseImpl*>(h.get())) {
308
19
          if (v->invert_match_) {
309
            res.append(indent + 2, ' ').append("invert_match: true\n");
310
          }
311

            
312
19
          if (v->treat_missing_as_empty_) {
313
            res.append(indent + 2, ' ').append("treat_missing_as_empty: true\n");
314
          }
315
19
        }
316
19
      }
317
19
    }
318
19
    if (!header_matches_.empty()) {
319
      if (first) {
320
        // first = false; // not used after, so no need to update
321
        res.append(indent - 2, ' ').append("- ");
322
      } else {
323
        res.append(indent, ' ');
324
      }
325
      res.append("header_matches:\n");
326
      for (auto& hm : header_matches_) {
327
        hm.toString(indent + 2, res);
328
      }
329
    }
330
19
  }
331

            
332
  std::vector<Http::HeaderUtility::HeaderDataPtr> headers_; // Allowed if empty.
333
  std::vector<HeaderMatch> header_matches_;
334
};
335

            
336
class L7NetworkPolicyRule : public Logger::Loggable<Logger::Id::config> {
337
public:
338
  L7NetworkPolicyRule(const NetworkPolicyMapImpl& parent, const cilium::L7NetworkPolicyRule& rule)
339
      : name_(rule.name()) {
340
    for (const auto& matcher : rule.metadata_rule()) {
341
      metadata_matchers_.emplace_back(matcher,
342
                                      parent.transportFactoryContext().serverFactoryContext());
343
      matchers_.emplace_back(matcher);
344
    }
345
  }
346

            
347
  bool matches(const envoy::config::core::v3::Metadata& metadata) const {
348
    // All matchers must be satisfied for the rule to match
349
    for (const auto& metadata_matcher : metadata_matchers_) {
350
      if (!metadata_matcher.match(metadata)) {
351
        return false;
352
      }
353
    }
354
    return true;
355
  }
356

            
357
  void toString(int indent, std::string& res) const {
358
    res.append(indent - 2, ' ').append("- name: \"").append(name_).append("\"\n");
359
  }
360

            
361
  std::string name_;
362

            
363
private:
364
  std::vector<Envoy::Matchers::MetadataMatcher> metadata_matchers_;
365
  std::vector<envoy::type::matcher::v3::MetadataMatcher> matchers_;
366
};
367

            
368
class PortNetworkPolicyRule : public Logger::Loggable<Logger::Id::config> {
369
public:
370
  PortNetworkPolicyRule(const NetworkPolicyMapImpl& parent,
371
                        const cilium::PortNetworkPolicyRule& rule)
372
484
      : name_(rule.name()), deny_(rule.deny()), precedence_(rule.precedence()),
373
484
        proxy_id_(rule.proxy_id()), l7_proto_(rule.l7_proto()) {
374
533
    for (const auto& remote : rule.remote_policies()) {
375
532
      ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule(): {} remote {} by rule: {}",
376
532
                deny_ ? "Denying" : "Allowing", remote, name_);
377
532
      remotes_.emplace(remote);
378
532
    }
379
484
    if (rule.has_downstream_tls_context()) {
380
21
      auto config = rule.downstream_tls_context();
381
21
      server_context_ = std::make_unique<DownstreamTLSContext>(parent, config);
382
21
    }
383
484
    if (rule.has_upstream_tls_context()) {
384
21
      auto config = rule.upstream_tls_context();
385
21
      client_context_ = std::make_unique<UpstreamTLSContext>(parent, config);
386
21
    }
387
484
    for (const auto& sni : rule.server_names()) {
388
6
      ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule(): Allowing SNI {} by rule {}", sni, name_);
389
6
      allowed_snis_.emplace_back(sni);
390
6
    }
391
484
    if (rule.has_http_rules()) {
392
753
      for (const auto& http_rule : rule.http_rules().http_rules()) {
393
753
        if (http_rule.header_matches_size() > 0) {
394
303
          has_headermatches_ = true;
395
303
        }
396
753
        http_rules_.emplace_back(parent, http_rule);
397
753
      }
398
279
    }
399
484
    if (l7_proto_.length() > 0 && rule.has_l7_rules()) {
400
      const auto& ruleset = rule.l7_rules();
401
      for (const auto& l7_rule : ruleset.l7_deny_rules()) {
402
        l7_deny_rules_.emplace_back(parent, l7_rule);
403
      }
404
      for (const auto& l7_rule : ruleset.l7_allow_rules()) {
405
        l7_allow_rules_.emplace_back(parent, l7_rule);
406
      }
407
    }
408
484
  }
409

            
410
1033
  bool allowed(uint32_t proxy_id, uint32_t remote_id, bool& denied) const {
411
    // proxy_id must match if we have any.
412
1033
    if (proxy_id_ != 0 && proxy_id != proxy_id_) {
413
3
      return false;
414
3
    }
415
    // Remote ID must match if we have any.
416
1030
    if (!remotes_.empty()) {
417
1016
      auto match = remotes_.find(remote_id);
418
1016
      if (match != remotes_.end()) {
419
        // remote ID matched
420
653
        if (deny_) {
421
          // Explicit deny
422
6
          denied = true;
423
6
          return false;
424
6
        }
425
        // Explicit allow
426
647
        return true;
427
653
      }
428
      // Not found, not allowed, but also not explicitly denied
429
363
      return false;
430
1016
    }
431
    // Allow rules allow by default when remotes_ is empty, deny rules do not
432
14
    if (deny_) {
433
10
      denied = true;
434
10
      return false;
435
10
    }
436
4
    return true;
437
14
  }
438

            
439
472
  bool allowed(uint32_t proxy_id, uint32_t remote_id, absl::string_view sni, bool& denied) const {
440
    // sni must match if we have any
441
472
    if (!allowed_snis_.empty()) {
442
30
      if (sni.length() == 0) {
443
6
        return false;
444
6
      }
445
24
      bool matched = false;
446
42
      for (const auto& pattern : allowed_snis_) {
447
42
        if (pattern.matches(sni)) {
448
18
          matched = true;
449
18
          break;
450
18
        }
451
42
      }
452
24
      if (!matched) {
453
6
        return false;
454
6
      }
455
24
    }
456
460
    return allowed(proxy_id, remote_id, denied);
457
472
  }
458

            
459
  bool allowed(uint32_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers,
460
194
               Cilium::AccessLog::Entry& log_entry, bool& denied) const {
461
194
    if (!allowed(proxy_id, remote_id, denied)) {
462
70
      return false;
463
70
    }
464
124
    if (!http_rules_.empty()) {
465
122
      bool allowed = false;
466
347
      for (const auto& rule : http_rules_) {
467
347
        if (rule.allowed(headers)) {
468
          // Return on the first match if no rule has HeaderMatches
469
76
          if (!has_headermatches_) {
470
38
            allowed = true;
471
38
            break;
472
38
          }
473
          // orherwise evaluate all rules to run all the header actions,
474
          // and remember if any of them matched
475
38
          if (rule.headerMatches(headers, log_entry)) {
476
31
            allowed = true;
477
31
          }
478
38
        }
479
347
      }
480
122
      return allowed;
481
122
    }
482
    // Empty set matches any payload
483
2
    return true;
484
124
  }
485

            
486
  bool useProxylib(uint32_t proxy_id, uint32_t remote_id, std::string& l7_proto,
487
379
                   bool& denied) const {
488
379
    if (!allowed(proxy_id, remote_id, denied)) {
489
140
      return false;
490
140
    }
491
239
    if (l7_proto_.length() > 0) {
492
36
      ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule::useProxylib(): returning {}", l7_proto_);
493
36
      l7_proto = l7_proto_;
494
36
      return true;
495
36
    }
496
203
    return false;
497
239
  }
498

            
499
  // Envoy Metadata matcher, called after deny has already been checked for
500
  bool allowed(uint32_t proxy_id, uint32_t remote_id,
501
               const envoy::config::core::v3::Metadata& metadata, bool& denied) const {
502
    if (!allowed(proxy_id, remote_id, denied)) {
503
      return false;
504
    }
505
    for (const auto& rule : l7_deny_rules_) {
506
      if (rule.matches(metadata)) {
507
        ENVOY_LOG(trace,
508
                  "Cilium L7 PortNetworkPolicyRule::allowed(): DENY due to "
509
                  "a matching deny rule {}",
510
                  rule.name_);
511
        return false; // request is denied if any deny rule matches
512
      }
513
    }
514
    if (!l7_allow_rules_.empty()) {
515
      for (const auto& rule : l7_allow_rules_) {
516
        if (rule.matches(metadata)) {
517
          ENVOY_LOG(trace,
518
                    "Cilium L7 PortNetworkPolicyRule::allowed(): ALLOW due "
519
                    "to a matching allow rule {}",
520
                    rule.name_);
521
          return true;
522
        }
523
      }
524
      ENVOY_LOG(trace,
525
                "Cilium L7 PortNetworkPolicyRule::allowed(): DENY due to "
526
                "all {} allow rules mismatching",
527
                l7_allow_rules_.size());
528
      return false;
529
    }
530
    ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicyRule::allowed(): default ALLOW "
531
                     "due to no allow rules");
532
    return true; // allowed by default
533
  }
534

            
535
  Ssl::ContextSharedPtr getServerTlsContext(uint32_t proxy_id, uint32_t remote_id,
536
                                            absl::string_view sni,
537
                                            const Ssl::ContextConfig** config,
538
33
                                            bool& raw_socket_allowed, bool& denied) const {
539
33
    if (allowed(proxy_id, remote_id, sni, denied)) {
540
18
      if (server_context_) {
541
14
        *config = &server_context_->getTlsContextConfig();
542
14
        return server_context_->getTlsContext();
543
14
      }
544
4
      raw_socket_allowed = true;
545
4
    }
546
19
    return nullptr;
547
33
  }
548

            
549
  Ssl::ContextSharedPtr getClientTlsContext(uint32_t proxy_id, uint32_t remote_id,
550
                                            absl::string_view sni,
551
                                            const Ssl::ContextConfig** config,
552
29
                                            bool& raw_socket_allowed, bool& denied) const {
553
29
    if (allowed(proxy_id, remote_id, sni, denied)) {
554
21
      if (client_context_) {
555
21
        *config = &client_context_->getTlsContextConfig();
556
21
        return client_context_->getTlsContext();
557
21
      }
558
      raw_socket_allowed = true;
559
    }
560
8
    return nullptr;
561
29
  }
562

            
563
66
  void toString(int indent, std::string& res) const {
564
66
    res.append(indent - 2, ' ').append("- remotes: [");
565
66
    int count = 0;
566
66
    for (auto remote : remotes_) {
567
62
      if (count++ > 0) {
568
4
        res.append(",");
569
4
      }
570
62
      res.append(fmt::format("{}", remote));
571
62
    }
572
66
    res.append("]\n");
573

            
574
66
    if (name_.length() > 0) {
575
      res.append(indent, ' ').append("name: \"").append(name_).append("\"\n");
576
    }
577
66
    if (deny_) {
578
8
      res.append(indent, ' ').append("deny: true\n");
579
8
    }
580
66
    if (precedence_) {
581
4
      res.append(indent, ' ').append(fmt::format("precedence: {}\n", precedence_));
582
4
    }
583
66
    if (proxy_id_ != 0) {
584
4
      res.append(indent, ' ').append(fmt::format("proxy_id: {}\n", proxy_id_));
585
4
    }
586

            
587
66
    if (!allowed_snis_.empty()) {
588
      res.append(indent, ' ').append("allowed_snis: [");
589
      int count = 0;
590
      for (auto& sni : allowed_snis_) {
591
        if (count++ > 0) {
592
          res.append(",");
593
        }
594
        sni.toString(res);
595
      }
596
      res.append("]\n");
597
    }
598

            
599
66
    if (!http_rules_.empty()) {
600
19
      res.append(indent, ' ').append("http_rules:\n");
601
19
      for (auto& rule : http_rules_) {
602
19
        rule.toString(indent + 2, res);
603
19
      }
604
19
    }
605

            
606
66
    if (!l7_proto_.empty()) {
607
      res.append(indent, ' ').append("l7_proto: \"").append(l7_proto_).append("\"\n");
608
    }
609
66
    if (!l7_allow_rules_.empty()) {
610
      res.append(indent, ' ').append("l7_allow_rules:\n");
611
      for (auto& rule : l7_allow_rules_) {
612
        rule.toString(indent + 2, res);
613
      }
614
    }
615
66
    if (!l7_deny_rules_.empty()) {
616
      res.append(indent, ' ').append("l7_deny_rules:\n");
617
      for (auto& rule : l7_deny_rules_) {
618
        rule.toString(indent + 2, res);
619
      }
620
    }
621
66
  }
622

            
623
753
  bool hasHttpRules() const { return !http_rules_.empty(); }
624

            
625
  std::string name_;
626
  DownstreamTLSContextPtr server_context_;
627
  UpstreamTLSContextPtr client_context_;
628
  bool has_headermatches_{false};
629
  bool deny_;
630
  uint32_t precedence_;
631
  uint32_t proxy_id_;
632
  absl::btree_set<uint32_t> remotes_;
633

            
634
  std::vector<SniPattern> allowed_snis_;          // All SNIs allowed if empty.
635
  std::vector<HttpNetworkPolicyRule> http_rules_; // Allowed if empty, but remote is checked first.
636
  std::string l7_proto_{};
637
  std::vector<L7NetworkPolicyRule> l7_allow_rules_;
638
  std::vector<L7NetworkPolicyRule> l7_deny_rules_;
639
};
640
using PortNetworkPolicyRuleConstSharedPtr = std::shared_ptr<const PortNetworkPolicyRule>;
641

            
642
class PortNetworkPolicyRules : public Logger::Loggable<Logger::Id::config> {
643
public:
644
366
  PortNetworkPolicyRules() = default;
645

            
646
1192
  ~PortNetworkPolicyRules() {
647
1192
    if (!Thread::MainThread::isMainOrTestThread()) {
648
      IS_ENVOY_BUG("PortNetworkPolicyRules: Destructor executing in a worker thread, while "
649
                   "only main thread should destruct xDS resources");
650
    }
651
1192
  }
652

            
653
  void append(const NetworkPolicyMapImpl& parent,
654
59
              const Protobuf::RepeatedPtrField<cilium::PortNetworkPolicyRule>& rules) {
655
59
    for (const auto& it : rules) {
656
59
      rules_.emplace_back(std::make_shared<PortNetworkPolicyRule>(parent, it));
657
59
      if (rules_.back()->has_headermatches_) {
658
        can_short_circuit_ = false;
659
      }
660
59
    }
661
59
  }
662

            
663
  void prepend(const NetworkPolicyMapImpl& parent,
664
298
               const Protobuf::RepeatedPtrField<cilium::PortNetworkPolicyRule>& rules) {
665
425
    for (const auto& it : rules) {
666
425
      rules_.emplace(rules_.begin(), std::make_shared<PortNetworkPolicyRule>(parent, it));
667
425
      if (rules_.front()->has_headermatches_) {
668
78
        can_short_circuit_ = false;
669
78
      }
670
425
    }
671
298
  }
672

            
673
  // sort by descending precedence
674
350
  void sort() {
675
350
    std::sort(rules_.begin(), rules_.end(),
676
350
              [](const PortNetworkPolicyRuleConstSharedPtr& a,
677
350
                 const PortNetworkPolicyRuleConstSharedPtr& b) {
678
145
                return (a->precedence_ > b->precedence_) ||
679
145
                       (a->precedence_ == b->precedence_ && (a->deny_ && !b->deny_));
680
145
              });
681
350
  }
682

            
683
  bool empty() const { return rules_.empty(); }
684

            
685
  enum class RuleVerdict {
686
    None,
687
    Intermediate,
688
    Final,
689
  };
690

            
691
728
  template <typename F> void forEachRule(F&& func) const {
692
728
    uint32_t precedence = 0;
693
728
    bool have_verdict = false;
694

            
695
1045
    for (const auto& rule : rules_) {
696
      // lower precedence rules are skipped if there is a verdict
697
1045
      if (rule->precedence_ < precedence && have_verdict) {
698
        break;
699
      }
700
1045
      precedence = rule->precedence_;
701

            
702
1045
      RuleVerdict v = func(*rule);
703

            
704
1045
      if (v == RuleVerdict::Final) {
705
321
        break;
706
321
      }
707
724
      if (v == RuleVerdict::Intermediate) {
708
76
        have_verdict = true;
709
76
      }
710
724
    }
711
728
  }
712

            
713
  bool allowed(uint32_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers,
714
115
               Cilium::AccessLog::Entry& log_entry, bool& denied) const {
715
    // Empty set matches any payload from anyone
716
115
    if (rules_.empty()) {
717
      return true;
718
    }
719

            
720
115
    bool allowed = false;
721
194
    forEachRule([&](const auto& rule) {
722
194
      if (rule.allowed(proxy_id, remote_id, headers, log_entry, denied)) {
723
69
        allowed = true;
724
        // Short-circuit on the first match if no rules have HeaderMatches
725
69
        if (can_short_circuit_) {
726
40
          return RuleVerdict::Final;
727
40
        }
728
29
        return RuleVerdict::Intermediate;
729
69
      }
730
125
      return denied ? RuleVerdict::Final : RuleVerdict::None;
731
194
    });
732

            
733
115
    ENVOY_LOG(trace,
734
115
              "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, headers: {}): "
735
115
              "{}",
736
115
              proxy_id, remote_id, headers, allowed && !denied ? "ALLOWED" : "DENIED");
737
115
    return allowed && !denied;
738
115
  }
739

            
740
336
  bool allowed(uint32_t proxy_id, uint32_t remote_id, absl::string_view sni, bool& denied) const {
741
    // Empty set matches any payload from anyone
742
336
    if (rules_.empty()) {
743
12
      return true;
744
12
    }
745

            
746
324
    bool allowed = false;
747
410
    forEachRule([&](const auto& rule) {
748
410
      if (rule.allowed(proxy_id, remote_id, sni, denied)) {
749
249
        allowed = true;
750
        // Short-circuit on the first match if no rules have HeaderMatches
751
249
        if (can_short_circuit_) {
752
202
          return RuleVerdict::Final;
753
202
        }
754
47
        return RuleVerdict::Intermediate;
755
249
      }
756
161
      return denied ? RuleVerdict::Final : RuleVerdict::None;
757
410
    });
758

            
759
324
    ENVOY_LOG(trace,
760
324
              "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, sni: {}): "
761
324
              "{}",
762
324
              proxy_id, remote_id, sni, allowed && !denied ? "ALLOWED" : "DENIED");
763
324
    return allowed && !denied;
764
336
  }
765

            
766
239
  bool useProxylib(uint32_t proxy_id, uint32_t remote_id, std::string& l7_proto) const {
767
239
    bool denied = false;
768
239
    bool use_proxylib = false;
769
379
    forEachRule([&](const auto& rule) {
770
379
      if (rule.useProxylib(proxy_id, remote_id, l7_proto, denied)) {
771
36
        use_proxylib = true;
772
36
        return RuleVerdict::Final;
773
36
      }
774
343
      return denied ? RuleVerdict::Final : RuleVerdict::None;
775
379
    });
776
239
    return use_proxylib && !denied;
777
239
  }
778

            
779
  bool allowed(uint32_t proxy_id, uint32_t remote_id,
780
               const envoy::config::core::v3::Metadata& metadata, bool& denied) const {
781
    // Empty set matches any payload from anyone
782
    if (rules_.empty()) {
783
      return true;
784
    }
785

            
786
    bool allowed = false;
787
    forEachRule([&](const auto& rule) {
788
      if (rule.allowed(proxy_id, remote_id, metadata, denied)) {
789
        allowed = true;
790
        // Short-circuit on the first match if no rules have HeaderMatches
791
        if (can_short_circuit_) {
792
          return RuleVerdict::Final;
793
        }
794
        return RuleVerdict::Intermediate;
795
      }
796
      return denied ? RuleVerdict::Final : RuleVerdict::None;
797
    });
798

            
799
    ENVOY_LOG(trace,
800
              "Cilium L7 PortNetworkPolicyRules(proxy_id: {}, remote_id: {}, metadata: {}): "
801
              "{}",
802
              proxy_id, remote_id, metadata.DebugString(),
803
              allowed && !denied ? "ALLOWED" : "DENIED");
804
    return allowed && !denied;
805
  }
806

            
807
  Ssl::ContextSharedPtr getServerTlsContext(uint32_t proxy_id, uint32_t remote_id,
808
                                            absl::string_view sni,
809
                                            const Ssl::ContextConfig** config,
810
25
                                            bool& raw_socket_allowed) const {
811
25
    bool denied = false;
812
25
    Ssl::ContextSharedPtr tls_ctx = nullptr;
813
33
    forEachRule([&](const auto& rule) {
814
33
      Ssl::ContextSharedPtr server_context =
815
33
          rule.getServerTlsContext(proxy_id, remote_id, sni, config, raw_socket_allowed, denied);
816
33
      if (server_context) {
817
12
        tls_ctx = server_context;
818
12
        return RuleVerdict::Final;
819
12
      }
820
21
      return denied ? RuleVerdict::Final : RuleVerdict::None;
821
33
    });
822
25
    return denied ? nullptr : tls_ctx;
823
25
  }
824

            
825
  Ssl::ContextSharedPtr getClientTlsContext(uint32_t proxy_id, uint32_t remote_id,
826
                                            absl::string_view sni,
827
                                            const Ssl::ContextConfig** config,
828
25
                                            bool& raw_socket_allowed) const {
829
25
    bool denied = false;
830
25
    Ssl::ContextSharedPtr tls_ctx = nullptr;
831
29
    forEachRule([&](const auto& rule) {
832
29
      Ssl::ContextSharedPtr client_context =
833
29
          rule.getClientTlsContext(proxy_id, remote_id, sni, config, raw_socket_allowed, denied);
834
29
      if (client_context) {
835
15
        tls_ctx = client_context;
836
15
        return RuleVerdict::Final;
837
15
      }
838
14
      return denied ? RuleVerdict::Final : RuleVerdict::None;
839
29
    });
840
25
    return denied ? nullptr : tls_ctx;
841
25
  }
842

            
843
49
  void toString(int indent, std::string& res) const {
844
49
    res.append(indent - 2, ' ').append("- rules:\n");
845
66
    for (auto& rule : rules_) {
846
66
      rule->toString(indent + 2, res);
847
66
    }
848
49
    if (!can_short_circuit_) {
849
      res.append(indent, ' ').append(fmt::format("can_short_circuit: false\n"));
850
    }
851
49
  }
852

            
853
715
  bool hasHttpRules() const {
854
753
    for (const auto& rule : rules_) {
855
753
      if (rule->hasHttpRules()) {
856
328
        return true;
857
328
      }
858
753
    }
859
387
    return false;
860
715
  }
861

            
862
  // ordered set of rules as a sorted vector
863
  std::vector<PortNetworkPolicyRuleConstSharedPtr> rules_; // Allowed if empty.
864
  bool can_short_circuit_{true};
865
};
866

            
867
// end port is zero on lookup!
868
PortPolicy::PortPolicy(const PolicyMap& map, uint16_t port)
869
807
    : map_(map), wildcard_rules_([&]() {
870
807
        const auto it = map_.find({0, 0});
871
807
        return it != map_.cend() ? &it->second : nullptr;
872
807
      }()),
873
807
      port_rules_([&]() {
874
807
        const auto it = map_.find({port, port});
875
807
        return it != map_.cend() ? &it->second : nullptr;
876
807
      }()),
877
807
      has_http_rules_((port_rules_ && port_rules_->hasHttpRules()) ||
878
807
                      (wildcard_rules_ && wildcard_rules_->hasHttpRules())) {}
879

            
880
// forRange is used for policy lookups, so it will need to check both port-specific and
881
// wildcard-port rules, as either of them could contain rules that must be evaluated (i.e., deny
882
// or header match rules with side effects).
883
bool PortPolicy::forRange(
884
531
    std::function<bool(const PortNetworkPolicyRules&, bool& denied)> allowed) const {
885
531
  bool allow = false;
886
531
  bool denied = false;
887
531
  if (port_rules_) {
888
432
    if (allowed(*port_rules_, denied)) {
889
313
      allow = true;
890
313
    }
891
432
  }
892
  // Wildcard port can deny a specific remote, so need to check for it too.
893
531
  if (!(allow && wildcard_rules_ && wildcard_rules_->can_short_circuit_)) {
894
531
    if (wildcard_rules_ && allowed(*wildcard_rules_, denied)) {
895
17
      allow = true;
896
17
    }
897
531
  }
898
531
  return allow && !denied;
899
531
}
900

            
901
// forFirstRange is used for proxylib parser and TLS context selection.
902
//
903
// rules for the specific ports are checked first, and within there singe-port ranges are placed in
904
// the front, while actual ranges are placed in the back. This results in the following precedence
905
// order for both proxylib parser and TLS context selection:
906
//
907
// 1. single port rules (e.g., port 80)
908
// 2. port ranges (e.g., ports 80-90)
909
// 3. Wildcard port rules
910
//
911
301
bool PortPolicy::forFirstRange(std::function<bool(const PortNetworkPolicyRules&)> f) const {
912
301
  if (port_rules_ && f(*port_rules_)) {
913
63
    return true;
914
63
  }
915
  // Check the wildcard port entry
916
238
  if (wildcard_rules_ && f(*wildcard_rules_)) {
917
    return true;
918
  }
919
238
  return false;
920
238
}
921

            
922
239
bool PortPolicy::useProxylib(uint32_t proxy_id, uint32_t remote_id, std::string& l7_proto) const {
923
239
  return forFirstRange([&](const PortNetworkPolicyRules& rules) -> bool {
924
239
    return rules.useProxylib(proxy_id, remote_id, l7_proto);
925
239
  });
926
239
}
927

            
928
bool PortPolicy::allowed(uint32_t proxy_id, uint32_t remote_id,
929
                         Envoy::Http::RequestHeaderMap& headers,
930
115
                         Cilium::AccessLog::Entry& log_entry) const {
931
  // Network layer policy has already been enforced. If there are no http rules, then there is
932
  // nothing to do.
933
115
  if (!has_http_rules_) {
934
    return true;
935
  }
936
115
  return forRange([&](const PortNetworkPolicyRules& rules, bool& denied) -> bool {
937
115
    return rules.allowed(proxy_id, remote_id, headers, log_entry, denied);
938
115
  });
939
115
}
940

            
941
416
bool PortPolicy::allowed(uint32_t proxy_id, uint32_t remote_id, absl::string_view sni) const {
942
416
  return forRange([&](const PortNetworkPolicyRules& rules, bool& denied) -> bool {
943
336
    return rules.allowed(proxy_id, remote_id, sni, denied);
944
336
  });
945
416
}
946

            
947
bool PortPolicy::allowed(uint32_t proxy_id, uint32_t remote_id,
948
                         const envoy::config::core::v3::Metadata& metadata) const {
949
  return forRange([&](const PortNetworkPolicyRules& rules, bool& denied) -> bool {
950
    return rules.allowed(proxy_id, remote_id, metadata, denied);
951
  });
952
}
953

            
954
Ssl::ContextSharedPtr PortPolicy::getServerTlsContext(uint32_t proxy_id, uint32_t remote_id,
955
                                                      absl::string_view sni,
956
                                                      const Ssl::ContextConfig** config,
957
32
                                                      bool& raw_socket_allowed) const {
958
32
  Ssl::ContextSharedPtr ret;
959
32
  forFirstRange([&](const PortNetworkPolicyRules& rules) -> bool {
960
25
    ret = rules.getServerTlsContext(proxy_id, remote_id, sni, config, raw_socket_allowed);
961
25
    return ret != nullptr;
962
25
  });
963
32
  return ret;
964
32
}
965

            
966
Ssl::ContextSharedPtr PortPolicy::getClientTlsContext(uint32_t proxy_id, uint32_t remote_id,
967
                                                      absl::string_view sni,
968
                                                      const Ssl::ContextConfig** config,
969
30
                                                      bool& raw_socket_allowed) const {
970
30
  Ssl::ContextSharedPtr ret;
971
30
  forFirstRange([&](const PortNetworkPolicyRules& rules) -> bool {
972
25
    ret = rules.getClientTlsContext(proxy_id, remote_id, sni, config, raw_socket_allowed);
973
25
    return ret != nullptr;
974
25
  });
975
30
  return ret;
976
30
}
977

            
978
// Ranges overlap when one is not completely below or above the other
979
365
bool inline rangesOverlap(const PortRange& a, const PortRange& b) {
980
  // !(a.second < b.first || a.first > b.second)
981
365
  return a.second >= b.first && a.first <= b.second;
982
365
}
983

            
984
class PortNetworkPolicy : public Logger::Loggable<Logger::Id::config> {
985
public:
986
  PortNetworkPolicy(const NetworkPolicyMapImpl& parent,
987
394
                    const Protobuf::RepeatedPtrField<cilium::PortNetworkPolicy>& rules) {
988
394
    for (const auto& rule : rules) {
989
      // Only TCP supported for HTTP
990
348
      if (rule.protocol() == envoy::config::core::v3::SocketAddress::TCP) {
991
        // Port may be zero, which matches any port.
992
348
        uint16_t port = rule.port();
993
        // End port may be zero, which means no range
994
348
        uint16_t end_port = rule.end_port();
995
348
        if (end_port < port) {
996
262
          if (end_port != 0) {
997
1
            throw EnvoyException(fmt::format(
998
1
                "PortNetworkPolicy: Invalid port range, end port is less than start port {}-{}",
999
1
                port, end_port));
1
          }
261
          end_port = port;
261
        }
347
        if (port == 0) {
35
          if (end_port > 0) {
2
            throw EnvoyException(fmt::format(
2
                "PortNetworkPolicy: Invalid port range including the wildcard zero port {}-{}",
2
                port, end_port));
2
          }
35
        }
345
        ENVOY_LOG(trace,
345
                  "Cilium L7 PortNetworkPolicy(): installing TCP policy for "
345
                  "port range {}-{}",
345
                  port, end_port);
345
        auto rule_range = std::make_pair(port, end_port);
345
        auto pair = rules_.emplace(rule_range, PortNetworkPolicyRules{});
345
        auto it = pair.first;
345
        if (!pair.second) {
17
          ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicy(): new entry [{}-{}] overlaps with [{}-{}]",
17
                    port, end_port, it->first.first, it->first.second);
          // Explicitly manage overlapping ranges by breaking them up.
          //
          // rules_ has the breaked up, non-overlapping ranges in order.
          //
          // While iterating through all the existing overlapping ranges:
          // - add new ranges when there are any gaps in the existing ranges (for the new range)
          // - split existing ranges if they are only partially overlapping with the new range
          // Then, as a separate step:
          // - add new rules to all of the (disjoint, ordered) ranges covered by the new range
          // The new range can overlap with multiple entries in the map, current iterator can
          // point to any one of them. Find the first entry the new entry overlaps with.
17
          auto last_overlap = it;
17
          while (it != rules_.begin()) {
1
            last_overlap = it;
1
            it--;
1
            if (!rangesOverlap(it->first, rule_range)) {
1
              break;
1
            }
1
          }
17
          it = last_overlap; // Move back up to the frontmost overlapping entry
          // absl::btree_map manipulation operations invalidate iterators, so we keep the range
          // (the map key) of the first overlapping entry in 'start_range' to be able to locate
          // the first range that needs the new rules after all the overlaps have been resolved.
          // 'start_key' is updated as needed below.
17
          auto start_range = it->first;
          // split the current entry due to partial overlap in the beginning?
          // For example, if the current entry is 80-8080 and we are adding 4040-9999,
          // the current entry should be split to two ranges 80-4039 and 4040-8080,
          // both of which should retain their current rules, but new rules should only be
          // added to the 2nd half covered by the new range 4040-9999.
17
          if (port > start_range.first) {
6
            RELEASE_ASSERT(port <= start_range.second, "non-overlapping range");
6
            auto rules = it->second;
6
            PortRange range1 = start_range;
6
            range1.second = port - 1;
6
            PortRange range2 = start_range;
6
            range2.first = port;
6
            rules_.erase(it);
6
            auto pr1 = rules_.insert({range1, rules});
6
            RELEASE_ASSERT(pr1.second, "Range split failed 1 begin");
6
            auto pr2 = rules_.insert({range2, rules});
6
            RELEASE_ASSERT(pr2.second, "Range split failed 2 begin");
6
            it = pr2.first;          // update current iterator
6
            start_range = it->first; // update the start range
6
          }
          // scan the range of the new rule, filling the gaps with new (partial) ranges
29
          for (; it != rules_.end() && port <= end_port && end_port >= it->first.first; it++) {
18
            auto range = it->first;
            // create a new entry below the current one?
18
            if (port < range.first) {
4
              auto new_range = std::make_pair(port, std::min(end_port, uint16_t(range.first - 1)));
4
              auto new_pair = rules_.emplace(new_range, PortNetworkPolicyRules{});
4
              RELEASE_ASSERT(new_pair.second,
4
                             "duplicate entry when explicitly adding a new range!");
              // update the start range if a new start entry was added, which can happen only at the
              // beginning of this loop when port is still at the beginning of the rule range being
              // added.
4
              if (port == rule_range.first) {
3
                start_range = new_range;
3
              }
              // absl::btree_map insertion invalidates iterators, have to update.
4
              it = ++new_pair.first; // one past the new entry
4
              if (end_port < range.first) {
                // done
                break;
              }
              // covered upto range.first-1, continue from range.first
4
              port = range.first;
4
            }
18
            RELEASE_ASSERT(port == range.first, "port should match the start of the current range");
            // split the current range into two due to partial overlap in the end?
18
            if (end_port < range.second) {
6
              auto rules = it->second;
6
              PortRange range1 = it->first;
6
              range1.second = end_port;
6
              PortRange range2 = it->first;
6
              range2.first = end_port + 1;
6
              rules_.erase(it);
6
              auto pr1 = rules_.insert({range1, rules});
6
              RELEASE_ASSERT(pr1.second, "Range split failed 1 end");
6
              auto pr2 = rules_.insert({range2, rules});
6
              RELEASE_ASSERT(pr2.second, "Range split failed 2 end");
6
              it = pr2.first;      // one past the end of range
6
              port = end_port + 1; // one past the end
6
              break;
12
            } else {
              // current entry completely covered by the new range, skip to the next
12
              port = range.second + 1;
12
            }
18
          }
          // create a new entry covering the end?
17
          if (port <= end_port) {
7
            auto new_range = std::make_pair(port, end_port);
7
            auto new_pair = rules_.emplace(new_range, PortNetworkPolicyRules{});
7
            RELEASE_ASSERT(new_pair.second,
7
                           "duplicate entry at end when explicitly adding a new range!");
7
            it = ++new_pair.first;
7
          }
          // make 'it' point to the first overlapping entry for the rule updates to follow
17
          it = rules_.find(start_range);
17
          RELEASE_ASSERT(it != rules_.end(), "first overlapping entry not found");
17
        }
        // Add rules to all the overlapping entries
345
        bool singular = rule_range.first == rule_range.second;
702
        for (; it != rules_.end() && rangesOverlap(it->first, rule_range); it++) {
357
          auto range = it->first;
357
          auto& rules = it->second;
357
          ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicy(): Adding rules for [{}-{}] to [{}-{}]",
357
                    rule_range.first, rule_range.second, range.first, range.second);
357
          if (singular) {
            // Exact port rules go to the front of the list.
            // This gives precedence for trivial range rules for proxylib parser
            // and TLS context selection.
298
            rules.prepend(parent, rule.rules());
310
          } else {
            // Rules with a non-trivial range go to the back of the list
59
            rules.append(parent, rule.rules());
59
          }
357
        }
345
      } else {
        ENVOY_LOG(trace, "Cilium L7 PortNetworkPolicy(): NOT installing non-TCP policy");
      }
348
    }
    // sort rules into descending precedence
397
    for (auto& pair : rules_) {
350
      pair.second.sort();
350
    }
391
  }
804
  const PortPolicy findPortPolicy(uint16_t port) const { return PortPolicy(rules_, port); }
36
  void toString(int indent, std::string& res) const {
36
    if (rules_.empty()) {
14
      res.append(indent, ' ').append("rules: []\n");
22
    } else {
22
      res.append(indent, ' ').append("rules:\n");
49
      for (const auto& entry : rules_) {
49
        res.append(indent + 2, ' ')
49
            .append(fmt::format("[{}-{}]:\n", entry.first.first, entry.first.second));
49
        entry.second.toString(indent + 4, res);
49
      }
22
    }
36
  }
  PolicyMap rules_;
  bool has_http_rules_ = false;
};
// Construction is single-threaded, but all other use is from multiple worker threads using const
// methods.
class PolicyInstanceImpl : public PolicyInstance {
public:
  PolicyInstanceImpl(const NetworkPolicyMapImpl& parent, uint64_t hash,
                     const cilium::NetworkPolicy& proto)
198
      : conntrack_map_name_(proto.conntrack_map_name()), endpoint_id_(proto.endpoint_id()),
198
        hash_(hash), policy_proto_(proto), endpoint_ips_(proto), parent_(parent),
198
        ingress_(parent, policy_proto_.ingress_per_port_policies()),
198
        egress_(parent, policy_proto_.egress_per_port_policies()) {}
  bool allowed(bool ingress, uint32_t proxy_id, uint32_t remote_id, uint16_t port,
               Envoy::Http::RequestHeaderMap& headers,
129
               Cilium::AccessLog::Entry& log_entry) const override {
129
    const auto port_policy = findPortPolicy(ingress, port);
129
    if (!port_policy.hasHttpRules()) {
86
      return true;
86
    }
43
    return port_policy.allowed(proxy_id, remote_id, headers, log_entry);
129
  }
  bool allowed(bool ingress, uint32_t proxy_id, uint32_t remote_id, absl::string_view sni,
288
               uint16_t port) const override {
288
    const auto port_policy = findPortPolicy(ingress, port);
288
    return port_policy.allowed(proxy_id, remote_id, sni);
288
  }
804
  const PortPolicy findPortPolicy(bool ingress, uint16_t port) const override {
804
    return ingress ? ingress_.findPortPolicy(port) : egress_.findPortPolicy(port);
804
  }
  bool useProxylib(bool ingress, uint32_t proxy_id, uint32_t remote_id, uint16_t port,
124
                   std::string& l7_proto) const override {
124
    const auto port_policy = findPortPolicy(ingress, port);
124
    return port_policy.useProxylib(proxy_id, remote_id, l7_proto);
124
  }
  const std::string& conntrackName() const override { return conntrack_map_name_; }
4
  uint32_t getEndpointID() const override { return endpoint_id_; }
1
  const IpAddressPair& getEndpointIPs() const override { return endpoint_ips_; }
18
  std::string string() const override {
18
    std::string res;
18
    res.append("ingress:\n");
18
    ingress_.toString(2, res);
18
    res.append("egress:\n");
18
    egress_.toString(2, res);
18
    return res;
18
  }
  void tlsWrapperMissingPolicyInc() const override { parent_.tlsWrapperMissingPolicyInc(); }
public:
  std::string conntrack_map_name_;
  uint32_t endpoint_id_;
  uint64_t hash_;
  const cilium::NetworkPolicy policy_proto_;
  const IpAddressPair endpoint_ips_;
private:
  const NetworkPolicyMapImpl& parent_;
  const PortNetworkPolicy ingress_;
  const PortNetworkPolicy egress_;
};
// Common base constructor
// This is used directly for testing with a file-based subscription
NetworkPolicyMap::NetworkPolicyMap(Server::Configuration::FactoryContext& context)
148
    : context_(context.serverFactoryContext()) {
148
  impl_ = std::make_unique<NetworkPolicyMapImpl>(context);
148
  if (context_.admin().has_value()) {
148
    ENVOY_LOG(debug, "Registering NetworkPolicies to config tracker");
148
    config_tracker_entry_ = context_.admin()->getConfigTracker().add(
148
        "networkpolicies", [this](const Matchers::StringMatcher& name_matcher) {
          return dumpNetworkPolicyConfigs(name_matcher);
        });
148
    RELEASE_ASSERT(config_tracker_entry_, "");
148
  }
148
}
// This is used in production
NetworkPolicyMap::NetworkPolicyMap(Server::Configuration::FactoryContext& context,
                                   Cilium::CtMapSharedPtr& ct)
    : NetworkPolicyMap(context) {
  getImpl().setConntrackMap(ct);
  getImpl().startSubscription();
}
148
NetworkPolicyMap::~NetworkPolicyMap() {
148
  ENVOY_LOG(debug,
148
            "Cilium L7 NetworkPolicyMap: posting NetworkPolicyMapImpl deletion to main thread");
  // Policy map destruction happens when the last listener with the Cilium bpf_metadata listener
  // filter has drained out and is finally removed, and last connection of the old listener is
  // closed. This does not happen if new listener(s) with references to policy map are created in
  // the meanwhile.
  //
  // Destruction of the NetworkPolicyMapImpl must be made from the main thread to ensure integrity
  // of SDS subscription management. Since this can be called from a worker thread of the last
  // connection we must post the destruction to the main thread dispatcher.
  //
  // Move the NetworkPolicyMapImpl to the lambda capture so that it goes out of scope and gets
  // deleted in the main thread.
148
  context_.mainThreadDispatcher().post([impl = std::move(impl_)]() {});
148
}
NetworkPolicyMapImpl::NetworkPolicyMapImpl(Server::Configuration::FactoryContext& context)
148
    : context_(context.serverFactoryContext()), map_ptr_(nullptr),
148
      npds_stats_scope_(context_.serverScope().createScope("cilium.npds.")),
148
      policy_stats_scope_(context_.serverScope().createScope("cilium.policy.")),
148
      init_target_(fmt::format("Cilium Network Policy subscription start"),
148
                   [this]() {
129
                     subscription_->start({});
                     // Allow listener init to continue before network policy updates are received
129
                     init_target_.ready();
129
                   }),
      transport_factory_context_(
148
          std::make_shared<Server::Configuration::TransportSocketFactoryContextImpl>(
148
              context_, *npds_stats_scope_,
148
              context_.messageValidationContext().dynamicValidationVisitor())),
148
      stats_{ALL_CILIUM_POLICY_STATS(POOL_COUNTER(*policy_stats_scope_),
148
                                     POOL_HISTOGRAM(*policy_stats_scope_))} {
  // Use listener init manager for subscription initialization
148
  context.initManager().add(init_target_);
  // Allocate an initial policy map so that the map pointer is never a nullptr
148
  store(new RawPolicyMap());
148
  ENVOY_LOG(trace, "NetworkPolicyMapImpl({}) created.", instance_id_);
148
}
// NetworkPolicyMapImpl destructor must only be called from the main thread.
148
NetworkPolicyMapImpl::~NetworkPolicyMapImpl() {
148
  ENVOY_LOG(debug, "Cilium L7 NetworkPolicyMapImpl({}): NetworkPolicyMap is deleted NOW!",
148
            instance_id_);
148
  delete load();
148
}
void NetworkPolicyMapImpl::startSubscription() {
  subscription_ = subscribe("type.googleapis.com/cilium.NetworkPolicy", context_.localInfo(),
                            context_.clusterManager(), context_.mainThreadDispatcher(),
                            context_.api().randomGenerator(), *npds_stats_scope_, *this,
                            std::make_shared<NetworkPolicyDecoder>());
}
void NetworkPolicyMapImpl::tlsWrapperMissingPolicyInc() const {
  stats_.tls_wrapper_missing_policy_.inc();
}
173
bool NetworkPolicyMapImpl::isNewStream() {
173
  auto sub = dynamic_cast<Config::GrpcSubscriptionImpl*>(subscription_.get());
173
  if (!sub) {
173
    ENVOY_LOG(error, "Cilium NetworkPolicyMapImpl: Cannot get GrpcSubscriptionImpl");
173
    return false;
173
  }
  auto mux = dynamic_cast<GrpcMuxImpl*>(sub->grpcMux().get());
  if (!mux) {
    ENVOY_LOG(error, "Cilium NetworkPolicyMapImpl: Cannot get GrpcMuxImpl");
    return false;
  }
  return mux->isNewStream();
}
// removeInitManager must be called at the end of each policy update
173
void NetworkPolicyMapImpl::removeInitManager() {
  // Remove the local init manager from the transport factory context
173
#ifdef __clang__
173
#pragma clang diagnostic push
173
#pragma clang diagnostic ignored "-Wnull-dereference"
173
#endif
173
  transport_factory_context_->setInitManager(*static_cast<Init::Manager*>(nullptr));
173
#ifdef __clang__
173
#pragma clang diagnostic pop
173
#endif
173
}
// onConfigUpdate parses the new network policy resources, allocates a new policy map and atomically
// swaps it in place of the old policy map. Throws if any of the 'resources' can not be
// parsed. Otherwise an OK status is returned without pausing NPDS gRPC stream, causing a new
// request (ACK) to be sent immediately, without waiting SDS secrets to be loaded.
absl::Status NetworkPolicyMapImpl::onConfigUpdate(
    const std::vector<Envoy::Config::DecodedResourceRef>& resources,
173
    const std::string& version_info) {
173
  ENVOY_LOG(debug, "NetworkPolicyMapImpl::onConfigUpdate({}), {} resources, version: {}",
173
            instance_id_, resources.size(), version_info);
173
  stats_.updates_total_.inc();
  // Reopen IPcache for every new stream. Cilium agent re-creates IP cache on restart,
  // and that is also when the old stream terminates and a new one is created.
  // New security identities (e.g., for FQDN policies) only get inserted to the new IP cache,
  // so open it before the workers get a chance to enforce policy on the new IDs.
173
  if (isNewStream()) {
    ENVOY_LOG(info, "New NetworkPolicy stream");
    // Get ipcache singleton only if it was successfully created previously
    IpCacheSharedPtr ipcache = IpCache::getIpCache(context_);
    if (ipcache != nullptr) {
      ENVOY_LOG(info, "Reopening ipcache on new stream");
      ipcache->open();
    }
  }
173
  std::string version_name = fmt::format("NetworkPolicyMap version {}", version_info);
173
  Init::ManagerImpl version_init_manager(version_name);
  // Set the init manager to use via the transport factory context
  // Must be set before the new network policy is parsed, as the parsed
  // SDS secrets will use this!
173
  transport_factory_context_->setInitManager(version_init_manager);
173
  absl::flat_hash_set<std::string> ctmaps_to_be_closed;
173
  const auto* old_map = load();
173
  {
173
    absl::flat_hash_set<std::string> ctmaps_to_keep;
173
    auto new_map = new RawPolicyMap();
173
    try {
206
      for (const auto& resource : resources) {
198
        const auto& config = dynamic_cast<const cilium::NetworkPolicy&>(resource.get().resource());
198
        ENVOY_LOG(debug,
198
                  "Received Network Policy for endpoint {}, endpoint_ip {} in onConfigUpdate() "
198
                  "version {}",
198
                  config.endpoint_id(), config.endpoint_ips()[0], version_info);
198
        if (config.endpoint_ips().empty()) {
          throw EnvoyException("Network Policy has no endpoint ips");
        }
198
        ctmaps_to_keep.insert(config.conntrack_map_name());
        // First find the old config to figure out if an update is needed.
198
        const uint64_t new_hash = MessageUtil::hash(config);
198
        auto it = old_map->find(config.endpoint_ips()[0]);
198
        if (it != old_map->cend()) {
21
          const auto& old_policy = it->second;
21
          if (old_policy && old_policy->hash_ == new_hash &&
21
              Protobuf::util::MessageDifferencer::Equals(old_policy->policy_proto_, config)) {
            ENVOY_LOG(trace, "New policy is equal to old one, not updating.");
            for (const auto& endpoint_ip : config.endpoint_ips()) {
              ENVOY_LOG(trace, "Cilium keeping network policy for endpoint {}", endpoint_ip);
              new_map->emplace(endpoint_ip, old_policy);
            }
            continue;
          }
21
        }
        // May throw
198
        auto new_policy = std::make_shared<const PolicyInstanceImpl>(*this, new_hash, config);
247
        for (const auto& endpoint_ip : config.endpoint_ips()) {
244
          ENVOY_LOG(trace, "Cilium updating network policy for endpoint {}", endpoint_ip);
          // new_map is not exception safe, new_policy must be computed separately!
244
          new_map->emplace(endpoint_ip, new_policy);
244
        }
198
      }
173
    } catch (const EnvoyException& e) {
3
      ENVOY_LOG(warn, "NetworkPolicy update for version {} failed: {}", version_info, e.what());
3
      stats_.updates_rejected_.inc();
3
      removeInitManager();
3
      throw; // re-throw
3
    }
170
    removeInitManager();
    // Initialize SDS secrets. We do not wait for the completion.
170
    version_init_manager.initialize(Init::WatcherImpl(version_name, []() {}));
    // Add old ctmaps to be closed
    //
    // NOTE: Support for local CT maps was removed in Cilium 1.17. This clean-up code can be
    // simplified by always keeping the global map open when Cilium 1.17 is the oldest supported
    // version.
170
    for (auto& pair : *old_map) {
      // insert conntrack map names we don't want to keep
21
      auto& ct_map_name = pair.second->conntrack_map_name_;
21
      if (ctmaps_to_keep.find(ct_map_name) == ctmaps_to_keep.end()) {
        ctmaps_to_be_closed.insert(ct_map_name);
      }
21
    }
    // Swap the new map in, new_map goes out of scope right after to eliminate accidental
    // modification.
170
    old_map = exchange(new_map);
170
  }
  // Delete the old map once all worker threads have entered their event queues, as this
  // is proof that they no longer refer to the old map.
170
  runAfterAllThreads([ctmap = ctmap_, ctmaps_to_be_closed, old_map]() {
    // Clean-up in the main thread after all threads have scheduled
170
    if (ctmap) {
      ctmap->closeMaps(ctmaps_to_be_closed);
    }
170
    delete old_map;
170
  });
170
  return absl::OkStatus();
173
}
void NetworkPolicyMapImpl::onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason,
1
                                                const EnvoyException*) {
  // We need to allow server startup to continue, even if we have a bad
  // config.
1
  ENVOY_LOG(debug, "Network Policy Update failed, keeping existing policy.");
1
}
170
void NetworkPolicyMapImpl::runAfterAllThreads(std::function<void()> cb) const {
  // We can guarantee the callback 'cb' runs in the main thread after all worker threads have
  // entered their event loop, and thus relinquished all state, such as policy lookup results that
  // were stored in their call stack, by posting and empty function to their event queues and
  // waiting until all of them have returned, as managed by 'runOnAllWorkerThreads'.
  //
  // For now we rely on the implementation dependent fact that the reference returned by
  // context_.threadLocal() actually is a ThreadLocal::Instance reference, where
  // runOnAllWorkerThreads() is exposed. Without this cast we'd need to use a dummy thread local
  // variable that would take a thread local slot for no other purpose than to avoid this type cast.
170
  dynamic_cast<ThreadLocal::Instance&>(context_.threadLocal()).runOnAllWorkerThreads([]() {}, cb);
170
}
ProtobufTypes::MessagePtr
NetworkPolicyMap::dumpNetworkPolicyConfigs(const Matchers::StringMatcher& name_matcher) {
  ENVOY_LOG(debug, "Writing NetworkPolicies to NetworkPoliciesConfigDump");
  std::vector<uint64_t> policy_endpoint_ids;
  auto config_dump = std::make_unique<cilium::NetworkPoliciesConfigDump>();
  for (const auto& item : *getImpl().load()) {
    // filter duplicates (policies are stored per endpoint ip)
    if (std::find(policy_endpoint_ids.begin(), policy_endpoint_ids.end(),
                  item.second->policy_proto_.endpoint_id()) != policy_endpoint_ids.end()) {
      continue;
    }
    if (!name_matcher.match(item.first)) {
      continue;
    }
    config_dump->mutable_networkpolicies()->Add()->CopyFrom(item.second->policy_proto_);
    policy_endpoint_ids.emplace_back(item.second->policy_proto_.endpoint_id());
  }
  return config_dump;
}
// Allow-all Egress policy
class AllowAllEgressPolicyInstanceImpl : public PolicyInstance {
public:
10
  AllowAllEgressPolicyInstanceImpl() {
10
    empty_map_.emplace(std::make_pair(uint16_t(1), uint16_t(1)), PortNetworkPolicyRules{});
10
  }
  bool allowed(bool ingress, uint32_t, uint32_t, uint16_t, Envoy::Http::RequestHeaderMap&,
               Cilium::AccessLog::Entry&) const override {
    return ingress ? false : true;
  }
  bool allowed(bool ingress, uint32_t, uint32_t, absl::string_view, uint16_t) const override {
    return ingress ? false : true;
  }
  const PortPolicy findPortPolicy(bool ingress, uint16_t) const override {
    return ingress ? PortPolicy(empty_map_, 0) : PortPolicy(empty_map_, 1);
  }
1
  bool useProxylib(bool, uint32_t, uint32_t, uint16_t, std::string&) const override {
1
    return false;
1
  }
  const std::string& conntrackName() const override { return empty_string; }
2
  uint32_t getEndpointID() const override { return 0; }
  const IpAddressPair& getEndpointIPs() const override { return empty_ips; }
  std::string string() const override { return "AllowAllEgressPolicyInstanceImpl"; }
  void tlsWrapperMissingPolicyInc() const override {}
private:
  PolicyMap empty_map_;
  static const std::string empty_string;
  static const IpAddressPair empty_ips;
};
const std::string AllowAllEgressPolicyInstanceImpl::empty_string = "";
const IpAddressPair AllowAllEgressPolicyInstanceImpl::empty_ips{};
AllowAllEgressPolicyInstanceImpl NetworkPolicyMap::AllowAllEgressPolicy;
PolicyInstance& NetworkPolicyMap::getAllowAllEgressPolicy() { return AllowAllEgressPolicy; }
// Deny-all policy
class DenyAllPolicyInstanceImpl : public PolicyInstance {
public:
10
  DenyAllPolicyInstanceImpl() = default;
  bool allowed(bool, uint32_t, uint32_t, uint16_t, Envoy::Http::RequestHeaderMap&,
               Cilium::AccessLog::Entry&) const override {
    return false;
  }
11
  bool allowed(bool, uint32_t, uint32_t, absl::string_view, uint16_t) const override {
11
    return false;
11
  }
3
  const PortPolicy findPortPolicy(bool, uint16_t) const override {
3
    return PortPolicy(empty_map_, 0);
3
  }
3
  bool useProxylib(bool, uint32_t, uint32_t, uint16_t, std::string&) const override {
3
    return false;
3
  }
  const std::string& conntrackName() const override { return empty_string; }
2
  uint32_t getEndpointID() const override { return 0; }
  const IpAddressPair& getEndpointIPs() const override { return empty_ips; }
2
  std::string string() const override { return "DenyAllPolicyInstanceImpl"; }
  void tlsWrapperMissingPolicyInc() const override {}
private:
  PolicyMap empty_map_;
  static const std::string empty_string;
  static const IpAddressPair empty_ips;
};
const std::string DenyAllPolicyInstanceImpl::empty_string = "";
const IpAddressPair DenyAllPolicyInstanceImpl::empty_ips{};
DenyAllPolicyInstanceImpl NetworkPolicyMap::DenyAllPolicy;
PolicyInstance& NetworkPolicyMap::getDenyAllPolicy() { return DenyAllPolicy; }
const PolicyInstance*
703
NetworkPolicyMapImpl::getPolicyInstanceImpl(const std::string& endpoint_ip) const {
703
  const auto* map = load();
703
  auto it = map->find(endpoint_ip);
703
  if (it != map->end()) {
678
    return it->second.get();
678
  }
25
  return nullptr;
703
}
// getPolicyInstance return a const reference to a policy in the policy map for the given
// 'endpoint_ip'. If there is no policy for the given IP, a default policy is returned,
// controlled by the 'default_allow_egress' argument as follows:
//
// 'false' - a deny all policy is returned,
// 'true' -  a deny all ingress / allow all egress is returned.
//
// Returning a default deny policy makes the caller report a "policy deny" rather than "internal
// server error" if no policy is found. This mirrors what bpf datapath does if no policy entry is
// found in the bpf policy map. The default deny for ingress with default allow for egress is needed
// for Cilium Ingress when there is no egress policy enforcement for the Ingress traffic.
const PolicyInstance& NetworkPolicyMap::getPolicyInstance(const std::string& endpoint_ip,
676
                                                          bool default_allow_egress) const {
676
  const auto* policy = getImpl().getPolicyInstanceImpl(endpoint_ip);
676
  return policy != nullptr      ? *policy
676
         : default_allow_egress ? *static_cast<PolicyInstance*>(&AllowAllEgressPolicy)
19
                                : *static_cast<PolicyInstance*>(&DenyAllPolicy);
676
}
} // namespace Cilium
} // namespace Envoy