1
// <gtest.h> TEST
2
#include <fmt/base.h>
3
#include <fmt/format.h>
4
#include <gtest/gtest-param-test.h>
5
#include <gtest/gtest.h>
6

            
7
#include <cstdint>
8
#include <memory>
9
#include <string>
10
#include <utility>
11
#include <vector>
12

            
13
#include "envoy/common/exception.h"
14
#include "envoy/network/address.h"
15
#include "envoy/service/discovery/v3/discovery.pb.h"
16

            
17
#include "source/common/common/logger.h"
18
#include "source/common/config/decoded_resource_impl.h"
19
#include "source/common/network/address_impl.h"
20
#include "source/common/network/utility.h"
21
#include "source/common/protobuf/message_validator_impl.h"
22
#include "source/common/protobuf/utility.h"
23
#include "source/common/thread_local/thread_local_impl.h"
24

            
25
#include "test/integration/http_integration.h"
26
#include "test/test_common/environment.h"
27
#include "test/test_common/utility.h"
28

            
29
#include "absl/strings/numbers.h"
30
#include "absl/time/clock.h"
31
#include "absl/time/time.h"
32
#include "absl/types/optional.h"
33
#include "cilium/api/accesslog.pb.h"
34
#include "cilium/host_map.h"
35
#include "cilium/secret_watcher.h"
36
#include "tests/bpf_metadata.h" // host_map_config
37
#include "tests/cilium_http_integration.h"
38

            
39
namespace Envoy {
40

            
41
// params: destination port number
42
const std::string BASIC_POLICY_fmt = R"EOF(version_info: "0"
43
resources:
44
- "@type": type.googleapis.com/cilium.NetworkPolicy
45
  endpoint_ips:
46
  - '{{ ntop_ip_loopback_address }}'
47
  endpoint_id: 3
48
  ingress_per_port_policies:
49
  - port: {0}
50
    rules:
51
    - remote_policies: [ 1 ]
52
      http_rules:
53
        http_rules:
54
        - headers:
55
          - name: ':path'
56
            exact_match: '/allowed'
57
        - headers:
58
          - name: ':path'
59
            safe_regex_match:
60
              google_re2: {{}}
61
              regex: '.*public$'
62
        - headers:
63
          - name: ':authority'
64
            exact_match: 'allowedHOST'
65
        - headers:
66
          - name: ':authority'
67
            safe_regex_match:
68
              google_re2: {{}}
69
              regex: '.*REGEX.*'
70
        - headers:
71
          - name: ':method'
72
            exact_match: 'PUT'
73
          - name: ':path'
74
            exact_match: '/public/opinions'
75
    - remote_policies: [ 2 ]
76
      http_rules:
77
        http_rules:
78
        - headers:
79
          - name: ':path'
80
            exact_match: '/only-2-allowed'
81
  egress_per_port_policies:
82
  - port: {0}
83
    rules:
84
    - remote_policies: [ 1 ]
85
      http_rules:
86
        http_rules:
87
        - headers:
88
          - name: ':path'
89
            exact_match: '/allowed'
90
        - headers:
91
          - name: ':path'
92
            safe_regex_match:
93
              google_re2: {{}}
94
              regex: '.*public$'
95
        - headers:
96
          - name: ':authority'
97
            exact_match: 'allowedHOST'
98
        - headers:
99
          - name: ':authority'
100
            safe_regex_match:
101
              google_re2: {{}}
102
              regex: '.*REGEX.*'
103
        - headers:
104
          - name: ':method'
105
            exact_match: 'PUT'
106
          - name: ':path'
107
            exact_match: '/public/opinions'
108
    - remote_policies: [ 2 ]
109
      http_rules:
110
        http_rules:
111
        - headers:
112
          - name: ':path'
113
            exact_match: '/only-2-allowed'
114
)EOF";
115

            
116
const std::string SECRET_TOKEN_CONFIG = R"EOF(version_info: "0"
117
resources:
118
- "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.Secret
119
  name: bearer-token
120
  generic_secret:
121
    secret:
122
      inline_string: "d4ef0f5011f163ac"
123
)EOF";
124

            
125
const std::string HEADER_ACTION_POLICY_fmt = R"EOF(version_info: "0"
126
resources:
127
- "@type": type.googleapis.com/cilium.NetworkPolicy
128
  endpoint_ips:
129
  - '{{ ntop_ip_loopback_address }}'
130
  endpoint_id: 3
131
  ingress_per_port_policies:
132
  - port: {0}
133
    rules:
134
    - remote_policies: [ 1 ]
135
      http_rules:
136
        http_rules:
137
        - headers:
138
          - name: ':path'
139
            exact_match: '/allowed'
140
          header_matches:
141
          - name: 'header42'
142
            match_action: FAIL_ON_MATCH
143
            mismatch_action: CONTINUE_ON_MISMATCH
144
          - name: 'bearer-token'
145
            value_sds_secret: 'bearer-token'
146
            mismatch_action: REPLACE_ON_MISMATCH
147
        - headers:
148
          - name: ':path'
149
            safe_regex_match:
150
              google_re2: {{}}
151
              regex: '.*public$'
152
          header_matches:
153
          - name: 'user-agent'
154
            value: 'CuRL'
155
            mismatch_action: DELETE_ON_MISMATCH
156
        - headers:
157
          - name: ':authority'
158
            exact_match: 'allowedHOST'
159
          header_matches:
160
          - name: 'header2'
161
            value: 'value2'
162
            mismatch_action: ADD_ON_MISMATCH
163
          - name: 'header42'
164
            match_action: DELETE_ON_MATCH
165
            mismatch_action: CONTINUE_ON_MISMATCH
166
        - headers:
167
          - name: ':authority'
168
            safe_regex_match:
169
              google_re2: {{}}
170
              regex: '.*REGEX.*'
171
          header_matches:
172
          - name: 'header42'
173
            value: '42'
174
            mismatch_action: DELETE_ON_MISMATCH
175
        - headers:
176
          - name: ':method'
177
            exact_match: 'PUT'
178
          - name: ':path'
179
            exact_match: '/public/opinions'
180
    - remote_policies: [ 2 ]
181
      http_rules:
182
        http_rules:
183
        - headers:
184
          - name: ':path'
185
            exact_match: '/only-2-allowed'
186
  egress_per_port_policies:
187
  - port: {0}
188
    rules:
189
    - remote_policies: [ 1 ]
190
      http_rules:
191
        http_rules:
192
        - headers:
193
          - name: ':path'
194
            exact_match: '/allowed'
195
        - headers:
196
          - name: ':path'
197
            safe_regex_match:
198
              google_re2: {{}}
199
              regex: '.*public$'
200
        - headers:
201
          - name: ':authority'
202
            exact_match: 'allowedHOST'
203
        - headers:
204
          - name: ':authority'
205
            safe_regex_match:
206
              google_re2: {{}}
207
              regex: '.*REGEX.*'
208
        - headers:
209
          - name: ':method'
210
            exact_match: 'PUT'
211
          - name: ':path'
