Skip to content

Commit a207197

Browse files
authoredJan 26, 2024
feat: add Istio VirtualService Requestmatch to UDS Operator (#129)
1 parent 671f977 commit a207197

13 files changed

+230
-50
lines changed
 

‎package-lock.json

+33-32
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "uds-core",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"description": "A collection of capabilities for UDS Core",
55
"keywords": [
66
"pepr",
@@ -10,7 +10,7 @@
1010
"security"
1111
],
1212
"engines": {
13-
"node": ">=18.0.0"
13+
"node": ">=20.0.0"
1414
},
1515
"pepr": {
1616
"name": "UDS Core",

‎src/pepr/operator/controllers/istio/virtual-service.ts

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { K8s, Log } from "pepr";
22

33
import { UDSConfig } from "../../../config";
4-
import { Gateway, Istio, UDSPackage, getOwnerRef } from "../../crd";
4+
import { Expose, Gateway, Istio, UDSPackage, getOwnerRef } from "../../crd";
55
import { sanitizeResourceName } from "../utils";
66

77
/**
@@ -22,10 +22,9 @@ export async function virtualService(pkg: UDSPackage, namespace: string) {
2222

2323
// Iterate over each exposed service
2424
for (const expose of exposeList) {
25-
const { gateway = Gateway.Tenant, host, port, service } = expose;
25+
const { gateway = Gateway.Tenant, host, port, service, match } = expose;
2626

27-
// Ensure the resource name is valid
28-
const name = sanitizeResourceName(`${pkgName}-${gateway}-${host}`);
27+
const name = generateVSName(pkg, expose);
2928

3029
// For the admin gateway, we need to add the path prefix
3130
const domain = (gateway === Gateway.Admin ? "admin." : "") + UDSConfig.domain;
@@ -34,7 +33,7 @@ export async function virtualService(pkg: UDSPackage, namespace: string) {
3433
const fqdn = `${host}.${domain}`;
3534

3635
// Create the route to the service
37-
const httpRoute: Istio.HTTPRoute[] = [
36+
const route: Istio.HTTPRoute[] = [
3837
{
3938
destination: {
4039
// Use the service name as the host
@@ -62,7 +61,7 @@ export async function virtualService(pkg: UDSPackage, namespace: string) {
6261
// Map the gateway (admin, passthrough or tenant) to the VirtualService
6362
gateways: [`istio-${gateway}-gateway/${gateway}-gateway`],
6463
// Apply the route to the VirtualService
65-
http: [{ route: httpRoute }],
64+
http: [{ route, match }],
6665
},
6766
};
6867

@@ -71,7 +70,7 @@ export async function virtualService(pkg: UDSPackage, namespace: string) {
7170
payload.spec!.tls = [
7271
{
7372
match: [{ port: 443, sniHosts: [fqdn] }],
74-
route: httpRoute,
73+
route,
7574
},
7675
];
7776
}
@@ -104,3 +103,14 @@ export async function virtualService(pkg: UDSPackage, namespace: string) {
104103
// Return the list of generated VirtualServices
105104
return payloads;
106105
}
106+
107+
export function generateVSName(pkg: UDSPackage, expose: Expose) {
108+
const { gateway = Gateway.Tenant, host, port, service, match, description } = expose;
109+
110+
// Ensure the resource name is valid
111+
const matchHash = match?.flatMap(m => m.name).join("-") || "";
112+
const nameSuffix = description || `${host}-${port}-${service}-${matchHash}`;
113+
const name = sanitizeResourceName(`${pkg.metadata!.name}-${gateway}-${nameSuffix}`);
114+
115+
return name;
116+
}

‎src/pepr/operator/crd/generated/package-v1alpha1.ts

+53
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ export enum RemoteGenerated {
8888
}
8989

9090
export interface Expose {
91+
/**
92+
* A description of this expose entry, this will become part of the VirtualService name
93+
*/
94+
description?: string;
9195
/**
9296
* The name of the gateway to expose the service on (default: tenant)
9397
*/
@@ -96,6 +100,11 @@ export interface Expose {
96100
* The hostname to expose the service on
97101
*/
98102
host: string;
103+
/**
104+
* Match the incoming request based on custom rules. Not permitted when using the
105+
* passthrough gateway.
106+
*/
107+
match?: Match[];
99108
/**
100109
* Labels to match pods in the namespace to apply the policy to. Leave empty to apply to all
101110
* pods in the namespace
@@ -126,6 +135,50 @@ export enum Gateway {
126135
Tenant = "tenant",
127136
}
128137

138+
export interface Match {
139+
/**
140+
* Flag to specify whether the URI matching should be case-insensitive.
141+
*/
142+
ignoreUriCase?: boolean;
143+
method?: Method;
144+
/**
145+
* The name assigned to a match.
146+
*/
147+
name: string;
148+
/**
149+
* Query parameters for matching.
150+
*/
151+
queryParams?: { [key: string]: QueryParam };
152+
uri?: URI;
153+
}
154+
155+
export interface Method {
156+
exact?: string;
157+
prefix?: string;
158+
/**
159+
* RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax).
160+
*/
161+
regex?: string;
162+
}
163+
164+
export interface QueryParam {
165+
exact?: string;
166+
prefix?: string;
167+
/**
168+
* RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax).
169+
*/
170+
regex?: string;
171+
}
172+
173+
export interface URI {
174+
exact?: string;
175+
prefix?: string;
176+
/**
177+
* RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax).
178+
*/
179+
regex?: string;
180+
}
181+
129182
export interface Status {
130183
endpoints?: string[];
131184
networkPolicyCount?: number;

‎src/pepr/operator/crd/index.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import { Package as UDSPackage } from "./generated/package-v1alpha1";
55
export {
66
Allow,
77
Direction,
8+
Expose,
89
Gateway,
910
Phase,
11+
RemoteGenerated,
1012
Status,
1113
Package as UDSPackage,
12-
RemoteGenerated,
1314
} from "./generated/package-v1alpha1";
1415

1516
export * as Istio from "./generated/istio/virtualservice-v1beta1";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { V1JSONSchemaProps } from "@kubernetes/client-node";
2+
3+
const matchRequired = [{ required: ["exact"] }, { required: ["prefix"] }, { required: ["regex"] }];
4+
const matchTemplate = {
5+
oneOf: [
6+
{
7+
not: {
8+
anyOf: matchRequired,
9+
},
10+
},
11+
...matchRequired,
12+
],
13+
properties: {
14+
exact: {
15+
type: "string",
16+
},
17+
prefix: {
18+
type: "string",
19+
},
20+
regex: {
21+
description: "RE2 style regex-based match (https://github.com/google/re2/wiki/Syntax).",
22+
type: "string",
23+
},
24+
},
25+
type: "object",
26+
};
27+
28+
export const virtualServiceHttpMatch: V1JSONSchemaProps = {
29+
description:
30+
"Match the incoming request based on custom rules. Not permitted when using the passthrough gateway.",
31+
items: {
32+
properties: {
33+
ignoreUriCase: {
34+
description: "Flag to specify whether the URI matching should be case-insensitive.",
35+
type: "boolean",
36+
},
37+
method: matchTemplate,
38+
name: {
39+
description: "The name assigned to a match.",
40+
type: "string",
41+
},
42+
queryParams: {
43+
additionalProperties: matchTemplate,
44+
description: "Query parameters for matching.",
45+
type: "object",
46+
},
47+
uri: matchTemplate,
48+
},
49+
required: ["name"],
50+
type: "object",
51+
},
52+
type: "array",
53+
};

0 commit comments

Comments
 (0)
Please sign in to comment.