1
#include "tests/bpf_metadata.h"
2

            
3
#include <cstdint>
4
#include <memory>
5
#include <string>
6
#include <utility>
7
#include <vector>
8

            
9
#include "envoy/common/exception.h"
10
#include "envoy/config/core/v3/config_source.pb.h"
11
#include "envoy/config/subscription.h"
12
#include "envoy/network/address.h"
13
#include "envoy/network/filter.h"
14
#include "envoy/network/listen_socket.h"
15
#include "envoy/registry/registry.h"
16
#include "envoy/server/factory_context.h"
17
#include "envoy/server/filter_config.h"
18

            
19
#include "source/common/common/logger.h"
20
#include "source/common/config/utility.h"
21
#include "source/common/protobuf/message_validator_impl.h"
22
#include "source/common/protobuf/protobuf.h" // IWYU pragma: keep
23
#include "source/common/protobuf/utility.h"
24
#include "source/extensions/config_subscription/filesystem/filesystem_subscription_impl.h"
25

            
26
#include "test/test_common/environment.h"
27

            
28
#include "absl/strings/string_view.h"
29
#include "absl/types/optional.h"
30
#include "cilium/api/bpf_metadata.pb.h"
31
#include "cilium/bpf_metadata.h"
32
#include "cilium/host_map.h"
33
#include "cilium/network_policy.h"
34
#include "cilium/secret_watcher.h"
35
#include "fmt/printf.h"
36
#include "tests/bpf_metadata.pb.h"
37
#include "tests/bpf_metadata.pb.validate.h" // IWYU pragma: keep
38

            
39
namespace Envoy {
40

            
41
std::string host_map_config = "version_info: \"0\"";
42
std::shared_ptr<const Cilium::PolicyHostMap> hostmap{nullptr}; // Keep reference to singleton
43

            
44
Network::Address::InstanceConstSharedPtr original_dst_address;
45
std::shared_ptr<const Cilium::NetworkPolicyMap> npmap{nullptr}; // Keep reference to singleton
46

            
47
std::string policy_config = "version_info: \"0\"";
48
std::string policy_path = "";
49

            
50
std::vector<std::pair<std::string, std::string>> sds_configs{};
51

            
52
namespace {
53

            
54
std::shared_ptr<const Cilium::PolicyHostMap>
55
129
createHostMap(const std::string& config, Server::Configuration::ListenerFactoryContext& context) {
56
129
  return context.serverFactoryContext().singletonManager().getTyped<const Cilium::PolicyHostMap>(
57
129
      "cilium_host_map_singleton", [&config, &context] {
58
129
        std::string path = TestEnvironment::writeStringToFileForTest("host_map.yaml", config);
59
129
        ENVOY_LOG_MISC(debug, "Loading Cilium Host Map from file \'{}\' instead of using gRPC",
60
129
                       path);
61

            
62
129
        THROW_IF_NOT_OK(Envoy::Config::Utility::checkFilesystemSubscriptionBackingPath(
63
129
            path, context.serverFactoryContext().api()));
64
129
        Envoy::Config::SubscriptionStats stats =
65
129
            Envoy::Config::Utility::generateStats(context.scope());
66
129
        auto map = std::make_shared<Cilium::PolicyHostMap>(context.serverFactoryContext());
67
129
        auto subscription = std::make_unique<Envoy::Config::FilesystemSubscriptionImpl>(
68
129
            context.serverFactoryContext().mainThreadDispatcher(),
69
129
            Envoy::Config::makePathConfigSource(path), *map,
70
129
            std::make_shared<Cilium::PolicyHostDecoder>(), stats,
71
129
            ProtobufMessage::getNullValidationVisitor(), context.serverFactoryContext().api());
72
129
        map->startSubscription(std::move(subscription));
73
129
        return map;
74
129
      });
75
129
}
76

            
77
std::shared_ptr<const Cilium::NetworkPolicyMap>
78
createPolicyMap(const std::string& config,
79
                const std::vector<std::pair<std::string, std::string>>& secret_configs,
80
129
                Server::Configuration::FactoryContext& context) {
81
129
  return context.serverFactoryContext().singletonManager().getTyped<const Cilium::NetworkPolicyMap>(
82
129
      "cilium_network_policy_singleton", [&config, &secret_configs, &context] {
83
129
        if (!secret_configs.empty()) {
84
73
          for (const auto& sds_pair : secret_configs) {
85
73
            auto& name = sds_pair.first;
86
73
            auto& sds_config = sds_pair.second;
87
73
            std::string sds_path = TestEnvironment::writeStringToFileForTest(
88
73
                fmt::sprintf("secret-%s.yaml", name), sds_config);
89
73
            THROW_IF_NOT_OK(Envoy::Config::Utility::checkFilesystemSubscriptionBackingPath(
90
73
                sds_path, context.serverFactoryContext().api()));
91
73
          }
92
65
          Cilium::setSDSConfigFunc(
93
96
              [](const std::string& name) -> envoy::config::core::v3::ConfigSource {
94
91
                auto file_config = envoy::config::core::v3::ConfigSource();
95
                /* initial_fetch_timeout left at default 15 seconds. */
96
91
                file_config.set_resource_api_version(envoy::config::core::v3::ApiVersion::V3);
97
91
                auto sds_path =
98
91
                    TestEnvironment::temporaryPath(fmt::sprintf("secret-%s.yaml", name));
99
91
                *file_config.mutable_path_config_source() =
100
91
                    Envoy::Config::makePathConfigSource(sds_path);
101
91
                return file_config;
102
91
              });
103
65
        }
104
        // File subscription.
105
129
        policy_path = TestEnvironment::writeStringToFileForTest("network_policy.yaml", config);
106
129
        ENVOY_LOG_MISC(debug,
107
129
                       "Loading Cilium Network Policy from file \'{}\' instead "
108
129
                       "of using gRPC",
109
129
                       policy_path);
110
129
        THROW_IF_NOT_OK(Envoy::Config::Utility::checkFilesystemSubscriptionBackingPath(
111
129
            policy_path, context.serverFactoryContext().api()));
112
129
        Envoy::Config::SubscriptionStats stats =
113
129
            Envoy::Config::Utility::generateStats(context.scope());
114
129
        auto map = std::make_shared<Cilium::NetworkPolicyMap>(context);
115
129
        auto subscription = std::make_unique<Envoy::Config::FilesystemSubscriptionImpl>(
116
129
            context.serverFactoryContext().mainThreadDispatcher(),
117
129
            Envoy::Config::makePathConfigSource(policy_path), map->getImpl(),
118
129
            std::make_shared<Cilium::NetworkPolicyDecoder>(), stats,
119
129
            ProtobufMessage::getNullValidationVisitor(), context.serverFactoryContext().api());
120
129
        map->startSubscription(std::move(subscription));
121
129
        return map;
122
129
      });
123
129
}
124

            
125
} // namespace
126

            
127
129
void initTestMaps(Server::Configuration::ListenerFactoryContext& context) {
128
  // Create the file-based policy map before the filter is created, so that the
129
  // singleton is set before the gRPC subscription is attempted.
130
129
  hostmap = createHostMap(host_map_config, context);
131
  // Create the file-based policy map before the filter is created, so that the
132
  // singleton is set before the gRPC subscription is attempted.
133
129
  npmap = createPolicyMap(policy_config, sds_configs, context);
134
129
}
135

            
136
namespace Cilium {
137
namespace BpfMetadata {
138

            
139
namespace {
140
113
::cilium::BpfMetadata getTestConfig(const ::cilium::TestBpfMetadata& config) {
141
113
  ::cilium::BpfMetadata test_config;
142
113
  test_config.set_is_ingress(config.is_ingress());
143
113
  test_config.set_is_l7lb(config.is_l7lb());
144
113
  return test_config;
145
113
}
146
} // namespace
147

            
148
TestConfig::TestConfig(const ::cilium::TestBpfMetadata& config,
149
                       Server::Configuration::ListenerFactoryContext& context)
150
113
    : Config(getTestConfig(config), context) {
151
113
  hosts_ = hostmap;
152
113
  npmap_ = npmap;
153
113
}
154

            
155
113
TestConfig::~TestConfig() {
156
113
  hostmap.reset();
157
113
  npmap.reset();
158
113
}
159

            
160
absl::optional<Cilium::BpfMetadata::SocketMetadata>
161
119
TestConfig::extractSocketMetadata(Network::ConnectionSocket& socket) {
162
  // TLS filter chain matches this, make namespace part of this (e.g.,
163
  // "default")?
164
119
  socket.setDetectedTransportProtocol("cilium:default");
165

            
166
  // This must be the full domain name
167
119
  socket.setRequestedServerName("localhost");
168

            
169
119
  std::string pod_ip;
170
119
  uint64_t source_identity;
171
119
  uint64_t destination_identity;
172
119
  if (is_ingress_) {
173
73
    source_identity = 1;
174
73
    destination_identity = 173;
175
73
    pod_ip = original_dst_address->ip()->addressAsString();
176
73
    ENVOY_LOG_MISC(debug, "INGRESS POD_IP: {}", pod_ip);
177
108
  } else {
178
46
    source_identity = 173;
179
46
    auto ip = socket.connectionInfoProvider().localAddress()->ip();
180
46
    destination_identity = hosts_->resolve(ip);
181
46
    pod_ip = ip->addressAsString();
182
46
    ENVOY_LOG_MISC(debug, "EGRESS POD_IP: {}", pod_ip);
183
46
  }
184
119
  const auto& policy = getPolicy(pod_ip);
185
119
  auto port = original_dst_address->ip()->port();
186

            
187
  // Set metadata for policy based listener filter chain matching
188
  // Note: tls_inspector may overwrite this value, if it executes after us!
189
119
  std::string l7proto;
190
119
  policy.useProxylib(is_ingress_, proxy_id_, is_ingress_ ? source_identity : destination_identity,
191
119
                     port, l7proto);
192

            
193
119
  return {Cilium::BpfMetadata::SocketMetadata(
194
119
      0, 0, source_identity, is_ingress_, is_l7lb_, port, std::move(pod_ip), "", nullptr, nullptr,
195
119
      nullptr, original_dst_address, shared_from_this(), 0, std::move(l7proto), "")};
196
119
}
197

            
198
} // namespace BpfMetadata
199
} // namespace Cilium
200

            
201
namespace Server {
202
namespace Configuration {
203

            
204
class TestBpfMetadataConfigFactory : public NamedListenerFilterConfigFactory {
205
public:
206
  // NamedListenerFilterConfigFactory
207
  Network::ListenerFilterFactoryCb createListenerFilterFactoryFromProto(
208
      const Protobuf::Message& proto_config,
209
      const Network::ListenerFilterMatcherSharedPtr& listener_filter_matcher,
210
113
      ListenerFactoryContext& context) override {
211

            
212
113
    initTestMaps(context);
213

            
214
113
    auto config = std::make_shared<Cilium::BpfMetadata::TestConfig>(
215
113
        MessageUtil::downcastAndValidate<const ::cilium::TestBpfMetadata&>(
216
113
            proto_config, context.messageValidationVisitor()),
217
113
        context);
218

            
219
113
    return [listener_filter_matcher,
220
119
            config](Network::ListenerFilterManager& filter_manager) mutable -> void {
221
119
      filter_manager.addAcceptFilter(listener_filter_matcher,
222
119
                                     std::make_unique<Cilium::BpfMetadata::Instance>(config));
223
119
    };
224
113
  }
225

            
226
121
  ProtobufTypes::MessagePtr createEmptyConfigProto() override {
227
121
    return std::make_unique<::cilium::TestBpfMetadata>();
228
121
  }
229

            
230
122
  std::string name() const override { return "test_bpf_metadata"; }
231
};
232

            
233
/**
234
 * Static registration for the bpf metadata filter. @see RegisterFactory.
235
 */
236
REGISTER_FACTORY(TestBpfMetadataConfigFactory, NamedListenerFilterConfigFactory);
237

            
238
} // namespace Configuration
239
} // namespace Server
240

            
241
} // namespace Envoy