212
            exact_match: '/public/opinions'
213
    - remote_policies: [ 2 ]
214
      http_rules:
215
        http_rules:
216
        - headers:
217
          - name: ':path'
218
            exact_match: '/only-2-allowed'
219
)EOF";
220

            
221
// params: is_ingress ("true", "false")
222
const std::string cilium_proxy_config_fmt = R"EOF(
223
admin:
224
  address:
225
    socket_address:
226
      address: 127.0.0.1
227
      port_value: 0
228
static_resources:
229
  clusters:
230
  - name: cluster1
231
    type: ORIGINAL_DST
232
    lb_policy: CLUSTER_PROVIDED
233
    connect_timeout:
234
      seconds: 1
235
  - name: xds-grpc-cilium
236
    connect_timeout:
237
      seconds: 5
238
    type: STATIC
239
    lb_policy: ROUND_ROBIN
240
    http2_protocol_options:
241
    load_assignment:
242
      cluster_name: xds-grpc-cilium
243
      endpoints:
244
      - lb_endpoints:
245
        - endpoint:
246
            address:
247
              pipe:
248
                path: /var/run/cilium/xds.sock
249
  listeners:
250
    name: http
251
    address:
252
      socket_address:
253
        address: 127.0.0.1
254
        port_value: 0
255
    listener_filters:
256
      name: test_bpf_metadata
257
      typed_config:
258
        "@type": type.googleapis.com/cilium.TestBpfMetadata
259
        is_ingress: {0}
260
    filter_chains:
261
      filters:
262
      - name: cilium.network
263
        typed_config:
264
          "@type": type.googleapis.com/cilium.NetworkFilter
265
          access_log_path: "{{ test_udsdir }}/access_log.sock"
266
      - name: envoy.http_connection_manager
267
        typed_config:
268
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
269
          stat_prefix: config_test
270
          codec_type: auto
271
          use_remote_address: true
272
          skip_xff_append: true
273
          http_filters:
274
          - name: test_l7policy
275
            typed_config:
276
              "@type": type.googleapis.com/cilium.L7Policy
277
              access_log_path: "{{ test_udsdir }}/access_log.sock"
278
          - name: envoy.filters.http.router
279
          route_config:
280
            name: policy_enabled
281
            virtual_hosts:
282
              name: integration
283
              domains: "*"
284
              routes:
285
              - route:
286
                  cluster: cluster1
287
                  max_grpc_timeout:
288
                    seconds: 0
289
                    nanos: 0
290
                match:
291
                  prefix: "/"
292
)EOF";
293

            
294
class CiliumIntegrationTest : public CiliumHttpIntegrationTest {
295
public:
296
  CiliumIntegrationTest()
297
22
      : CiliumHttpIntegrationTest(fmt::format(
298
22
            fmt::runtime(TestEnvironment::substitute(cilium_proxy_config_fmt, GetParam())),
299
22
            "true")) {}
300
11
  CiliumIntegrationTest(const std::string& config) : CiliumHttpIntegrationTest(config) {}
301

            
302
25
  std::string testPolicyFmt() override {
303
25
    return TestEnvironment::substitute(HEADER_ACTION_POLICY_fmt, GetParam());
304
25
  }
305

            
306
30
  std::vector<std::pair<std::string, std::string>> testSecrets() override {
307
30
    return std::vector<std::pair<std::string, std::string>>{
308
30
        {"bearer-token", SECRET_TOKEN_CONFIG},
309
30
    };
310
30
  }
311

            
312
36
  void initialize() override {
313
36
    accessLogServer_.clear();
314
36
    if (!initialized_) {
315
33
      HttpIntegrationTest::initialize();
316
33
      initialized_ = true;
317
33
    }
318
36
  }
319

            
320
13
  void denied(Http::TestRequestHeaderMapImpl&& headers) {
321
13
    initialize();
322
13
    codec_client_ = makeHttpConnection(lookupPort("http"));
323
13
    auto response = codec_client_->makeHeaderOnlyRequest(headers);
324
13
    ASSERT_TRUE(response->waitForEndStream());
325

            
326
    // Validate that request access log message with x-request-id is logged
327
13
    absl::optional<std::string> maybe_x_request_id;
328
13
    EXPECT_TRUE(expectAccessLogDeniedTo([&maybe_x_request_id](const ::cilium::LogEntry& entry) {
329
13
      maybe_x_request_id = getHeader(entry.http().headers(), "x-request-id");
330
13
      return entry.http().status() == 0;
331
13
    }));
332
13
    ASSERT_TRUE(maybe_x_request_id.has_value());
333

            
334
    // Validate that response x-request-id is the same as in request
335
13
    absl::optional<std::string> maybe_x_request_id_resp;
336
13
    EXPECT_TRUE(
337
13
        expectAccessLogResponseTo([&maybe_x_request_id_resp](const ::cilium::LogEntry& entry) {
338
13
          maybe_x_request_id_resp = getHeader(entry.http().headers(), "x-request-id");
339
13
          return entry.http().status() == 403;
340
13
        }));
341
13
    ASSERT_TRUE(maybe_x_request_id_resp.has_value());
342
13
    EXPECT_EQ(maybe_x_request_id.value(), maybe_x_request_id_resp.value());
343

            
344
13
    EXPECT_TRUE(response->complete());
345
13
    EXPECT_EQ("403", response->headers().getStatusValue());
346
13
    cleanupUpstreamAndDownstream();
347
13
  }
348

            
349
2
  void deniedL3(Http::TestRequestHeaderMapImpl&& headers) {
350
2
    initialize();
351
2
    codec_client_ = makeHttpConnection(lookupPort("http"));
352
2
    auto response = codec_client_->makeHeaderOnlyRequest(headers);
353
2
    ASSERT_TRUE(codec_client_->waitForDisconnect());
354

            
355
    // Validate that request access log message is logged
356
2
    absl::optional<std::string> maybe_x_request_id;
357
2
    EXPECT_TRUE(expectAccessLogDeniedTo([](const ::cilium::LogEntry&) { return true; }));
358
2
    cleanupUpstreamAndDownstream();
359
2
  }
360

            
361
20
  void accepted(Http::TestRequestHeaderMapImpl&& headers) {
362
20
    initialize();
363
20
    codec_client_ = makeHttpConnection(lookupPort("http"));
364
20
    auto response = sendRequestAndWaitForResponse(headers, 0, default_response_headers_, 0);
365

            
366
    // Validate that request access log message with x-request-id is logged
367
20
    absl::optional<std::string> maybe_x_request_id;
368
20
    EXPECT_TRUE(expectAccessLogRequestTo([&maybe_x_request_id](const ::cilium::LogEntry& entry) {
369
20
      maybe_x_request_id = getHeader(entry.http().headers(), "x-request-id");
370
20
      return entry.http().status() == 0;
371
20
    }));
372
20
    ASSERT_TRUE(maybe_x_request_id.has_value());
373

            
374
    // Validate that response x-request-id is the same as in request
375
20
    absl::optional<std::string> maybe_x_request_id_resp;
376
20
    EXPECT_TRUE(
377
20
        expectAccessLogResponseTo([&maybe_x_request_id_resp](const ::cilium::LogEntry& entry) {
378
20
          maybe_x_request_id_resp = getHeader(entry.http().headers(), "x-request-id");
379
20
          return entry.http().status() == 200;
380
20
        }));
381
20
    ASSERT_TRUE(maybe_x_request_id_resp.has_value());
382
20
    EXPECT_EQ(maybe_x_request_id.value(), maybe_x_request_id_resp.value());
383

            
384
20
    EXPECT_TRUE(response->complete());
385
20
    EXPECT_EQ("200", response->headers().getStatusValue());
386
20
    EXPECT_TRUE(upstream_request_->complete());
387
20
    EXPECT_EQ(0, upstream_request_->bodyLength());
388
20
    cleanupUpstreamAndDownstream();
389
20
  }
390

            
391
  bool initialized_ = false;
392
};
393

            
394
INSTANTIATE_TEST_SUITE_P(IpVersions, CiliumIntegrationTest,
395
                         testing::ValuesIn(TestEnvironment::getIpVersionsForTest()));
