@highstate/k8s 0.9.4 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/chunk-DQSCJM5S.js +183 -0
  2. package/dist/chunk-DQSCJM5S.js.map +1 -0
  3. package/dist/chunk-FKNHHKOL.js +260 -0
  4. package/dist/chunk-FKNHHKOL.js.map +1 -0
  5. package/dist/chunk-HW3NS3MC.js +347 -0
  6. package/dist/chunk-HW3NS3MC.js.map +1 -0
  7. package/dist/chunk-OQ7UXASD.js +193 -0
  8. package/dist/chunk-OQ7UXASD.js.map +1 -0
  9. package/dist/chunk-QGHMLKTW.js +1123 -0
  10. package/dist/chunk-QGHMLKTW.js.map +1 -0
  11. package/dist/chunk-UNVSWG6D.js +214 -0
  12. package/dist/chunk-UNVSWG6D.js.map +1 -0
  13. package/dist/deployment-ZP3ASKPT.js +10 -0
  14. package/dist/deployment-ZP3ASKPT.js.map +1 -0
  15. package/dist/highstate.manifest.json +8 -6
  16. package/dist/index.js +291 -954
  17. package/dist/index.js.map +1 -1
  18. package/dist/stateful-set-2AH7RAF7.js +10 -0
  19. package/dist/stateful-set-2AH7RAF7.js.map +1 -0
  20. package/dist/units/access-point/index.js +6 -1
  21. package/dist/units/access-point/index.js.map +1 -1
  22. package/dist/units/cert-manager/index.js +19 -24
  23. package/dist/units/cert-manager/index.js.map +1 -1
  24. package/dist/units/cluster-dns/index.js +36 -0
  25. package/dist/units/cluster-dns/index.js.map +1 -0
  26. package/dist/units/cluster-patch/index.js +34 -0
  27. package/dist/units/cluster-patch/index.js.map +1 -0
  28. package/dist/units/dns01-issuer/index.js +2 -2
  29. package/dist/units/dns01-issuer/index.js.map +1 -1
  30. package/dist/units/existing-cluster/index.js +22 -14
  31. package/dist/units/existing-cluster/index.js.map +1 -1
  32. package/dist/units/gateway-api/index.js +1 -1
  33. package/package.json +12 -10
  34. package/src/access-point.ts +44 -39
  35. package/src/container.ts +54 -5
  36. package/src/cron-job.ts +14 -30
  37. package/src/deployment.ts +170 -127
  38. package/src/gateway/http-route.ts +7 -5
  39. package/src/helm.ts +57 -8
  40. package/src/index.ts +11 -4
  41. package/src/job.ts +14 -32
  42. package/src/namespace.ts +241 -0
  43. package/src/network-policy.ts +371 -87
  44. package/src/network.ts +41 -0
  45. package/src/pvc.ts +43 -25
  46. package/src/scripting/bundle.ts +125 -22
  47. package/src/scripting/container.ts +16 -11
  48. package/src/scripting/environment.ts +56 -6
  49. package/src/secret.ts +195 -0
  50. package/src/service.ts +209 -89
  51. package/src/shared.ts +42 -51
  52. package/src/stateful-set.ts +193 -88
  53. package/src/units/access-point/index.ts +8 -1
  54. package/src/units/cert-manager/index.ts +15 -20
  55. package/src/units/cluster-dns/index.ts +37 -0
  56. package/src/units/cluster-patch/index.ts +35 -0
  57. package/src/units/dns01-issuer/index.ts +1 -1
  58. package/src/units/existing-cluster/index.ts +24 -14
  59. package/src/workload.ts +342 -44
  60. package/dist/chunk-K4WKJ4L5.js +0 -455
  61. package/dist/chunk-K4WKJ4L5.js.map +0 -1
  62. package/dist/chunk-T5Z2M4JE.js +0 -103
  63. package/dist/chunk-T5Z2M4JE.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import { networking, types, type core } from "@pulumi/kubernetes"
