diff --git a/backend/go.mod b/backend/go.mod index 2c5a8290c..b8cd0aa1f 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,8 +1,6 @@ module github.com/cilium/hubble-ui/backend -go 1.21.0 - -toolchain go1.21.4 +go 1.21.4 require ( github.com/cilium/cilium v1.15.0-pre.2 diff --git a/src/api/__mocks__/data.ts b/src/api/__mocks__/data.ts index 12ce68521..163c7a25a 100644 --- a/src/api/__mocks__/data.ts +++ b/src/api/__mocks__/data.ts @@ -372,5 +372,7 @@ export const flows: HubbleFlow[] = range(1000).map((): HubbleFlow => { trafficDirection: Math.random() <= 0.5 ? TrafficDirection.Egress : TrafficDirection.Ingress, authType: AuthType.Disbaled, + egressAllowedBy: [], + ingressAllowedBy: [], }; }); diff --git a/src/api/grpc/common.ts b/src/api/grpc/common.ts index 12d5587c7..dba96fc10 100644 --- a/src/api/grpc/common.ts +++ b/src/api/grpc/common.ts @@ -181,8 +181,8 @@ export const filterEntryWhitelistFilters = ( const pod = filter.podNamespace ? `${filter.podNamespace}/${filter.query}` : filters.namespace - ? `${filters.namespace}/${filter.query}` - : null; + ? `${filters.namespace}/${filter.query}` + : null; if (filter.fromRequired) { // NOTE: this makes possible to catch flows [outside of ns] -> [ns] diff --git a/src/components/FlowsTable/Sidebar.tsx b/src/components/FlowsTable/Sidebar.tsx index 2361944de..c4f7ba7d7 100644 --- a/src/components/FlowsTable/Sidebar.tsx +++ b/src/components/FlowsTable/Sidebar.tsx @@ -19,6 +19,7 @@ import { DnsBodyItem, IdentityEntry, PodEntry, + PolicyEntry, } from './SidebarComponents'; import css from './styles.scss'; @@ -460,6 +461,23 @@ export const FlowsTableSidebar = memo(function FlowsTableSidebar(props) { )} +
+ {flow.hasEgressAllowedBy && ( +
+ Egress allowed by policies +
+ +
+
+ )} + {flow.hasIngressAllowedBy && ( +
+ Ingress allowed by policies +
+ +
+
+ )} ); }); diff --git a/src/components/FlowsTable/SidebarComponents.tsx b/src/components/FlowsTable/SidebarComponents.tsx index 102bfa9c9..e5a55e742 100644 --- a/src/components/FlowsTable/SidebarComponents.tsx +++ b/src/components/FlowsTable/SidebarComponents.tsx @@ -252,3 +252,29 @@ export const PodEntry = memo( ); }, ); + +export interface PolicyEntryProps { + allowedBy: string[]; +} + +export const PolicyEntry = memo( + function FlowsTableSidebarLabelsEntry(props) { + return ( +
+ {props.allowedBy.map(policyName => { + return ; + })} +
+ ); + }, +); + +export interface PolicyEntryItemProps { + name: string; +} + +export const PolicyEntryItem = memo( + function FlowsTableSidebarPolicyEntry(props) { + return {props.name}; + }, +); diff --git a/src/components/FlowsTable/styles.scss b/src/components/FlowsTable/styles.scss index d36cb6b60..bc4f8f93d 100644 --- a/src/components/FlowsTable/styles.scss +++ b/src/components/FlowsTable/styles.scss @@ -237,6 +237,12 @@ } } + .policies { + .policy { + display: block; + } + } + .tcpFlags { .tcpFlag { margin-right: 5px; diff --git a/src/components/TopBar/NamespaceSelectorDropdown.tsx b/src/components/TopBar/NamespaceSelectorDropdown.tsx index fcd066142..cfdcc63f4 100644 --- a/src/components/TopBar/NamespaceSelectorDropdown.tsx +++ b/src/components/TopBar/NamespaceSelectorDropdown.tsx @@ -58,8 +58,8 @@ export const NamespaceSelectorDropdown = memo( currentNamespace && namespaces.includes(currentNamespace) ? currentNamespace : currentNamespace - ? `Waiting ${currentNamespace} namespace…` - : 'Choose namespace'; + ? `Waiting ${currentNamespace} namespace…` + : 'Choose namespace'; return ( ' : this.l7?.type === L7FlowType.Response - ? '<-' - : '??'; + ? '<-' + : '??'; // TODO: check if this is a correct fingerprints for all but http return `${direction} ${l7helpers.getEndpointId(l7)}`; @@ -517,4 +517,20 @@ export class Flow { public get authTypeLabel(): string { return authtypeHelpers.toString(this.ref.authType); } + + public get hasEgressAllowedBy(): boolean { + return this.ref.egressAllowedBy.length > 0; + } + + public get hasIngressAllowedBy(): boolean { + return this.ref.ingressAllowedBy.length > 0; + } + + public get egressAllowedBy(): string[] { + return this.ref.egressAllowedBy || []; + } + + public get ingressAllowedBy(): string[] { + return this.ref.ingressAllowedBy || []; + } } diff --git a/src/domain/helpers/flows.ts b/src/domain/helpers/flows.ts index dea058fc9..e24ef7cc9 100644 --- a/src/domain/helpers/flows.ts +++ b/src/domain/helpers/flows.ts @@ -16,6 +16,7 @@ import { TrafficDirection as PBTrafficDirection, CiliumEventType as PBCiliumEventType, Service as PBService, + Policy as Policy, } from '~backend/proto/flow/flow_pb'; import { @@ -47,6 +48,8 @@ import * as misc from '~/domain/misc'; import * as verdictHelpers from './verdict'; import { authTypeFromPb } from './auth-type'; +const derivedFrom = 'reserved:io.cilium.policy.derived-from'; + export const hubbleFlowFromObj = (obj: any): HubbleFlow | null => { obj = obj.flow != null ? obj.flow : obj; @@ -84,6 +87,9 @@ export const hubbleFlowFromObj = (obj: any): HubbleFlow | null => { const trafficDirection = trafficDirectionFromStr(obj.trafficDirection); const authType = authTypeFromStr(obj.authType); + const egressAllowedBy = getNameFromPolicy(obj.getEgressAllowedByList); + const ingressAllowedBy = getNameFromPolicy(obj.getIngressAllowedByList); + return { time, verdict, @@ -105,6 +111,8 @@ export const hubbleFlowFromObj = (obj: any): HubbleFlow | null => { summary: obj.summary, trafficDirection, authType, + egressAllowedBy, + ingressAllowedBy, }; }; @@ -148,6 +156,9 @@ export const hubbleFlowFromPb = (flow: PBFlow): HubbleFlow => { const trafficDirection = trafficDirectionFromPb(flow.getTrafficDirection()); const authType = authTypeFromPb(flow.getAuthType()); + const egressAllowedBy = getNameFromPolicy(flow.getEgressAllowedByList()); + const ingressAllowedBy = getNameFromPolicy(flow.getIngressAllowedByList()); + return { time, verdict, @@ -169,6 +180,8 @@ export const hubbleFlowFromPb = (flow: PBFlow): HubbleFlow => { summary: flow.getSummary(), trafficDirection, authType, + egressAllowedBy: egressAllowedBy, + ingressAllowedBy: ingressAllowedBy, }; }; @@ -512,3 +525,25 @@ export const icmpv4FromPb = (icmp: PBICMPv4): ICMPv4 => { export const icmpv6FromPb = (icmp: PBICMPv6): ICMPv6 => { return icmpv4FromPb(icmp); }; + +export const getNameFromPolicy = (policies: Array): Array => { + return policies.map(policy => { + if (policy.getName() === '') { + const labelMap = new Map(); + policy.getLabelsList().forEach(keyValueString => { + const [key, value] = keyValueString.split('='); + labelMap.set(key, value); + }); + + // Note: We try to automatically derive the policy name if it is set. + // This is set by the policy mapstate for certain known scenarios, like allowing localhost access. + // See: https://github.com/cilium/cilium/blob/614f2ddcc8fe93aeaf463b4535dcc0f1dcc373a3/pkg/policy/mapstate.go#L42-L49 + if (labelMap.has(derivedFrom)) { + return '/' + labelMap.get(derivedFrom); + } + return '/unknown'; + } + + return policy.getNamespace() + '/' + policy.getName(); + }); +}; diff --git a/src/domain/hubble.ts b/src/domain/hubble.ts index 26b259bf5..da32eeff9 100644 --- a/src/domain/hubble.ts +++ b/src/domain/hubble.ts @@ -21,6 +21,8 @@ export interface HubbleFlow { readonly summary: string; readonly trafficDirection: TrafficDirection; readonly authType: AuthType; + readonly egressAllowedBy: Array; + readonly ingressAllowedBy: Array; } export interface HubbleService { diff --git a/src/testing/data/flows.ts b/src/testing/data/flows.ts index e2ebfa44b..b7173490b 100644 --- a/src/testing/data/flows.ts +++ b/src/testing/data/flows.ts @@ -49,6 +49,8 @@ export const icmpv4Flow: HubbleFlow = { }, trafficDirection: TrafficDirection.Ingress, authType: AuthType.Disbaled, + egressAllowedBy: [], + ingressAllowedBy: [], }; export const icmpv6Flow: HubbleFlow = { @@ -96,6 +98,8 @@ export const hubbleOne: HubbleFlow = { }, trafficDirection: TrafficDirection.Ingress, authType: AuthType.Disbaled, + egressAllowedBy: [], + ingressAllowedBy: [], }; export const hubbleNoSourceName: HubbleFlow = { diff --git a/src/testing/helpers.ts b/src/testing/helpers.ts index e43e3ae68..cd2646cdc 100644 --- a/src/testing/helpers.ts +++ b/src/testing/helpers.ts @@ -64,6 +64,8 @@ export const flowsBetweenPods = ( time: nextFlowTimestamp(), trafficDirection: TrafficDirection.Ingress, authType: AuthType.Disbaled, + egressAllowedBy: [], + ingressAllowedBy: [], }; const fromBtoA: HubbleFlow = { @@ -98,6 +100,8 @@ export const flowsBetweenPods = ( time: nextFlowTimestamp(), trafficDirection: TrafficDirection.Ingress, authType: AuthType.Disbaled, + egressAllowedBy: [], + ingressAllowedBy: [], }; return { fromAtoB, fromBtoA }; @@ -139,6 +143,8 @@ export const flowsBetweenServices = ( time: nextFlowTimestamp(), trafficDirection: TrafficDirection.Ingress, authType: AuthType.Disbaled, + egressAllowedBy: [], + ingressAllowedBy: [], }; const fromBtoA: HubbleFlow = { @@ -173,6 +179,8 @@ export const flowsBetweenServices = ( time: nextFlowTimestamp(), trafficDirection: TrafficDirection.Ingress, authType: AuthType.Disbaled, + egressAllowedBy: [], + ingressAllowedBy: [], }; return { fromAtoB, fromBtoA }; @@ -221,6 +229,8 @@ export const flowsFromToService = (from: HubbleService, to: HubbleService) => { time: nextFlowTimestamp(), trafficDirection: TrafficDirection.Ingress, authType: AuthType.Disbaled, + egressAllowedBy: [], + ingressAllowedBy: [], }; const fromBtoA: HubbleFlow = { @@ -253,6 +263,8 @@ export const flowsFromToService = (from: HubbleService, to: HubbleService) => { time: nextFlowTimestamp(), trafficDirection: TrafficDirection.Ingress, authType: AuthType.Disbaled, + egressAllowedBy: [], + ingressAllowedBy: [], }; return { fromAtoB, fromBtoA }; diff --git a/src/utils/types.ts b/src/utils/types.ts index cf8ef756c..e69fe43be 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -6,91 +6,105 @@ export type Parameter = N extends 0 ? P : never : N extends 1 - ? F extends (a: any, b: infer P, ...args: any) => any - ? P - : never - : N extends 2 - ? F extends (a: any, b: any, c: infer P, ...args: any) => any - ? P - : never - : N extends 3 - ? F extends (a: any, b: any, c: any, d: infer P, ...args: any) => any - ? P - : never - : N extends 4 - ? F extends (a: any, b: any, c: any, d: any, e: infer P, ...args: any) => any - ? P - : never - : N extends 5 - ? F extends ( - a: any, - b: any, - c: any, - d: any, - e: any, - f: infer P, - ...args: any - ) => any - ? P - : never - : N extends 6 - ? F extends ( - a: any, - b: any, - c: any, - d: any, - e: any, - f: any, - g: infer P, - ...args: any - ) => any - ? P - : never - : never; + ? F extends (a: any, b: infer P, ...args: any) => any + ? P + : never + : N extends 2 + ? F extends (a: any, b: any, c: infer P, ...args: any) => any + ? P + : never + : N extends 3 + ? F extends (a: any, b: any, c: any, d: infer P, ...args: any) => any + ? P + : never + : N extends 4 + ? F extends ( + a: any, + b: any, + c: any, + d: any, + e: infer P, + ...args: any + ) => any + ? P + : never + : N extends 5 + ? F extends ( + a: any, + b: any, + c: any, + d: any, + e: any, + f: infer P, + ...args: any + ) => any + ? P + : never + : N extends 6 + ? F extends ( + a: any, + b: any, + c: any, + d: any, + e: any, + f: any, + g: infer P, + ...args: any + ) => any + ? P + : never + : never; export type ParametersAfter = N extends 0 ? F extends (a: any, ...args: infer P) => any ? P : never : N extends 1 - ? F extends (a: any, b: any, ...args: infer P) => any - ? P - : never - : N extends 2 - ? F extends (a: any, b: any, c: any, ...args: infer P) => any - ? P - : never - : N extends 3 - ? F extends (a: any, b: any, c: any, d: any, ...args: infer P) => any - ? P - : never - : N extends 4 - ? F extends (a: any, b: any, c: any, d: any, e: any, ...args: infer P) => any - ? P - : never - : N extends 5 - ? F extends ( - a: any, - b: any, - c: any, - d: any, - e: any, - f: any, - ...args: infer P - ) => any - ? P - : never - : N extends 6 - ? F extends ( - a: any, - b: any, - c: any, - d: any, - e: any, - f: any, - g: any, - ...args: infer P - ) => any - ? P - : never - : never; + ? F extends (a: any, b: any, ...args: infer P) => any + ? P + : never + : N extends 2 + ? F extends (a: any, b: any, c: any, ...args: infer P) => any + ? P + : never + : N extends 3 + ? F extends (a: any, b: any, c: any, d: any, ...args: infer P) => any + ? P + : never + : N extends 4 + ? F extends ( + a: any, + b: any, + c: any, + d: any, + e: any, + ...args: infer P + ) => any + ? P + : never + : N extends 5 + ? F extends ( + a: any, + b: any, + c: any, + d: any, + e: any, + f: any, + ...args: infer P + ) => any + ? P + : never + : N extends 6 + ? F extends ( + a: any, + b: any, + c: any, + d: any, + e: any, + f: any, + g: any, + ...args: infer P + ) => any + ? P + : never + : never;