396

            
397
class HostMapTest : public CiliumHttpIntegrationTest {
398
public:
399
  HostMapTest()
400
10
      : CiliumHttpIntegrationTest(fmt::format(
401
10
            fmt::runtime(TestEnvironment::substitute(cilium_proxy_config_fmt, GetParam())),
402
10
            "true")) {}
403

            
404
  std::string testPolicyFmt() override { return "version_info: \"0\""; }
405

            
406
9
  void invalidHostMap(const std::string& config, const char* exmsg) {
407
9
    std::string path = TestEnvironment::writeStringToFileForTest("host_map_fail.yaml", config);
408
9
    envoy::service::discovery::v3::DiscoveryResponse message;
409
9
    ThreadLocal::InstanceImpl tls;
410
9
    Cilium::PolicyHostDecoder host_decoder;
411

            
412
9
    THROW_IF_NOT_OK(MessageUtil::loadFromFile(
413
9
        path, message, ProtobufMessage::getNullValidationVisitor(), *api_.get()));
414
9
    Envoy::Cilium::PolicyHostMap hmap(tls);
415
9
    const auto decoded_resources =
416
9
        THROW_OR_RETURN_VALUE(Config::DecodedResourcesWrapper::create(
417
9
                                  host_decoder, message.resources(), message.version_info()),
418
9
                              std::unique_ptr<Config::DecodedResourcesWrapper>);
419

            
420
9
    EXPECT_THROW_WITH_MESSAGE(
421
9
        EXPECT_TRUE(hmap.onConfigUpdate(decoded_resources->refvec_, message.version_info()).ok()),
422
9
        EnvoyException, exmsg);
423

            
424
9
    tls.shutdownGlobalThreading();
425
9
  }
426
};
427

            
428
INSTANTIATE_TEST_SUITE_P(IpVersions, HostMapTest,
429
                         testing::ValuesIn(TestEnvironment::getIpVersionsForTest()));
430

            
431
1
TEST_P(HostMapTest, HostMapValid) {
432
1
  std::string config = R"EOF(version_info: "0"
433
1
resources:
434
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
435
1
  policy: 173
436
1
  host_addresses: [ "192.168.0.1", "f00d::1" ]
437
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
438
1
  policy: 1
439
1
  host_addresses: [ "127.0.0.1/32", "::1/128" ]
440
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
441
1
  policy: 11
442
1
  host_addresses: [ "127.0.0.0/8", "beef::/63" ]
443
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
444
1
  policy: 12
445
1
  host_addresses: [ "0.0.0.0/0", "::/0" ]
446
1
)EOF";
447

            
448
1
  std::string path = TestEnvironment::writeStringToFileForTest("host_map_success.yaml", config);
449
1
  envoy::service::discovery::v3::DiscoveryResponse message;
450
1
  ThreadLocal::InstanceImpl tls;
451
1
  Cilium::PolicyHostDecoder host_decoder;
452

            
453
1
  THROW_IF_NOT_OK(MessageUtil::loadFromFile(
454
1
      path, message, ProtobufMessage::getNullValidationVisitor(), *api_.get()));
455
1
  auto hmap = std::make_shared<Envoy::Cilium::PolicyHostMap>(tls);
456
1
  const auto decoded_resources =
457
1
      THROW_OR_RETURN_VALUE(Config::DecodedResourcesWrapper::create(
458
1
                                host_decoder, message.resources(), message.version_info()),
459
1
                            std::unique_ptr<Config::DecodedResourcesWrapper>);
460

            
461
1
  EXPECT_TRUE(hmap->onConfigUpdate(decoded_resources->refvec_, message.version_info()).ok());
462

            
463
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv4Instance("192.168.0.1").ip()), 173);
464
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv4Instance("192.168.0.0").ip()), 12);
465
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv4Instance("192.168.0.2").ip()), 12);
466
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv4Instance("127.0.0.1").ip()), 1);
467
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv4Instance("127.0.0.2").ip()), 11);
468
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv4Instance("126.0.0.2").ip()), 12);
469
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv4Instance("128.0.0.0").ip()), 12);
470
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv6Instance("::1").ip()), 1);
471
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv6Instance("::").ip()), 12);
472
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv6Instance("f00d::1").ip()), 173);
473
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv6Instance("f00d::").ip()), 12);
474
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv6Instance("beef::1.2.3.4").ip()), 11);
475
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv6Instance("beef:0:0:1::").ip()), 11);
476
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv6Instance("beef:0:0:1::42").ip()), 11);
477
1
  EXPECT_EQ(hmap->resolve(Network::Address::Ipv6Instance("beef:0:0:2::").ip()), 12);
478

            
479
1
  tls.shutdownGlobalThreading();
480
1
}
481

            
482
1
TEST_P(HostMapTest, HostMapInvalidNonCIDRBits) {
483
1
  if (GetParam() == Network::Address::IpVersion::v4) {
484
1
    invalidHostMap(R"EOF(version_info: "0"
485
1
resources:
486
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
487
1
  policy: 11
488
1
  host_addresses: [ "127.0.0.1/32", "127.0.0.1/31" ]
489
1
)EOF",
490
1
                   "NetworkPolicyHosts: Non-prefix bits set in '127.0.0.1/31'");
491
1
  } else {
492
    invalidHostMap(R"EOF(version_info: "0"
493
resources:
494
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
495
  policy: 11
496
  host_addresses: [ "::1/128", "::1/63" ]
497
)EOF",
498
                   "NetworkPolicyHosts: Non-prefix bits set in '::1/63'");
499
  }