2
2
  import {
3
3
  ComponentResource,
4
+ interpolate,
4
5
  normalize,
5
6
  output,
6
7
  type Input,
@@ -10,10 +11,16 @@ import {
10
11
  type ResourceOptions,
11
12
  type Unwrap,
12
13
  } from "@highstate/pulumi"
13
- import { capitalize, flat, merge, mergeDeep } from "remeda"
14
- import { parseDomain, ParseResultType, type ParseResultIp } from "parse-domain"
15
- import { k8s } from "@highstate/library"
14
+ import { capitalize, flat, groupBy, merge, mergeDeep, uniqueBy } from "remeda"
15
+ import { k8s, network } from "@highstate/library"
16
16
  import {
17
+ l34EndpointToString,
18
+ l3EndpointToCidr,
19
+ parseL34Endpoint,
20
+ type InputL34Endpoint,
21
+ } from "@highstate/common"
22
+ import {
23
+ getProvider,
17
24
  mapMetadata,
18
25
  mapNamespaceLikeToNamespaceName,
19
26
  mapNamespaceNameToSelector,
@@ -22,7 +29,7 @@ import {
22
29
  type NamespaceLike,
23
30
  type SelectorLike,
24
31
  } from "./shared"
25
- import { mapServiceToLabelSelector } from "./service"
32
+ import { getServiceMetadata, isFromCluster, mapServiceToLabelSelector } from "./service"
26
33
 
27
34
  export type NetworkPolicyPort = {
28
35
  /**
@@ -36,7 +43,7 @@ export type NetworkPolicyPort = {
36
43
  /**
37
44
  * The single port to match.
38
45
  */
39
- port: number
46
+ port: number | string
40
47
  }
41
48
  | {
42
49
  /**
@@ -68,6 +75,30 @@ export type IngressRuleArgs = {
68
75
  */
69
76
  fromCidrs?: InputArray<string>
70
77
 
78
+ /**
79
+ * The list of allowed L3 or L4 endpoints for outgoing traffic.
80
+ *
81
+ * Just a syntactic sugar for `fromFqdn` and `fromService` for cases when the endpoint can be one of them + optional port/protocol.
82
+ *
83
+ * Will be ORed with other conditions inside the same rule (except ports).
84
+ *
85
+ * If a single endpoint also has a port/protocol/service metadata,
86
+ * it will produce separate rule for it with them and ORed with the rest of the rules.
87
+ */
88
+ fromEndpoint?: Input<InputL34Endpoint>
89
+
90
+ /**
91
+ * The list of allowed L3 or L4 endpoints for incoming traffic.
92
+ *
93
+ * Just a syntactic sugar for `fromFqdn` and `fromService` for cases when the endpoint can be one of them + optional port/protocol.
94
+ *
95
+ * Will be ORed with other conditions inside the same rule (except ports).
96
+ *
97
+ * If a single endpoint also has a port/protocol/service metadata,
98
+ * it will produce separate rule for it with them and ORed with the rest of the rules.
99
+ */
100
+ fromEndpoints?: InputArray<InputL34Endpoint>
101
+
71
102
  /**
72
103
  * The service to allow traffic from.
73
104
  *
@@ -174,20 +205,28 @@ export type EgressRuleArgs = {
174
205
  toFqdns?: InputArray<string>
175
206
 
176
207
  /**
177
- * Either the FQDN or the IP address of the endpoint to allow outgoing traffic.
208
+ * The L3 or L4 endpoint to allow outgoing traffic.
178
209
  *
179
- * Just a syntactic sugar for `toFqdn` and `toCidr` for cases when the endpoint can be both.
210
+ * Just a syntactic sugar for `toFqdn`, `toCidr` and `toService` for cases when the endpoint can be one of them + optional port/protocol.
180
211
  *
181
212
  * Will be ORed with other conditions inside the same rule (except ports).
213
+ *
214
+ * If a single endpoint also has a port/protocol/service metadata,
215
+ * it will produce separate rule for it with them and ORed with the rest of the rules.
182
216
  */
183
- toEndpoint?: Input<string>
217
+ toEndpoint?: Input<InputL34Endpoint>
184
218
 
185
219
  /**
186
- * The list of allowed endpoints for outgoing traffic.
220
+ * The list of allowed L3 or L4 endpoints for outgoing traffic.
221
+ *
222
+ * Just a syntactic sugar for `toFqdn`, `toCidr` and `toService` for cases when the endpoint can be one of them + optional port/protocol.
187
223
  *
188
224
  * Will be ORed with other conditions inside the same rule (except ports).
225
+ *
226
+ * If a single endpoint also has a port/protocol/service metadata,
227
+ * it will produce separate rule for it with them and ORed with the rest of the rules.
189
228
  */
190
- toEndpoints?: InputArray<string>
229
+ toEndpoints?: InputArray<InputL34Endpoint>
191
230
 
192
231
  /**
193
232
  * The service to allow traffic to.
@@ -369,17 +408,6 @@ export abstract class NetworkPolicy extends ComponentResource {
369
408
  const ingressRules = normalize(args.ingressRule, args.ingressRules)
370
409
  const egressRules = normalize(args.egressRule, args.egressRules)
371
410
 
372
- const endpoints = normalize(args.egressRule?.toEndpoint, args.egressRule?.toEndpoints)
373
- const parsedEndpoints = endpoints.map(endpoint => parseDomain(endpoint))
374
-
375
- const cidrsFromEndpoints = parsedEndpoints
376
- .filter(result => result.type === ParseResultType.Ip)
377
- .map(result => NetworkPolicy.mapCidrFromEndpoint(result))
378
-
379
- const fqdnsFromEndpoints = parsedEndpoints
380
- .filter(result => result.type !== ParseResultType.Invalid)
381
- .map(result => result.hostname)
382
-
383
411
  const extraEgressRules: NormalizedRuleArgs[] = []
384
412
 
385
413
  if (args.allowKubeDns) {
@@ -404,47 +432,182 @@ export abstract class NetworkPolicy extends ComponentResource {
404
432
 
405
433
  allowKubeApiServer: args.allowKubeApiServer ?? false,
406
434
 
407
- ingressRules: ingressRules.map(rule => ({
408
- all: rule.fromAll ?? false,
409
- cidrs: normalize(rule.fromCidr, rule.fromCidrs),
410
- fqdns: [],
411
- services: normalize(rule.fromService, rule.fromServices),
412
- namespaces: normalize(rule.fromNamespace, rule.fromNamespaces),
413
- selectors: normalize(rule.fromSelector, rule.fromSelectors),
414
- ports: normalize(rule.toPort, rule.toPorts),
415
- })),
435
+ ingressRules: ingressRules.flatMap(rule => {
436
+ const endpoints = normalize(
437
+ args.ingressRule?.fromEndpoint,
438
+ args.ingressRule?.fromEndpoints,
439
+ )
440
+ const parsedEndpoints = endpoints.map(parseL34Endpoint)
416
441
 
417
- egressRules: egressRules
418
- .map(rule => {
419
- return {
420
- all: rule.toAll ?? false,
421
- cidrs: normalize(rule.toCidr, rule.toCidrs).concat(cidrsFromEndpoints),
422
- fqdns: normalize(rule.toFqdn, rule.toFqdns).concat(fqdnsFromEndpoints),
423
- services: normalize(rule.toService, rule.toServices),
424
- namespaces: normalize(rule.toNamespace, rule.toNamespaces),
425
- selectors: normalize(rule.toSelector, rule.toSelectors),
442
+ const endpointsByPortsAndNamespaces = groupBy(parsedEndpoints, endpoint => {
443
+ const namespace = isFromCluster(endpoint, args.cluster)
444
+ ? endpoint.metadata.k8sService.namespace
445
+ : ""
446
+
447
+ const port = isFromCluster(endpoint, args.cluster)
448
+ ? endpoint.metadata.k8sService.targetPort
449
+ : endpoint.port
450
+
451
+ return `${port ?? "0"}:${namespace}`
452
+ })
453
+
454
+ const l3OnlyRule = endpointsByPortsAndNamespaces["0:"]
455
+ ? NetworkPolicy.getRuleFromEndpoint(
456
+ undefined,
457
+ endpointsByPortsAndNamespaces["0:"],
458
+ args.cluster,
459
+ )
460
+ : undefined
461
+
462
+ const otherRules = Object.entries(endpointsByPortsAndNamespaces)
463
+ .filter(([key]) => key !== "0:")
464
+ .map(([key, endpoints]) => {
465
+ const [port] = key.split(":")
466
+ const portNumber = parseInt(port, 10)
467
+ const portValue = isNaN(portNumber) ? port : portNumber
468
+
469
+ return NetworkPolicy.getRuleFromEndpoint(portValue, endpoints, args.cluster)
470
+ })
471
+
472
+ return [
473
+ {
474
+ all: rule.fromAll ?? false,
475
+ cidrs: normalize(rule.fromCidr, rule.fromCidrs).concat(l3OnlyRule?.cidrs ?? []),
476
+ fqdns: [],
477
+ services: normalize(rule.fromService, rule.fromServices),
478
+ namespaces: normalize(rule.fromNamespace, rule.fromNamespaces),
479
+ selectors: normalize(rule.fromSelector, rule.fromSelectors),
426
480
  ports: normalize(rule.toPort, rule.toPorts),
427
- } as NormalizedRuleArgs
481
+ } as NormalizedRuleArgs,
482
+
483
+ ...otherRules,
484
+ ].filter(rule => !NetworkPolicy.isEmptyRule(rule))
485
+ }),
486
+
487
+ egressRules: egressRules
488
+ .flatMap(rule => {
489
+ const endpoints = normalize(args.egressRule?.toEndpoint, args.egressRule?.toEndpoints)
490
+ const parsedEndpoints = endpoints.map(parseL34Endpoint)
491
+
492
+ const endpointsByPortsAnsNamespaces = groupBy(parsedEndpoints, endpoint => {
493
+ const namespace = isFromCluster(endpoint, args.cluster)
494
+ ? endpoint.metadata.k8sService.namespace
495
+ : ""
496
+
497
+ const port = isFromCluster(endpoint, args.cluster)
498
+ ? endpoint.metadata.k8sService.targetPort
499
+ : endpoint.port
500
+
501
+ return `${port ?? "0"}:${namespace}`
502
+ })
503
+
504
+ const l3OnlyRule = endpointsByPortsAnsNamespaces["0:"]
505
+ ? NetworkPolicy.getRuleFromEndpoint(
506
+ undefined,
507
+ endpointsByPortsAnsNamespaces["0:"],
508
+ args.cluster,
509
+ )
510
+ : undefined
511
+
512
+ const otherRules = Object.entries(endpointsByPortsAnsNamespaces)
513
+ .filter(([key]) => key !== "0:")
514
+ .map(([key, endpoints]) => {
515
+ const [port] = key.split(":")
516
+ const portNumber = parseInt(port, 10)
517
+ const portValue = isNaN(portNumber) ? port : portNumber
518
+
519
+ return NetworkPolicy.getRuleFromEndpoint(portValue, endpoints, args.cluster)
520
+ })
521
+
522
+ return [
523
+ {
524
+ all: rule.toAll ?? false,
525
+ cidrs: normalize(rule.toCidr, rule.toCidrs).concat(l3OnlyRule?.cidrs ?? []),
526
+ fqdns: normalize(rule.toFqdn, rule.toFqdns).concat(l3OnlyRule?.fqdns ?? []),
527
+ services: normalize(rule.toService, rule.toServices),
528
+ namespaces: normalize(rule.toNamespace, rule.toNamespaces),
529
+ selectors: normalize(rule.toSelector, rule.toSelectors),
530
+ ports: normalize(rule.toPort, rule.toPorts),
531
+ } as NormalizedRuleArgs,
532
+
533
+ ...otherRules,
534
+ ].filter(rule => !NetworkPolicy.isEmptyRule(rule))
428
535
  })
429
536
  .concat(extraEgressRules),
430
537
  }
431
538
  })
432
539
 
433
- this.networkPolicy = normalizedArgs.apply(args => {
434
- return output(
435
- this.create(name, args as NormalizedNetworkPolicyArgs, { ...opts, parent: this }),
436
- )
437
- })
540
+ this.networkPolicy = output(
541
+ normalizedArgs.apply(async args => {
542
+ return output(
543
+ this.create(name, args as NormalizedNetworkPolicyArgs, {
544
+ ...opts,
545
+ parent: this,
546
+ provider: await getProvider(args.cluster),
547
+ }),
548
+ )
549
+ }),
550
+ )
551
+ }
552
+
553
+ private static mapCidrFromEndpoint(
554
+ this: void,
555
+ result: network.L3Endpoint & { type: "ipv4" | "ipv6" },
556
+ ): string {
557
+ if (result.type === "ipv4") {
558
+ return `${result.address}/32`
559
+ }
438
560
 
439
- this.registerOutputs({ networkPolicy: this.networkPolicy })
561
+ return `${result.address}/128`
440
562
  }
441
563
 
442
- private static mapCidrFromEndpoint(result: ParseResultIp): string {
443
- if (result.ipVersion === 4) {
444
- return `${result.hostname}/32`
564
+ private static getRuleFromEndpoint(
565
+ port: number | string | undefined,
566
+ endpoints: network.L34Endpoint[],
567
+ cluster: k8s.Cluster,
568
+ ): NormalizedRuleArgs {
569
+ const ports: NetworkPolicyPort[] = port
570
+ ? [{ port, protocol: endpoints[0].protocol?.toUpperCase() }]
571
+ : []
572
+
573
+ const cidrs = endpoints
574
+ .filter(endpoint => !isFromCluster(endpoint, cluster))
575
+ .filter(endpoint => endpoint.type === "ipv4" || endpoint.type === "ipv6")
576
+ .map(NetworkPolicy.mapCidrFromEndpoint)
577
+
578
+ const fqdns = endpoints
579
+ .filter(endpoint => endpoint.type === "hostname")
580
+ .map(endpoint => endpoint.hostname)
581
+
582
+ const selectors = endpoints
583
+ .filter(endpoint => isFromCluster(endpoint, cluster))
584
+ .map(endpoint => endpoint.metadata.k8sService.selector)
585
+
586
+ const namespace = endpoints
587
+ .filter(endpoint => isFromCluster(endpoint, cluster))
588
+ .map(endpoint => getServiceMetadata(endpoint)?.namespace)[0]
589
+
590
+ return {
591
+ all: false,
592
+ cidrs,
593
+ fqdns,
594
+ services: [],
595
+ namespaces: namespace ? [namespace] : [],
596
+ selectors,
597
+ ports,
445
598
  }
599
+ }
446
600
 
447
- return `${result.hostname}/128`
601
+ private static isEmptyRule(rule: NormalizedRuleArgs): boolean {
602
+ return (
603
+ !rule.all &&
604
+ rule.cidrs.length === 0 &&
605
+ rule.fqdns.length === 0 &&
606
+ rule.services.length === 0 &&
607
+ rule.namespaces.length === 0 &&
608
+ rule.selectors.length === 0 &&
609
+ rule.ports.length === 0
610
+ )
448
611
  }
449
612
 
450
613
  protected abstract create(
@@ -453,17 +616,15 @@ export abstract class NetworkPolicy extends ComponentResource {
453
616
  opts?: ResourceOptions,
454
617
  ): Input<Resource>
455
618
 
456
- private static readonly supportedCNIs = ["cilium"]
457
-
458
619
  static create(
459
620
  name: string,
460
621
  args: NetworkPolicyArgs,
461
- opts: ResourceOptions,
622
+ opts?: ResourceOptions,
462
623
  ): Output<NetworkPolicy> {
463
624
  return output(args).apply(async args => {
464
- const cni = args.cluster.info.cni
625
+ const cni = args.cluster.cni
465
626
 
466
- if (!cni || !NetworkPolicy.supportedCNIs.includes(cni)) {
627
+ if (cni === "other") {
467
628
  return new NativeNetworkPolicy(name, args, opts)
468
629
  }
469
630
 
@@ -485,10 +646,30 @@ export abstract class NetworkPolicy extends ComponentResource {
485
646
  })
486
647
  }
487
648
 
649
+ static isolate(
650
+ namespace: Input<NamespaceLike>,
651
+ cluster: Input<k8s.Cluster>,
652
+ opts?: ResourceOptions,
653
+ ) {
654
+ return NetworkPolicy.create(
655
+ "isolate",
656
+ {
657
+ namespace,
658
+ cluster,
659
+
660
+ description: "By default, deny all traffic to/from the namespace.",
661
+
662
+ isolateEgress: true,
663
+ isolateIngress: true,
664
+ },
665
+ opts,
666
+ )
667
+ }
668
+
488
669
  static allowInsideNamespace(
489
670
  namespace: Input<NamespaceLike>,
490
671
  cluster: Input<k8s.Cluster>,
491
- opts: ResourceOptions,
672
+ opts?: ResourceOptions,
492
673
  ): Output<NetworkPolicy> {
493
674
  return NetworkPolicy.create(
494
675
  "allow-inside-namespace",
@@ -509,7 +690,7 @@ export abstract class NetworkPolicy extends ComponentResource {
509
690
  static allowKubeApiServer(
510
691
  namespace: Input<NamespaceLike>,
511
692
  cluster: Input<k8s.Cluster>,
512
- opts: ResourceOptions,
693
+ opts?: ResourceOptions,
513
694
  ): Output<NetworkPolicy> {
514
695
  return NetworkPolicy.create(
515
696
  "allow-kube-api-server",
@@ -528,7 +709,7 @@ export abstract class NetworkPolicy extends ComponentResource {
528
709
  static allowKubeDns(
529
710
  namespace: Input<NamespaceLike>,
530
711
  cluster: Input<k8s.Cluster>,
531
- opts: ResourceOptions,
712
+ opts?: ResourceOptions,
532
713
  ): Output<NetworkPolicy> {
533
714
  return NetworkPolicy.create(
534
715
  "allow-kube-dns",
@@ -547,7 +728,7 @@ export abstract class NetworkPolicy extends ComponentResource {
547
728
  static allowAllEgress(
548
729
  namespace: Input<NamespaceLike>,
549
730
  cluster: Input<k8s.Cluster>,
550
- opts: ResourceOptions,
731
+ opts?: ResourceOptions,
551
732
  ): Output<NetworkPolicy> {
552
733
  return NetworkPolicy.create(
553
734
  "allow-all-egress",
@@ -562,6 +743,69 @@ export abstract class NetworkPolicy extends ComponentResource {
562
743
  opts,
563
744
  )
564
745
  }
746
+
747
+ static allowAllIngress(
748
+ namespace: Input<NamespaceLike>,
749
+ cluster: Input<k8s.Cluster>,
750
+ opts?: ResourceOptions,
751
+ ): Output<NetworkPolicy> {
752
+ return NetworkPolicy.create(
753
+ "allow-all-ingress",
754
+ {
755
+ namespace,
756
+ cluster,
757
+
758
+ description: "Allow all ingress traffic to the namespace.",
759
+
760
+ ingressRule: { fromAll: true },
761
+ },
762
+ opts,
763
+ )
764
+ }
765
+
766
+ static allowEgressToEndpoint(
767
+ endpoint: InputL34Endpoint,
768
+ namespace: Input<NamespaceLike>,
769
+ cluster: Input<k8s.Cluster>,
770
+ opts?: ResourceOptions,
771
+ ): Output<NetworkPolicy> {
772
+ const parsedEndpoint = parseL34Endpoint(endpoint)
773
+
774
+ return NetworkPolicy.create(
775
+ `allow-egress-to-${l34EndpointToString(parsedEndpoint)}`,
776
+ {
777
+ namespace,
778
+ cluster,
779
+
780
+ description: interpolate`Allow egress traffic to "${l34EndpointToString(parsedEndpoint)}" from the namespace.`,
781
+
782
+ egressRule: { toEndpoint: endpoint },
783
+ },
784
+ opts,
785
+ )
786
+ }
787
+
788
+ static allowIngressFromEndpoint(
789
+ endpoint: InputL34Endpoint,
790
+ namespace: Input<NamespaceLike>,
791
+ cluster: Input<k8s.Cluster>,
792
+ opts?: ResourceOptions,
793
+ ): Output<NetworkPolicy> {
794
+ const parsedEndpoint = parseL34Endpoint(endpoint)
795
+
796
+ return NetworkPolicy.create(
797
+ `allow-ingress-from-${l34EndpointToString(parsedEndpoint)}`,
798
+ {
799
+ namespace,
800
+ cluster,
801
+
802
+ description: interpolate`Allow ingress traffic from "${l34EndpointToString(parsedEndpoint)}" to the namespace.`,
803
+
804
+ ingressRule: { fromEndpoint: endpoint },
805
+ },
806
+ opts,
807
+ )
808
+ }
565
809
  }
566
810
 
567
811
  export class NativeNetworkPolicy extends NetworkPolicy {
@@ -607,56 +851,96 @@ export class NativeNetworkPolicy extends NetworkPolicy {
607
851
  except: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"],
608
852
  }
609
853
 
854
+ private static fallbackDnsRule: types.input.networking.v1.NetworkPolicyEgressRule = {
855
+ to: [
856
+ {
857
+ namespaceSelector: { matchLabels: { "kubernetes.io/metadata.name": "kube-system" } },
858
+ podSelector: { matchLabels: { "k8s-app": "kube-dns" } },
859
+ },
860
+ ],
861
+ ports: [{ port: 53, protocol: "UDP" }],
862
+ }
863
+
610
864
  private static createIngressRules(
611
865
  args: NormalizedNetworkPolicyArgs,
612
866
  ): types.input.networking.v1.NetworkPolicyIngressRule[] {
613
- return args.ingressRules.map(rule => ({
614
- from: rule.all ? undefined : NativeNetworkPolicy.createRulePeers(rule),
615
- ports: NativeNetworkPolicy.mapPorts(rule.ports),
616
- }))
867
+ return uniqueBy(
868
+ args.ingressRules.map(rule => ({
869
+ from: rule.all ? [] : NativeNetworkPolicy.createRulePeers(rule),
870
+ ports: NativeNetworkPolicy.mapPorts(rule.ports),
871
+ })),
872
+ rule => JSON.stringify(rule),
873
+ )
617
874
  }
618
875
 
619
876
  private static createEgressRules(
620
877
  args: NormalizedNetworkPolicyArgs,
621
878
  ): types.input.networking.v1.NetworkPolicyEgressRule[] {
879
+ const extraRules: types.input.networking.v1.NetworkPolicyEgressRule[] = []
880
+
881
+ const needKubeDns = args.egressRules.some(rule => rule.fqdns.length > 0)
882
+ if (needKubeDns) {
883
+ extraRules.push(NativeNetworkPolicy.fallbackDnsRule)
884
+ }
885
+
622
886
  // the native resource does not support FQDNs
623
887
  // to provide compatibility, we need to fallback to all except private CIDRs
624
- const needFallback = args.egressRules.some(rule => rule.fqdns.length > 0)
888
+ const needFallback = args.egressRules.some(rule =>
889
+ rule.fqdns.some(fqdn => !fqdn.endsWith(".cluster.local")),
890
+ )
625
891
  if (needFallback) {
626
- return [{ to: [{ ipBlock: NativeNetworkPolicy.fallbackIpBlock }] }]
892
+ extraRules.push({ to: [{ ipBlock: NativeNetworkPolicy.fallbackIpBlock }] })
627
893
  }
628
894
 
629
- const extraRules: types.input.networking.v1.NetworkPolicyEgressRule[] = []
630
-
895
+ // apply fallback rules for kube-apiserver
631
896
  if (args.allowKubeApiServer) {
632
- const apiServerIp = args.cluster.info.kubeApiServerIp ?? "10.96.0.1"
633
- const apiServerPort = args.cluster.info.kubeApiServerPort ?? 443
897
+ const { quirks, apiEndpoints } = args.cluster
634
898
 
635
- extraRules.push({
636
- to: [{ ipBlock: { cidr: `${apiServerIp}/32` } }],
637
- ports: [{ port: apiServerPort, protocol: "TCP" }],
638
- })
899
+ if (quirks?.fallbackKubeApiAccess) {
900
+ extraRules.push({
901
+ to: [{ ipBlock: { cidr: `${quirks?.fallbackKubeApiAccess.serverIp}/32` } }],
902
+ ports: [{ port: quirks?.fallbackKubeApiAccess.serverPort, protocol: "TCP" }],
903
+ })
904
+ } else {
905
+ const rules = apiEndpoints
906
+ .filter(endpoint => endpoint.type !== "hostname")
907
+ .map(endpoint => ({
908
+ to: [{ ipBlock: { cidr: l3EndpointToCidr(endpoint) } }],
909
+ ports: [{ port: endpoint.port, protocol: "TCP" }],
910
+ }))
911
+
912
+ extraRules.push(...rules)
913
+ }
639
914
  }
640
915
 
641
- return args.egressRules
642
- .map(rule => {
643
- return {
644
- to: rule.all ? undefined : NativeNetworkPolicy.createRulePeers(rule),
645
- ports: NativeNetworkPolicy.mapPorts(rule.ports),
646
- } as types.input.networking.v1.NetworkPolicyEgressRule
647
- })
648
- .concat(extraRules)
916
+ return uniqueBy(
917
+ args.egressRules
918
+ .map(rule => {
919
+ return {
920
+ to: rule.all ? [] : NativeNetworkPolicy.createRulePeers(rule),
921
+ ports: NativeNetworkPolicy.mapPorts(rule.ports),
922
+ } as types.input.networking.v1.NetworkPolicyEgressRule
923
+ })
924
+ .filter(rule => rule.to !== undefined)
925
+ .concat(extraRules),
926
+ rule => JSON.stringify(rule),
927
+ )
649
928
  }
650
929
 
651
930
  private static createRulePeers(
652
931
  this: void,
653
932
  args: NormalizedRuleArgs,
654
- ): types.input.networking.v1.NetworkPolicyPeer[] {
655
- return [
656
- ...NativeNetworkPolicy.createCidrPeers(args),
657
- ...NativeNetworkPolicy.createServicePeers(args),
658
- ...NativeNetworkPolicy.createSelectorPeers(args),
659
- ]
933
+ ): types.input.networking.v1.NetworkPolicyPeer[] | undefined {
934
+ const peers = uniqueBy(
935
+ [
936
+ ...NativeNetworkPolicy.createCidrPeers(args),
937
+ ...NativeNetworkPolicy.createServicePeers(args),
938
+ ...NativeNetworkPolicy.createSelectorPeers(args),
939
+ ],
940
+ peer => JSON.stringify(peer),
941
+ )
942
+
943
+ return peers.length > 0 ? peers : undefined
660
944
  }
661
945
 
662
946
  private static createCidrPeers(
package/src/network.ts ADDED
@@ -0,0 +1,41 @@
1
+ import type { k8s, network } from "@highstate/library"
2
+ import { filterEndpoints } from "@highstate/common"
3
+ import { isFromCluster } from "./service"
4
+
5
+ export function getBestEndpoint(
6
+ endpoints: network.L4Endpoint[],
7
+ cluster?: k8s.Cluster,
8
+ ): network.L4Endpoint | undefined {
9
+ if (!endpoints.length) {
10
+ return undefined
11
+ }
12
+
13
+ if (endpoints.length === 1) {
14
+ return endpoints[0]
15
+ }
16
+
17
+ if (!cluster) {
18
+ return filterEndpoints(endpoints)[0]
19
+ }
20
+
21
+ const clusterEndpoint = endpoints.find(endpoint => isFromCluster(endpoint, cluster))
22
+
23
+ if (clusterEndpoint) {
24
+ return clusterEndpoint
25
+ }
26
+
27
+ return filterEndpoints(endpoints)[0]
28
+ }
29
+
30
+ export function requireBestEndpoint(
31
+ endpoints: network.L4Endpoint[],
32
+ cluster: k8s.Cluster,
33
+ ): network.L4Endpoint {
34
+ const endpoint = getBestEndpoint(endpoints, cluster)
35
+
36
+ if (!endpoint) {
37
+ throw new Error(`No best endpoint found for cluster "${cluster.name}" (${cluster.id})`)
38
+ }
39
+
40
+ return endpoint
41
+ }