1
#pragma once
2

            
3
#include <fmt/format.h>
4

            
5
#include <atomic>
6
#include <cstdint>
7
#include <functional>
8
#include <list>
9
#include <memory>
10
#include <string>
11
#include <utility>
12
#include <vector>
13

            
14
#include "envoy/common/exception.h"
15
#include "envoy/common/matchers.h"
16
#include "envoy/common/pure.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/network/address.h"
21
#include "envoy/protobuf/message_validator.h"
22
#include "envoy/server/config_tracker.h"
23
#include "envoy/server/factory_context.h"
24
#include "envoy/server/transport_socket_config.h"
25
#include "envoy/singleton/instance.h"
26
#include "envoy/ssl/context.h"
27
#include "envoy/ssl/context_config.h"
28
#include "envoy/stats/scope.h"
29
#include "envoy/stats/stats_macros.h" // IWYU pragma: keep
30

            
31
#include "source/common/common/assert.h"
32
#include "source/common/common/logger.h"
33
#include "source/common/common/macros.h"
34
#include "source/common/common/thread.h"
35
#include "source/common/init/target_impl.h"
36
#include "source/common/protobuf/message_validator_impl.h"
37
#include "source/common/protobuf/protobuf.h"
38
#include "source/common/protobuf/utility.h"
39
#include "source/server/transport_socket_config_impl.h"
40

            
41
#include "absl/container/btree_map.h"
42
#include "absl/container/flat_hash_map.h"
43
#include "absl/status/status.h"
44
#include "absl/strings/ascii.h"
45
#include "absl/strings/string_view.h"
46
#include "cilium/accesslog.h"
47
#include "cilium/api/npds.pb.h"
48
#include "cilium/api/npds.pb.validate.h" // IWYU pragma: keep
49
#include "cilium/conntrack.h"
50

            
51
namespace Envoy {
52
namespace Cilium {
53

            
54
// PortRangeCompare is used for as std::less replacement for port range keys.
55
//
56
// All port ranges in the map have non-overlapping keys, which allows total ordering needed for
57
// ordered map containers. When inserting new ranges, any range overlap will be flagged as a
58
// "duplicate" entry, as overlapping keys are considered equal (as neither is strictly less than the
59
// other given this comparison predicate).
60
// On lookups we'll set both ends of the port range to the same port number, which will find the one
61
// range that it overlaps with, if one exists.
62
using PortRange = std::pair<uint16_t, uint16_t>;
63
struct PortRangeCompare {
64
3701
  bool operator()(const PortRange& a, const PortRange& b) const {
65
    // return true if range 'a.first - a.second' is below range 'b.first - b.second'.
66
3701
    return a.second < b.first;
67
3701
  }
68
};
69

            
70
class PortNetworkPolicyRules;
71

            
72
// PolicyMap is keyed by port ranges, and contains a list of PortNetworkPolicyRules's applicable
73
// to this range. A list is needed as rules may come from multiple sources (e.g., resulting from
74
// use of named ports and numbered ports in Cilium Network Policy at the same time).
75
using PolicyMap = absl::btree_map<PortRange, PortNetworkPolicyRules, PortRangeCompare>;
76

            
77
// PortPolicy holds a reference to a set of rules in a policy map that apply to the given port.
78
// Methods then iterate through the set to determine if policy allows or denies. This is needed to
79
// support multiple rules on the same port, like when named ports are used, or when deny policies
80
// may be present.
81
class PortPolicy : public Logger::Loggable<Logger::Id::config> {
82
protected:
83
  friend class PortNetworkPolicy;
84
  friend class DenyAllPolicyInstanceImpl;
85
  friend class AllowAllEgressPolicyInstanceImpl;
86
  PortPolicy(const PolicyMap& map, uint16_t port);
87

            
88
public:
89
  // If hasHttpRules() returns false, then HTTP policy enforcement can be skipped,
90
  // given that Network layer policy has already been enforced.
91
205
  bool hasHttpRules() const { return has_http_rules_; }
92

            
93
  // useProxylib returns true if a proxylib parser should be used.
94
  // 'l7_proto' is set to the parser name in that case.
95
  bool useProxylib(uint32_t proxy_id, uint32_t remote_id, std::string& l7_proto) const;
96
  // HTTP-layer policy check. 'headers' and 'log_entry' may be manipulated by the policy.
97
  bool allowed(uint32_t proxy_id, uint32_t remote_id, Envoy::Http::RequestHeaderMap& headers,
98
               Cilium::AccessLog::Entry& log_entry) const;
99
  // Network-layer policy check
100
  bool allowed(uint32_t proxy_id, uint32_t remote_id, absl::string_view sni) const;
101
  // Envoy filter metadata policy check
102
  bool allowed(uint32_t proxy_id, uint32_t remote_id,
103
               const envoy::config::core::v3::Metadata& metadata) const;
104
  // getServerTlsContext returns the server TLS context, if any. If a non-null pointer is returned,
105
  // then also the config pointer '*config' is set.
106
  // If '*config' is nullptr and 'raw_socket_allowed' is 'true' on return then the policy
107
  // allows the connection without TLS and a raw socket should be used.
108
  Ssl::ContextSharedPtr getServerTlsContext(uint32_t proxy_id, uint32_t remote_id,
109
                                            absl::string_view sni,
110
                                            const Ssl::ContextConfig** config,
111
                                            bool& raw_socket_allowed) const;
112
  // getClientTlsContext returns the client TLS context, if any. If a non-null pointer is returned,
113
  // then also the config pointer '*config' is set.
114
  // If '*config' is nullptr and 'raw_socket_allowed' is 'true' on return then the policy
115
  // allows the connection without TLS and a raw socket should be used.
116
  Ssl::ContextSharedPtr getClientTlsContext(uint32_t proxy_id, uint32_t remote_id,
117
                                            absl::string_view sni,
118
                                            const Ssl::ContextConfig** config,
119
                                            bool& raw_socket_allowed) const;
120

            
121
private:
122
  bool forRange(std::function<bool(const PortNetworkPolicyRules&, bool& denied)> allowed) const;
123
  bool forFirstRange(std::function<bool(const PortNetworkPolicyRules&)> f) const;
124

            
125
  const PolicyMap& map_;
126
  // using raw pointers by design:
127
  // - pointer to distinguish between no rules and empty rules
128
  // - not using shared pointer to not allow a worker thread to hold the last reference to policy
129
  //   rule(s), as they must be destructed from the main thread only.
130
  // - lifetime on policy updates is managed explicitly by posting a lambda to all worker threads
131
  //   before the old rules are deleted; worker thread drop references to policy rules before
132
  //   returning to the event loop, so after the posted lambda executes it is safe to delete the old
133
  //   rules.
134
  const PortNetworkPolicyRules* wildcard_rules_;
135
  const PortNetworkPolicyRules* port_rules_;
136
  const bool has_http_rules_;
137
};
138

            
139
class IpAddressPair {
140
public:
141
1
  IpAddressPair() = default;
142
  IpAddressPair(Network::Address::InstanceConstSharedPtr& ipv4,
143
                Network::Address::InstanceConstSharedPtr& ipv6)
144
10
      : ipv4_(ipv4), ipv6_(ipv6) {};
145
  IpAddressPair(const cilium::NetworkPolicy& proto);
146

            
147
  Network::Address::InstanceConstSharedPtr ipv4_{};
148
  Network::Address::InstanceConstSharedPtr ipv6_{};
149
};
150

            
151
class PolicyInstance {
152
public:
153
198
  virtual ~PolicyInstance() {
154
198
    if (!Thread::MainThread::isMainOrTestThread()) {
155
      IS_ENVOY_BUG("PolicyInstance: Destructor executing in a worker thread, while "
156
                   "only main thread should destruct xDS resources");
157
    }
158
198
  };
159

            
160
  virtual bool allowed(bool ingress, uint32_t proxy_id, uint32_t remote_id, uint16_t port,
161
                       Envoy::Http::RequestHeaderMap& headers,
162
                       Cilium::AccessLog::Entry& log_entry) const PURE;
163

            
164
  virtual bool allowed(bool ingress, uint32_t proxy_id, uint32_t remote_id, absl::string_view sni,
165
                       uint16_t port) const PURE;
166

            
167
  virtual const PortPolicy findPortPolicy(bool ingress, uint16_t port) const PURE;
168

            
169
  // Returns true if the policy specifies l7 protocol for the connection, and
170
  // returns the l7 protocol string in 'l7_proto'
171
  virtual bool useProxylib(bool ingress, uint32_t proxy_id, uint32_t remote_id, uint16_t port,
172
                           std::string& l7_proto) const PURE;
173

            
174
  virtual const std::string& conntrackName() const PURE;
175

            
176
  virtual uint32_t getEndpointID() const PURE;
177

            
178
  virtual const IpAddressPair& getEndpointIPs() const PURE;
179

            
180
  virtual std::string string() const PURE;
181

            
182
  virtual void tlsWrapperMissingPolicyInc() const PURE;
183
};
184
using PolicyInstanceConstSharedPtr = std::shared_ptr<const PolicyInstance>;
185

            
186
class PolicyInstanceImpl;
187

            
188
class NetworkPolicyDecoder : public Envoy::Config::OpaqueResourceDecoder {
189
public:
190
168
  NetworkPolicyDecoder() : validation_visitor_(ProtobufMessage::getNullValidationVisitor()) {}
191

            
192
  // Config::OpaqueResourceDecoder
193
198
  ProtobufTypes::MessagePtr decodeResource(const ProtobufWkt::Any& resource) override {
194
198
    auto typed_message = std::make_unique<cilium::NetworkPolicy>();
195
    // If the Any is a synthetic empty message (e.g. because the resource field
196
    // was not set in Resource, this might be empty, so we shouldn't decode.
197
198
    if (!resource.type_url().empty()) {
198
198
      MessageUtil::anyConvertAndValidate<cilium::NetworkPolicy>(resource, *typed_message,
199
198
                                                                validation_visitor_);
200
198
    }
201
198
    return typed_message;
202
198
  }
203

            
204
198
  std::string resourceName(const Protobuf::Message& resource) override {
205
198
    return fmt::format("{}", dynamic_cast<const cilium::NetworkPolicy&>(resource).endpoint_id());
206
198
  }
207

            
208
private:
209
  ProtobufMessage::ValidationVisitor& validation_visitor_;
210
};
211

            
212
/**
213
 * All Cilium L7 filter stats. @see stats_macros.h
214
 */
215
// clang-format off
216
#define ALL_CILIUM_POLICY_STATS(COUNTER, HISTOGRAM)	\
217
148
  COUNTER(updates_total)				\
218
148
  COUNTER(updates_rejected)				\
219
148
  COUNTER(tls_wrapper_missing_policy)
220
// clang-format on
221

            
222
/**
223
 * Struct definition for all policy stats. @see stats_macros.h
224
 */
225
struct PolicyStats {
226
  ALL_CILIUM_POLICY_STATS(GENERATE_COUNTER_STRUCT, GENERATE_HISTOGRAM_STRUCT)
227
};
228

            
229
using RawPolicyMap = absl::flat_hash_map<std::string, std::shared_ptr<const PolicyInstanceImpl>>;
230

            
231
class NetworkPolicyMapImpl : public Envoy::Config::SubscriptionCallbacks,
232
                             public Logger::Loggable<Logger::Id::config> {
233
public:
234
  NetworkPolicyMapImpl(Server::Configuration::FactoryContext& context);
235
  ~NetworkPolicyMapImpl() override;
236

            
237
  void startSubscription();
238

            
239
  // This is used for testing with a file-based subscription
240
129
  void startSubscription(std::unique_ptr<Envoy::Config::Subscription>&& subscription) {
241
129
    subscription_ = std::move(subscription);
242
129
  }
243

            
244
  // run the given function after all the threads have scheduled
245
  void runAfterAllThreads(std::function<void()>) const;
246

            
247
  // Config::SubscriptionCallbacks
248
  absl::Status onConfigUpdate(const std::vector<Envoy::Config::DecodedResourceRef>& resources,
249
                              const std::string& version_info) override;
250
  absl::Status onConfigUpdate(const std::vector<Envoy::Config::DecodedResourceRef>& added_resources,
251
                              const Protobuf::RepeatedPtrField<std::string>& removed_resources,
252
                              const std::string& system_version_info) override {
253
    // NOT IMPLEMENTED YET.
254
    UNREFERENCED_PARAMETER(added_resources);
255
    UNREFERENCED_PARAMETER(removed_resources);
256
    UNREFERENCED_PARAMETER(system_version_info);
257
    return absl::OkStatus();
258
  }
259
  void onConfigUpdateFailed(Envoy::Config::ConfigUpdateFailureReason,
260
                            const EnvoyException* e) override;
261

            
262
1170
  Server::Configuration::TransportSocketFactoryContext& transportFactoryContext() const {
263
1170
    return *transport_factory_context_;
264
1170
  }
265

            
266
  void tlsWrapperMissingPolicyInc() const;
267

            
268
private:
269
  // Helpers for atomic swap of the policy map pointer.
270
  //
271
  // store() is only used for the initialization of the map during construction.
272
  // exchange() is used to atomically swap in a new map, the old map pointer is returned.
273
  // Once a map is stored or swapped in to the atomic pointer by the main thread, it may be "loaded"
274
  // from the atomic pointer by any thread. This is why the load returns a const pointer.
275
  //
276
  // For the loaded pointer to be safe to use, we must use acquire/release memory ordering:
277
  // - when a pointer stored or swapped in, 'std::memory_order_release' informs the compiler to make
278
  //   sure it is not reordering any write operations into the map to happen after the pointer is
279
  //   written, and emits CPU instructions to also make the CPU out-of-order-execution logic to not
280
  //   reorder any write operations to happen after the pointer itself is written. This guarantees
281
  //   that the map is not modified after the point when the worker threads can observe the new
282
  //   pointer value, i.e., the map is actaully immutable (const) from that point forward.
283
  // - when the pointer is read (by a worker thread) 'std::memory_order_acquire' in the load
284
  //   operation informs the compiler to emit CPU instructions to make the CPU
285
  //   out-of-order-execution logic to not reorder any reads from the new map to happen before the
286
  //   pointer itself is read, so that no values from the map are read before the map was "released"
287
  //   by the store or exchange operation.
288
  //
289
  // Typically it is easier to think about the release part of the acquire/release semantics, as at
290
  // the point of the store or exchange operation the compiler and the CPU know the location of the
291
  // map in memory before and after the pointer is stored, so that without
292
  // 'std::memory_order_release' there is an understandable risk of such write after release
293
  // happening. On the acquire side it seems less likely that the compiler or the CPU could know the
294
  // new map pointer value in advance and even try to reorder any read operations to happen before
295
  // the pointer is actually read. But consider the typical case where the pointer value is actually
296
  // not changing between consecutice load operations. The compiler or the CPU could speculate that
297
  // to be the case and read some values from the old memory location. 'std::memory_order_acquire'
298
  // tells the compiler (which then "tells" the CPU) that this can not be done, and all reads must
299
  // actually happen after the pointer value is loaded, be it a new one or the same as before.
300
  //
301
1024
  const RawPolicyMap* load() const { return map_ptr_.load(std::memory_order_acquire); }
302
148
  void store(const RawPolicyMap* map) { map_ptr_.store(map, std::memory_order_release); }
303
170
  const RawPolicyMap* exchange(const RawPolicyMap* map) {
304
170
    return map_ptr_.exchange(map, std::memory_order_release);
305
170
  }
306

            
307
  const PolicyInstance* getPolicyInstanceImpl(const std::string& endpoint_policy_name) const;
308

            
309
  void removeInitManager();
310

            
311
  bool isNewStream();
312

            
313
  static uint64_t instance_id_;
314

            
315
  Server::Configuration::ServerFactoryContext& context_;
316
  std::atomic<const RawPolicyMap*> map_ptr_;
317
  Stats::ScopeSharedPtr npds_stats_scope_;
318
  Stats::ScopeSharedPtr policy_stats_scope_;
319

            
320
  // init target which starts gRPC subscription
321
  Init::TargetImpl init_target_;
322
  std::shared_ptr<Server::Configuration::TransportSocketFactoryContextImpl>
323
      transport_factory_context_;
324

            
325
  std::unique_ptr<Envoy::Config::Subscription> subscription_;
326

            
327
protected:
328
  friend class NetworkPolicyMap;
329
  friend class CiliumNetworkPolicyTest;
330

            
331
  void setConntrackMap(Cilium::CtMapSharedPtr& ct) { ctmap_ = ct; }
332

            
333
  Cilium::CtMapSharedPtr ctmap_;
334
  PolicyStats stats_;
335
};
336

            
337
class DenyAllPolicyInstanceImpl;
338
class AllowAllEgressPolicyInstanceImpl;
339

            
340
class NetworkPolicyMap : public Singleton::Instance, public Logger::Loggable<Logger::Id::config> {
341
public:
342
  NetworkPolicyMap(Server::Configuration::FactoryContext& context);
343
  NetworkPolicyMap(Server::Configuration::FactoryContext& context, Cilium::CtMapSharedPtr& ct);
344
  ~NetworkPolicyMap() override;
345

            
346
  // This is used for testing with a file-based subscription
347
129
  void startSubscription(std::unique_ptr<Envoy::Config::Subscription>&& subscription) {
348
129
    getImpl().startSubscription(std::move(subscription));
349
129
  }
350

            
351
  const PolicyInstance& getPolicyInstance(const std::string& endpoint_policy_name,
352
                                          bool allow_egress) const;
353

            
354
  static DenyAllPolicyInstanceImpl DenyAllPolicy;
355
  static PolicyInstance& getDenyAllPolicy();
356
  static AllowAllEgressPolicyInstanceImpl AllowAllEgressPolicy;
357
  static PolicyInstance& getAllowAllEgressPolicy();
358

            
359
27
  bool exists(const std::string& endpoint_policy_name) const {
360
27
    return getImpl().getPolicyInstanceImpl(endpoint_policy_name) != nullptr;
361
27
  }
362

            
363
1002
  NetworkPolicyMapImpl& getImpl() const { return *impl_; }
364

            
365
private:
366
  Server::Configuration::ServerFactoryContext& context_;
367
  std::unique_ptr<NetworkPolicyMapImpl> impl_;
368

            
369
  ProtobufTypes::MessagePtr dumpNetworkPolicyConfigs(const Matchers::StringMatcher& name_matcher);
370
  Server::ConfigTracker::EntryOwnerPtr config_tracker_entry_;
371
};
372
using NetworkPolicyMapSharedPtr = std::shared_ptr<const NetworkPolicyMap>;
373

            
374
struct SniPattern {
375
  std::string pattern;
376

            
377
12
  explicit SniPattern(const std::string& p) : pattern(absl::AsciiStrToLower(p)) {}
378

            
379
76
  bool matches(const absl::string_view sni) const {
380
76
    if (pattern.empty() || sni.empty()) {
381
8
      return false;
382
8
    }
383
68
    auto const lower_sni = absl::AsciiStrToLower(sni);
384
    // Perform lower case exact match if there is no wildcard prefix
385
68
    if (!pattern.starts_with("*")) {
386
46
      return pattern == lower_sni;
387
46
    }
388

            
389
    // Pattern is "**.<domain>"
390
22
    if (pattern.starts_with("**.")) {
391
5
      return lower_sni.ends_with(pattern.substr(2));
392
5
    }
393

            
394
    // Pattern is "*.<domain>"
395
17
    if (pattern.starts_with("*.")) {
396
11
      auto const sub_pattern = pattern.substr(1);
397
11
      if (!lower_sni.ends_with(sub_pattern)) {
398
4
        return false;
399
4
      }
400
7
      auto const prefix = lower_sni.substr(0, sni.size() - sub_pattern.size());
401
      // Make sure that only and exactly one label is before the wildcard
402
7
      return !prefix.empty() && prefix.find_first_of('.') == std::string::npos;
403
11
    }
404

            
405
6
    return false;
406
17
  }
407

            
408
  void toString(std::string& res) const { res.append(fmt::format("\"{}\"", pattern)); }
409
};
410

            
411
} // namespace Cilium
412
} // namespace Envoy