500
1
}
501

            
502
1
TEST_P(HostMapTest, HostMapInvalidPrefixLengths) {
503
1
  if (GetParam() == Network::Address::IpVersion::v4) {
504
1
    invalidHostMap(
505
1
        R"EOF(version_info: "0"
506
1
resources:
507
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
508
1
  policy: 11
509
1
  host_addresses: [ "127.0.0.1", "127.0.0.0/8", "127.0.0.1/33" ]
510
1
)EOF",
511
1
        "NetworkPolicyHosts: Invalid prefix length in '127.0.0.1/33'");
512
1
  } else {
513
    invalidHostMap(R"EOF(version_info: "0"
514
resources:
515
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
516
  policy: 11
517
  host_addresses: [ "::1/128", "f00f::/65", "::3/129" ]
518
)EOF",
519
                   "NetworkPolicyHosts: Invalid prefix length in '::3/129'");
520
  }
521
1
}
522

            
523
1
TEST_P(HostMapTest, HostMapInvalidPrefixLengths2) {
524
1
  if (GetParam() == Network::Address::IpVersion::v4) {
525
1
    invalidHostMap(
526
1
        R"EOF(version_info: "0"
527
1
resources:
528
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
529
1
  policy: 11
530
1
  host_addresses: [ "127.0.0.1", "127.0.0.0/8", "127.0.0.1/32a" ]
531
1
)EOF",
532
1
        "NetworkPolicyHosts: Invalid prefix length in '127.0.0.1/32a'");
533
1
  } else {
534
    invalidHostMap(R"EOF(version_info: "0"
535
resources:
536
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
537
  policy: 11
538
  host_addresses: [ "::1/128", "f00f::/65", "::3/" ]
539
)EOF",
540
                   "NetworkPolicyHosts: Invalid prefix length in '::3/'");
541
  }
542
1
}
543

            
544
1
TEST_P(HostMapTest, HostMapInvalidPrefixLengths3) {
545
1
  if (GetParam() == Network::Address::IpVersion::v4) {
546
1
    invalidHostMap(
547
1
        R"EOF(version_info: "0"
548
1
resources:
549
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
550
1
  policy: 11
551
1
  host_addresses: [ "127.0.0.1", "127.0.0.0/8", "127.0.0.1/ 32" ]
552
1
)EOF",
553
1
        "NetworkPolicyHosts: Invalid prefix length in '127.0.0.1/ 32'");
554
1
  } else {
555
    invalidHostMap(R"EOF(version_info: "0"
556
resources:
557
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
558
  policy: 11
559
  host_addresses: [ "::1/128", "f00f::/65", "::3/128 " ]
560
)EOF",
561
                   "NetworkPolicyHosts: Invalid prefix length in '::3/128 '");
562
  }
563
1
}
564

            
565
1
TEST_P(HostMapTest, HostMapDuplicateEntry) {
566
1
  if (GetParam() == Network::Address::IpVersion::v4) {
567
1
    invalidHostMap(R"EOF(version_info: "0"
568
1
resources:
569
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
570
1
  policy: 11
571
1
  host_addresses: [ "127.0.0.0/16", "127.0.0.1/32", "127.0.0.1" ]
572
1
)EOF",
573
1
                   "NetworkPolicyHosts: Duplicate host entry '127.0.0.1' for "
574
1
                   "policy 11, already mapped to 11");
575
1
  } else {
576
    invalidHostMap(R"EOF(version_info: "0"
577
resources:
578
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
579
  policy: 11
580
  host_addresses: [ "::1/128", "f00f::/65", "::1" ]
581
)EOF",
582
                   "NetworkPolicyHosts: Duplicate host entry '::1' for policy "
583
                   "11, already mapped to 11");
584
  }
585
1
}
586

            
587
1
TEST_P(HostMapTest, HostMapDuplicateEntry2) {
588
1
  if (GetParam() == Network::Address::IpVersion::v4) {
589
1
    invalidHostMap(R"EOF(version_info: "0"
590
1
resources:
591
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
592
1
  policy: 11
593
1
  host_addresses: [ "127.0.0.0/16", "127.0.0.1/32" ]
594
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
595
1
  policy: 12
596
1
  host_addresses: [ "127.0.0.0/8", "127.0.0.1" ]
597
1
)EOF",
598
1
                   "NetworkPolicyHosts: Duplicate host entry '127.0.0.1' for "
599
1
                   "policy 12, already mapped to 11");
600
1
  } else {
601
    invalidHostMap(R"EOF(version_info: "0"
602
resources:
603
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
604
  policy: 11
605
  host_addresses: [ "::1/128", "f00f::/65" ]
606
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
607
  policy: 12
608
  host_addresses: [ "f00f::/16", "::1" ]
609
)EOF",
610
                   "NetworkPolicyHosts: Duplicate host entry '::1' for policy "
611
                   "12, already mapped to 11");
612
  }
613
1
}
614

            
615
1
TEST_P(HostMapTest, HostMapInvalidAddress) {
616
1
  if (GetParam() == Network::Address::IpVersion::v4) {
617
1
    invalidHostMap(
618
1
        R"EOF(version_info: "0"
619
1
resources:
620
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
621
1
  policy: 11
622
1
  host_addresses: [ "127.0.0.0/16", "127.0.0.1/32", "255.256.0.0" ]
623
1
)EOF",
624
1
        "NetworkPolicyHosts: Invalid host entry '255.256.0.0' for policy 11");
625
1
  } else {
626
    invalidHostMap(
627
        R"EOF(version_info: "0"
628
resources:
629
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
630
  policy: 11
631
  host_addresses: [ "::1/128", "f00f::/65", "fOOd::1" ]
632
)EOF",
633
        "NetworkPolicyHosts: Invalid host entry 'fOOd::1' for policy 11");
634
  }
635
1
}
636

            
637
1
TEST_P(HostMapTest, HostMapInvalidAddress2) {
638
1
  if (GetParam() == Network::Address::IpVersion::v4) {
639
1
    invalidHostMap(
640
1
        R"EOF(version_info: "0"
641
1
resources:
642
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
643
1
  policy: 11
644
1
  host_addresses: [ "127.0.0.0/16", "127.0.0.1/32", "255.255.0.0 " ]
645
1
)EOF",
646
1
        "NetworkPolicyHosts: Invalid host entry '255.255.0.0 ' for policy 11");
647
1
  } else {
648
    invalidHostMap(
649
        R"EOF(version_info: "0"
650
resources:
651
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
652
  policy: 11
653
  host_addresses: [ "::1/128", "f00f::/65", "f00d:: 1" ]
654
)EOF",
655
        "NetworkPolicyHosts: Invalid host entry 'f00d:: 1' for policy 11");
656
  }
657
1
}
658

            
659
1
TEST_P(HostMapTest, HostMapInvalidDefaults) {
660
1
  if (GetParam() == Network::Address::IpVersion::v4) {
661
1
    invalidHostMap(R"EOF(version_info: "0"
662
1
resources:
663
1
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
664
1
  policy: 11
665
1
  host_addresses: [ "0.0.0.0/0", "128.0.0.0/0" ]
666
1
)EOF",
667
1
                   "NetworkPolicyHosts: Non-prefix bits set in '128.0.0.0/0'");
668
1
  } else {
669
    invalidHostMap(R"EOF(version_info: "0"
670
resources:
671
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
672
  policy: 11
673
  host_addresses: [ "::/0", "8000::/0" ]
674
)EOF",
675
                   "NetworkPolicyHosts: Non-prefix bits set in '8000::/0'");
676
  }
677
1
}
678

            
679
1
TEST_P(CiliumIntegrationTest, DeniedPathPrefix) {
680
1
  denied({{":method", "GET"}, {":path", "/prefix"}, {":authority", "host"}});
681

            
682
  // Validate that missing headers are access logged correctly
683
1
  EXPECT_TRUE(expectAccessLogDeniedTo([](const ::cilium::LogEntry& entry) {
684
1
    const auto& http = entry.http();
685
1
    return http.missing_headers_size() == 0 && http.rejected_headers_size() == 0;
686
1
  }));
687
1
}
688

            
689
1
TEST_P(CiliumIntegrationTest, AllowedPathPrefix) {
690
1
  accepted({{":method", "GET"},
691
1
            {":path", "/allowed"},
692
1
            {":authority", "host"},
693
1
            {"bearer-token", "d4ef0f5011f163ac"}});
694

            
695
  // Validate that missing headers are access logged correctly
696
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
697
1
    const auto& http = entry.http();
698
1
    const auto& missing = http.missing_headers();
699
1
    return http.missing_headers_size() == 1 && hasHeader(missing, "header42") &&
700
1
           http.rejected_headers_size() == 0 && !hasHeader(http.headers(), "header42");
701
1
  }));
702
1
}
703

            
704
1
TEST_P(CiliumIntegrationTest, AllowedPathPrefixWrongHeader) {
705
1
  accepted({{":method", "GET"},
706
1
            {":path", "/allowed"},
707
1
            {":authority", "host"},
708
1
            {"bearer-token", "wrong-value"},
709
1
            {"x-envoy-original-dst-host", "1.1.1.1:9999"}});
710

            
711
  // Validate that missing headers are access logged correctly
712
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
713
1
    const auto& http = entry.http();
714
1
    const auto& rejected = http.rejected_headers();
715
1
    const auto& missing = http.missing_headers();
716
1
    return http.rejected_headers_size() == 1 && hasHeader(rejected, "bearer-token", "[redacted]") &&
717
1
           http.missing_headers_size() == 2 && hasHeader(missing, "header42") &&
718
1
           hasHeader(missing, "bearer-token", "[redacted]") &&
719
           // Check that logged headers have the replaced value
720
1
           hasHeader(http.headers(), "bearer-token", "d4ef0f5011f163ac") &&
721
1
           !hasHeader(http.headers(), "header42");
722
1
  }));
723
1
}
724

            
725
1
TEST_P(CiliumIntegrationTest, MultipleRequests) {
726
  // 1st request
727
1
  accepted({{":method", "GET"},
728
1
            {":path", "/allowed"},
729
1
            {":authority", "host"},
730
1
            {"bearer-token", "d4ef0f5011f163ac"}});
731

            
732
  // Validate that missing headers are access logged correctly
733
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
734
1
    const auto& http = entry.http();
735
1
    const auto& missing = http.missing_headers();
736
1
    return http.missing_headers_size() == 1 && hasHeader(missing, "header42") &&
737
1
           http.rejected_headers_size() == 0 && !hasHeader(http.headers(), "header42");
738
1
  }));
739

            
740
  // 2nd request
741
1
  accepted({{":method", "GET"},
742
1
            {":path", "/allowed"},
743
1
            {":authority", "host"},
744
1
            {"bearer-token", "wrong-value"},
745
1
            {"x-envoy-original-dst-host", "1.1.1.1:9999"}});
746

            
747
  // Validate that missing headers are access logged correctly
748
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
749
1
    const auto& http = entry.http();
750
1
    const auto& rejected = http.rejected_headers();
751
1
    const auto& missing = http.missing_headers();
752
1
    return http.rejected_headers_size() == 1 && hasHeader(rejected, "bearer-token", "[redacted]") &&
753
1
           http.missing_headers_size() == 2 && hasHeader(missing, "header42") &&
754
1
           hasHeader(missing, "bearer-token", "[redacted]") &&
755
           // Check that logged headers have the replaced value
756
1
           hasHeader(http.headers(), "bearer-token", "d4ef0f5011f163ac") &&
757
1
           !hasHeader(http.headers(), "header42");
758
1
  }));
759
1
}
760

            
761
1
TEST_P(CiliumIntegrationTest, AllowedPathRegex) {
762
1
  accepted({{":method", "GET"}, {":path", "/maybe/public"}, {":authority", "host"}});
763

            
764
  // Validate that missing headers are access logged correctly
765
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
766
1
    const auto& http = entry.http();
767
1
    return http.rejected_headers_size() == 0 && http.missing_headers_size() == 0;
768
1
  }));
769
1
}
770

            
771
1
TEST_P(CiliumIntegrationTest, AllowedPathRegexDeleteHeader) {
772
1
  accepted({{":method", "GET"},
773
1
            {":path", "/maybe/public"},
774
1
            {":authority", "host"},
775
1
            {"User-Agent", "test"}});
776

            
777
  // Validate that missing headers are access logged correctly
778
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
779
1
    const auto& http = entry.http();
780
1
    const auto& rejected = http.rejected_headers();
781
1
    return http.missing_headers_size() == 0 && http.rejected_headers_size() == 1 &&
782
1
           hasHeader(rejected, "user-agent", "test") && !hasHeader(http.headers(), "User-Agent");
783
1
  }));
784
1
}
785

            
786
1
TEST_P(CiliumIntegrationTest, AllowedHostRegexDeleteHeader) {
787
1
  accepted({{":method", "GET"},
788
1
            {":path", "/maybe/private"},
789
1
            {":authority", "hostREGEXname"},
790
1
            {"header42", "test"}});
791

            
792
  // Validate that missing headers are access logged correctly
793
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
794
1
    const auto& http = entry.http();
795
1
    const auto& rejected = http.rejected_headers();
796
1
    return http.missing_headers_size() == 0 && http.rejected_headers_size() == 1 &&
797
1
           hasHeader(rejected, "header42", "test") &&
798
1
           !hasHeader(http.headers(), "header42", "test");
799
1
  }));
800
1
}
801

            
802
1
TEST_P(CiliumIntegrationTest, DeniedPath) {
803
1
  denied({{":method", "GET"}, {":path", "/maybe/private"}, {":authority", "host"}});
804

            
805
  // Validate that missing headers are access logged correctly
806
1
  EXPECT_TRUE(expectAccessLogDeniedTo([](const ::cilium::LogEntry& entry) {
807
1
    const auto& http = entry.http();
808
1
    return http.missing_headers_size() == 0 && http.rejected_headers_size() == 0;
809
1
  }));
810
1
}
811

            
812
1
TEST_P(CiliumIntegrationTest, AllowedHostString) {
813
1
  accepted({{":method", "GET"}, {":path", "/maybe/private"}, {":authority", "allowedHOST"}});
814

            
815
  // Validate that missing headers are access logged correctly
816
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
817
1
    const auto& http = entry.http();
818
1
    const auto& missing = http.missing_headers();
819
1
    return http.missing_headers_size() == 2 && hasHeader(missing, "header2", "value2") &&
820
1
           hasHeader(missing, "header42") && http.rejected_headers_size() == 0 &&
821
1
           !hasHeader(http.headers(), "header42") && hasHeader(http.headers(), "header2", "value2");
822
1
  }));
823
1
}
824

            
825
1
TEST_P(CiliumIntegrationTest, AllowedReplaced) {
826
1
  accepted({{":method", "GET"}, {":path", "/allowed"}, {":authority", "allowedHOST"}});
827

            
828
  // Validate that missing headers are access logged correctly
829
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
830
1
    const auto& http = entry.http();
831
1
    const auto& missing = http.missing_headers();
832
1
    return http.missing_headers_size() == 3 && hasHeader(missing, "bearer-token", "[redacted]") &&
833
1
           hasHeader(missing, "header2", "value2") && hasHeader(missing, "header42") &&
834
1
           http.rejected_headers_size() == 0 && !hasHeader(http.headers(), "header42") &&
835
1
           hasHeader(http.headers(), "header2", "value2") &&
836
1
           hasHeader(http.headers(), "bearer-token", "d4ef0f5011f163ac");
837
1
  }));
838
1
}
839

            
840
1
TEST_P(CiliumIntegrationTest, Denied42) {
841
1
  denied({{":method", "GET"},
842
1
          {":path", "/allowed"},
843
1
          {":authority", "host"},
844
1
          {"header42", "anything"}});
845

            
846
  // Validate that missing headers are access logged correctly
847
1
  EXPECT_TRUE(expectAccessLogDeniedTo([](const ::cilium::LogEntry& entry) {
848
1
    const auto& http = entry.http();
849
1
    const auto& missing = http.missing_headers();
850
1
    const auto& rejected = http.rejected_headers();
851
1
    return http.rejected_headers_size() == 1 && hasHeader(rejected, "header42") &&
852
1
           http.missing_headers_size() == 1 && hasHeader(missing, "bearer-token", "[redacted]") &&
853
1
           hasHeader(http.headers(), "header42") &&
854
1
           hasHeader(http.headers(), "bearer-token", "d4ef0f5011f163ac");
855
1
  }));
856
1
}
857

            
858
1
TEST_P(CiliumIntegrationTest, AllowedReplacedAndDeleted) {
859
1
  accepted({{":method", "GET"},
860
1
            {":path", "/allowed"},
861
1
            {":authority", "allowedHOST"},
862
1
            {"header42", "anything"}});
863

            
864
  // Validate that missing headers are access logged correctly
865
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
866
1
    const auto& http = entry.http();
867
1
    const auto& missing = http.missing_headers();
868
1
    const auto& rejected = http.rejected_headers();
869
1
    return http.rejected_headers_size() == 1 && hasHeader(rejected, "header42") &&
870
1
           http.missing_headers_size() == 2 && hasHeader(missing, "bearer-token", "[redacted]") &&
871
1
           hasHeader(missing, "header2", "value2") && !hasHeader(http.headers(), "header42") &&
872
1
           hasHeader(http.headers(), "header2", "value2") &&
873
1
           hasHeader(http.headers(), "bearer-token", "d4ef0f5011f163ac");
874
1
  }));
875
1
}
876

            
877
1
TEST_P(CiliumIntegrationTest, AllowedHostRegex) {
878
1
  accepted({{":method", "GET"}, {":path", "/maybe/private"}, {":authority", "hostREGEXname"}});
879

            
880
  // Validate that missing headers are access logged correctly
881
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
882
1
    const auto& http = entry.http();
883
1
    return http.missing_headers_size() == 0 && http.rejected_headers_size() == 0;
884
1
  }));
885
1
}
886

            
887
1
TEST_P(CiliumIntegrationTest, DeniedMethod) {
888
1
  denied({{":method", "POST"}, {":path", "/maybe/private"}, {":authority", "host"}});
889

            
890
  // Validate that missing headers are access logged correctly
891
1
  EXPECT_TRUE(expectAccessLogDeniedTo([](const ::cilium::LogEntry& entry) {
892
1
    const auto& http = entry.http();
893
1
    return http.missing_headers_size() == 0 && http.rejected_headers_size() == 0;
894
1
  }));
895
1
}
896

            
897
1
TEST_P(CiliumIntegrationTest, AcceptedMethod) {
898
1
  accepted({{":method", "PUT"}, {":path", "/public/opinions"}, {":authority", "host"}});
899

            
900
  // Validate that missing headers are access logged correctly
901
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
902
1
    const auto& http = entry.http();
903
1
    return http.missing_headers_size() == 0 && http.rejected_headers_size() == 0;
904
1
  }));
905
1
}
906

            
907
1
TEST_P(CiliumIntegrationTest, L3DeniedPath) {
908
1
  denied({{":method", "GET"}, {":path", "/only-2-allowed"}, {":authority", "host"}});
909

            
910
  // Validate that missing headers are access logged correctly
911
1
  EXPECT_TRUE(expectAccessLogDeniedTo([](const ::cilium::LogEntry& entry) {
912
1
    const auto& http = entry.http();
913
1
    return http.missing_headers_size() == 0 && http.rejected_headers_size() == 0;
914
1
  }));
915
1
}
916

            
917
class CiliumIntegrationPortTest : public CiliumIntegrationTest {
918
public:
919
2
  CiliumIntegrationPortTest() = default;
920

            
921
2
  std::string testPolicyFmt() override {
922
2
    return TestEnvironment::substitute(R"EOF(version_info: "0"
923
2
resources:
924
2
- "@type": type.googleapis.com/cilium.NetworkPolicy
925
2
  endpoint_ips:
926
2
  - '{{ ntop_ip_loopback_address }}'
927
2
  endpoint_id: 3
928
2
  ingress_per_port_policies:
929
2
  - port: {0}
930
2
    rules:
931
2
    - remote_policies: [ 1 ]
932
2
      http_rules:
933
2
        http_rules:
934
2
        - headers:
935
2
          - name: ':path'
936
2
            exact_match: '/only-2-allowed'
937
2
  - port: {0}
938
2
    rules:
939
2
    - remote_policies: [ 1 ]
940
2
      http_rules:
941
2
        http_rules:
942
2
        - headers:
943
2
          - name: ':path'
944
2
            safe_regex_match:
945
2
              google_re2: {{}}
946
2
              regex: '/also-2-allowed'
947
2
  egress_per_port_policies:
948
2
  - port: {0}
949
2
    rules:
950
2
    - remote_policies: [ 2 ]
951
2
      http_rules:
952
2
        http_rules:
953
2
        - headers:
954
2
          - name: ':path'
955
2
            exact_match: '/only-2-allowed'
956
2
)EOF",
957
2
                                       GetParam());
958
2
  }
959
};
960

            
961
INSTANTIATE_TEST_SUITE_P(IpVersions, CiliumIntegrationPortTest,
962
                         testing::ValuesIn(TestEnvironment::getIpVersionsForTest()));
963

            
964
1
TEST_P(CiliumIntegrationPortTest, DuplicatePortAllowedPath) {
965
1
  accepted({{":method", "GET"}, {":path", "/only-2-allowed"}, {":authority", "host"}});
966
1
}
967

            
968
1
TEST_P(CiliumIntegrationPortTest, DuplicatePortAllowedPath2) {
969
1
  accepted({{":method", "GET"}, {":path", "/also-2-allowed"}, {":authority", "host"}});
970
1
}
971

            
972
class CiliumIntegrationPortRangeTest : public CiliumIntegrationTest {
973
public:
974
1
  CiliumIntegrationPortRangeTest() = default;
975

            
976
1
  std::string testPolicyFmt() override {
977
1
    return TestEnvironment::substitute(BASIC_POLICY_fmt + R"EOF(  - end_port: {0}
978
1
    rules:
979
1
    - remote_policies: [ 2 ]
980
1
      http_rules:
981
1
        http_rules:
982
1
        - headers:
983
1
          - name: ':path'
984
1
            safe_regex_match:
985
1
              google_re2: {{}}
986
1
              regex: '/only-2-allowed'
987
1
)EOF",
988
1
                                       GetParam());
989
1
  }
990
};
991

            
992
INSTANTIATE_TEST_SUITE_P(IpVersions, CiliumIntegrationPortRangeTest,
993
                         testing::ValuesIn(TestEnvironment::getIpVersionsForTest()));
994

            
995
1
TEST_P(CiliumIntegrationPortRangeTest, InvalidRange) {
996
1
  initialize();
997

            
998
  // This would normally be allowed, but since the policy fails, everything will
999
  // be rejected.
1
  Http::TestRequestHeaderMapImpl headers = {
1
      {":method", "GET"}, {":path", "/allowed"}, {":authority", "host"}};
1
  codec_client_ = makeHttpConnection(lookupPort("http"));
1
  ASSERT_TRUE(codec_client_->waitForDisconnect());
1
  cleanupUpstreamAndDownstream();
1
}
class CiliumIntegrationEgressTest : public CiliumIntegrationTest {
public:
  CiliumIntegrationEgressTest()
11
      : CiliumIntegrationTest(fmt::format(
11
            fmt::runtime(TestEnvironment::substitute(cilium_proxy_config_fmt, GetParam())),
11
            "false")) {
11
    host_map_config = R"EOF(version_info: "0"
11
resources:
11
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
11
  policy: 173
11
  host_addresses: [ "192.168.0.1", "f00d::1" ]
11
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
11
  policy: 1
11
  host_addresses: [ "127.0.0.0/8", "::/104" ]
11
)EOF";
11
  }
};
INSTANTIATE_TEST_SUITE_P(IpVersions, CiliumIntegrationEgressTest,
                         testing::ValuesIn(TestEnvironment::getIpVersionsForTest()));
1
TEST_P(CiliumIntegrationEgressTest, DeniedPathPrefix) {
1
  denied({{":method", "GET"}, {":path", "/prefix"}, {":authority", "host"}});
1
}
1
TEST_P(CiliumIntegrationEgressTest, AllowedPathPrefix) {
1
  accepted({{":method", "GET"}, {":path", "/allowed"}, {":authority", "host"}});
1
}
1
TEST_P(CiliumIntegrationEgressTest, AllowedPathRegex) {
1
  accepted({{":method", "GET"}, {":path", "/maybe/public"}, {":authority", "host"}});
1
}
1
TEST_P(CiliumIntegrationEgressTest, DeniedPath) {
1
  denied({{":method", "GET"}, {":path", "/maybe/private"}, {":authority", "host"}});
1
}
1
TEST_P(CiliumIntegrationEgressTest, AllowedHostString) {
1
  accepted({{":method", "GET"}, {":path", "/maybe/private"}, {":authority", "allowedHOST"}});
1
}
1
TEST_P(CiliumIntegrationEgressTest, AllowedHostRegex) {
1
  accepted({{":method", "GET"}, {":path", "/maybe/private"}, {":authority", "hostREGEXname"}});
1
}
1
TEST_P(CiliumIntegrationEgressTest, DeniedMethod) {
1
  denied({{":method", "POST"}, {":path", "/maybe/private"}, {":authority", "host"}});
1
}
1
TEST_P(CiliumIntegrationEgressTest, AcceptedMethod) {
1
  accepted({{":method", "PUT"}, {":path", "/public/opinions"}, {":authority", "host"}});
1
}
1
TEST_P(CiliumIntegrationEgressTest, L3DeniedPath) {
1
  denied({{":method", "GET"}, {":path", "/only-2-allowed"}, {":authority", "host"}});
1
}
// This policy has no HTTP rules, and allows all traffic to identity 42
const std::string L34_POLICY_fmt = R"EOF(version_info: "0"
resources:
- "@type": type.googleapis.com/cilium.NetworkPolicy
  endpoint_ips:
  - '{{ ntop_ip_loopback_address }}'
  endpoint_id: 3
  egress_per_port_policies:
  - port: {0}
    end_port: {0}
    rules:
    - remote_policies: [ 42 ]
)EOF";
class CiliumIntegrationEgressL34Test : public CiliumIntegrationEgressTest {
public:
2
  CiliumIntegrationEgressL34Test() = default;
2
  std::string testPolicyFmt() override {
2
    return TestEnvironment::substitute(L34_POLICY_fmt, GetParam());
2
  }
};
INSTANTIATE_TEST_SUITE_P(IpVersions, CiliumIntegrationEgressL34Test,
                         testing::ValuesIn(TestEnvironment::getIpVersionsForTest()));
1
TEST_P(CiliumIntegrationEgressL34Test, DeniedPathPrefix) {
1
  deniedL3({{":method", "GET"}, {":path", "/prefix"}, {":authority", "host"}});
1
}
1
TEST_P(CiliumIntegrationEgressL34Test, DeniedPathPrefix2) {
1
  deniedL3({{":method", "GET"}, {":path", "/allowed"}, {":authority", "host"}});
1
}
const std::string HEADER_ACTION_MISSING_SDS_POLICY_fmt = R"EOF(version_info: "1"
resources:
- "@type": type.googleapis.com/cilium.NetworkPolicy
  endpoint_ips:
  - '{{ ntop_ip_loopback_address }}'
  endpoint_id: 3
  ingress_per_port_policies:
  - port: {0}
    rules:
    - remote_policies: [ 1 ]
      http_rules:
        http_rules:
        - headers:
          - name: ':path'
            exact_match: '/allowed2'
    - remote_policies: [ 42 ]
      http_rules:
        http_rules:
        - headers:
          - name: ':path'
            exact_match: '/only42'
)EOF";
const std::string HEADER_ACTION_MISSING_SDS_POLICY2_fmt = R"EOF(version_info: "2"
resources:
- "@type": type.googleapis.com/cilium.NetworkPolicy
  endpoint_ips:
  - '{{ ntop_ip_loopback_address }}'
  endpoint_id: 3
  ingress_per_port_policies:
  - port: {0}
    rules:
    - remote_policies: [ 1 ]
      http_rules:
        http_rules:
        - headers:
          - name: ':path'
            exact_match: '/allowed'
          header_matches:
          - name: 'header42'
            match_action: FAIL_ON_MATCH
            mismatch_action: CONTINUE_ON_MISMATCH
          - name: 'bearer-token'
            value_sds_secret: 'nonexisting-sds-secret'
            mismatch_action: REPLACE_ON_MISMATCH
)EOF";
class SDSIntegrationTest : public CiliumIntegrationTest {
public:
3
  SDSIntegrationTest() {
    // switch back to SDS secrets so that we can test with a missing secret.
    // File based secret fails if the file does not exist, while SDS should allow for secret to be
    // created in future.
3
    Cilium::resetSDSConfigFunc();
3
    host_map_config = R"EOF(version_info: "0"
3
resources:
3
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
3
  policy: 42
3
  host_addresses: [ "192.168.1.1", "f00d::1:1" ]
3
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
3
  policy: 1
3
  host_addresses: [ "127.0.0.0/8", "::/104" ]
3
- "@type": type.googleapis.com/cilium.NetworkPolicyHosts
3
  policy: 2
3
  host_addresses: [ "0.0.0.0/0", "::/0" ]
3
)EOF";
3
  }
1
  std::string testPolicyFmt2() {
1
    return TestEnvironment::substitute(HEADER_ACTION_MISSING_SDS_POLICY2_fmt, GetParam());
1
  }
3
  std::string testPolicyFmt() override {
3
    return TestEnvironment::substitute(HEADER_ACTION_MISSING_SDS_POLICY_fmt, GetParam());
3
  }
3
  std::vector<std::pair<std::string, std::string>> testSecrets() override {
3
    return std::vector<std::pair<std::string, std::string>>{}; // No secrets
3
  }
};
INSTANTIATE_TEST_SUITE_P(IpVersions, SDSIntegrationTest,
                         testing::ValuesIn(TestEnvironment::getIpVersionsForTest()));
1
TEST_P(SDSIntegrationTest, TestDeniedL3) {
1
  denied({{":method", "GET"}, {":path", "/only42"}, {":authority", "host"}});
  // Validate that missing headers are access logged correctly
1
  EXPECT_TRUE(expectAccessLogDeniedTo([](const ::cilium::LogEntry& entry) {
1
    auto source_ip = Network::Utility::parseInternetAddressAndPortNoThrow(entry.source_address())
1
                         ->ip()
1
                         ->addressAsString();
1
    const auto& http = entry.http();
1
    ENVOY_LOG_MISC(info, "Access Log entry: {}", entry.DebugString());
1
    return http.rejected_headers_size() == 0 && http.missing_headers_size() == 0 &&
1
           entry.source_security_id() == 1 &&
1
           source_ip == ((GetParam() == Network::Address::IpVersion::v4) ? "127.0.0.1" : "::1");
1
  }));
1
}
1
TEST_P(SDSIntegrationTest, TestDeniedL3SpoofedXFF) {
1
  denied({{":method", "GET"},
1
          {":path", "/only42"},
1
          {":authority", "host"},
1
          {"x-forwarded-for", "192.168.1.1"}});
  // Validate that missing headers are access logged correctly
1
  EXPECT_TRUE(expectAccessLogDeniedTo([](const ::cilium::LogEntry& entry) {
1
    auto source_ip = Network::Utility::parseInternetAddressAndPortNoThrow(entry.source_address())
1
                         ->ip()
1
                         ->addressAsString();
1
    const auto& http = entry.http();
1
    ENVOY_LOG_MISC(info, "Access Log entry: {}", entry.DebugString());
1
    return http.rejected_headers_size() == 0 && http.missing_headers_size() == 0 &&
1
           entry.source_security_id() == 1 &&
1
           source_ip == ((GetParam() == Network::Address::IpVersion::v4) ? "127.0.0.1" : "::1");
1
  }));
1
}
1
TEST_P(SDSIntegrationTest, TestMissingSDSSecretOnUpdate) {
1
  accepted({{":method", "GET"}, {":path", "/allowed2"}, {":authority", "host"}});
  // Validate that missing headers are access logged correctly
1
  EXPECT_TRUE(expectAccessLogRequestTo([](const ::cilium::LogEntry& entry) {
1
    const auto& http = entry.http();
1
    ENVOY_LOG_MISC(info, "Access Log entry: {}", entry.DebugString());
1
    return http.rejected_headers_size() == 0 && http.missing_headers_size() == 0;
1
  }));
  // Update policy that still has the missing secret
1
  auto port = fake_upstreams_[0]->localAddress()->ip()->port();
1
  auto config = fmt::format(fmt::runtime(testPolicyFmt2()), port);
1
  std::string temp_path =
1
      TestEnvironment::writeStringToFileForTest("network_policy_tmp.yaml", config);
1
  std::string backup_path = policy_path + ".backup";
1
  TestEnvironment::renameFile(policy_path, backup_path);
1
  TestEnvironment::renameFile(temp_path, policy_path);
1
  ENVOY_LOG_MISC(debug,
1
                 "Updating Cilium Network Policy from file \'{}\'->\'{}\' instead "
1
                 "of using gRPC",
1
                 temp_path, policy_path);
  // Reduce flakiness by allowing some time for the policy to be updated before the following test
1
  absl::SleepFor(absl::Milliseconds(100));
  // 2nd round, on updated policy
1
  denied({{":method", "GET"}, {":path", "/allowed"}, {":authority", "host"}});
  // Validate that missing headers are access logged correctly
1
  EXPECT_TRUE(expectAccessLogDeniedTo([](const ::cilium::LogEntry& entry) {
1
    const auto& http = entry.http();
1
    const auto& missing = http.missing_headers();
1
    ENVOY_LOG_MISC(info, "Access Log entry: {}", entry.DebugString());
1
    return http.rejected_headers_size() == 0 && http.missing_headers_size() == 1 &&
1
           hasHeader(missing, "header42");
1
  }));
  // 3rd round back to the initial policy
1
  TestEnvironment::renameFile(backup_path, policy_path);
1
  ENVOY_LOG_MISC(debug,
1
                 "Updating Cilium Network Policy from file \'{}\'->\'{}\' instead "
1
                 "of using gRPC",
1
                 backup_path, policy_path);
  // Reduce flakiness by allowing some time for the policy to be updated before the following test
1
  absl::SleepFor(absl::Milliseconds(100));
1
  denied({{":method", "GET"}, {":path", "/allowed"}, {":authority", "host"}});
  // Validate that missing headers are access logged correctly
1
  EXPECT_TRUE(expectAccessLogDeniedTo([](const ::cilium::LogEntry& entry) {
1
    const auto& http = entry.http();
1
    ENVOY_LOG_MISC(info, "Access Log entry: {}", entry.DebugString());
1
    return http.rejected_headers_size() == 0 && http.missing_headers_size() == 0;
1
  }));
1
}
} // namespace Envoy