Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add expose service entry for internal cluster traffic #356

Merged
merged 31 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a5a1dd0
feat: add a keycloak service entry for internal cluster traffic
Racer159 Apr 19, 2024
7b21a94
add cap check
Racer159 Apr 19, 2024
91f0c12
Merge branch 'main' into add-keycloak-service-entry
Racer159 Apr 19, 2024
4820555
Merge branch 'main' into add-keycloak-service-entry
UnicornChance Apr 24, 2024
ac90288
Merge branch 'main' into add-keycloak-service-entry
UnicornChance Apr 24, 2024
6711f4e
Merge branch 'main' into add-keycloak-service-entry
Racer159 May 2, 2024
ebe2755
Merge branch 'main' into add-keycloak-service-entry
UnicornChance May 3, 2024
6ffa236
Merge branch 'main' into add-keycloak-service-entry
Racer159 May 6, 2024
fde7b14
this should work :fingers-crossed:
Racer159 May 6, 2024
759b141
remove service entry
Racer159 May 6, 2024
2ebbc86
fix linting
Racer159 May 6, 2024
90705d5
Merge branch 'main' into add-keycloak-service-entry
Racer159 May 6, 2024
948c259
fix plural and names
Racer159 May 7, 2024
67c9cd1
fix dev
Racer159 May 7, 2024
7d89156
remove vs
Racer159 May 7, 2024
21729e0
fix the feedback - refactor the codes
Racer159 May 9, 2024
23c3981
add a comment
Racer159 May 9, 2024
0531a3c
add a comment2
Racer159 May 9, 2024
a8f2838
add tests
Racer159 May 9, 2024
3ff3482
Merge branch 'main' into add-keycloak-service-entry
Racer159 May 9, 2024
10a0c7e
fix lint
Racer159 May 9, 2024
4393b62
fix other pkg name
Racer159 May 9, 2024
01fe692
Merge branch 'main' into add-keycloak-service-entry
Racer159 May 10, 2024
7721df3
update docs
Racer159 May 10, 2024
3a17981
update docs
Racer159 May 10, 2024
1fe059a
Merge branch 'main' into add-keycloak-service-entry
Racer159 May 10, 2024
28fc988
Merge branch 'main' into add-keycloak-service-entry
Racer159 May 10, 2024
482760c
Merge branch 'main' into add-keycloak-service-entry
Racer159 May 13, 2024
f165219
Update src/pepr/operator/controllers/istio/istio-resources.ts
Racer159 May 14, 2024
8712757
Update src/pepr/operator/controllers/istio/istio-resources.ts
Racer159 May 14, 2024
fff964e
Merge branch 'main' into add-keycloak-service-entry
Racer159 May 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,13 @@
"root": true,
"rules": {
"@typescript-eslint/no-floating-promises": ["error"]
}
},
"overrides": [
{
"files": [ "src/pepr/operator/crd/generated/**/*.ts", "src/pepr/operator/crd/generated/*.ts" ],
"rules": {
"@typescript-eslint/no-explicit-any": "off"
}
}
]
}
2 changes: 1 addition & 1 deletion src/keycloak/chart/templates/secret-admin-password.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ apiVersion: v1
kind: Secret
metadata:
name: {{ $secretName }}
namespace: {{ .Release.Namespace }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
type: Opaque
Expand Down
2 changes: 1 addition & 1 deletion src/keycloak/chart/templates/secret-postgresql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ apiVersion: v1
kind: Secret
metadata:
name: {{ include "keycloak.fullname" . }}-postgresql
namespace: {{ .Release.Namespace }}
namespace: {{ .Release.Namespace }}
labels:
{{- include "keycloak.labels" . | nindent 4 }}
type: Opaque
Expand Down
8 changes: 4 additions & 4 deletions src/pepr/operator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ The UDS Operator manages the lifecycle of UDS Package CRs and their correspondin
- establishing default-deny ingress/egress network policies
- creating a layered allow-list based approach on top of the default deny network policies including some basic defaults such as Istio requirements and DNS egress
- providing targeted remote endpoints network policies such as `KubeAPI` and `CloudMetadata` to make policies more DRY and provide dynamic bindings where a static definition is not possible
- creating Istio Virtual Services & related ingress gateway network policies
- creating Istio Virtual Services, Service Entries & related ingress gateway network policies

#### Exemption

Expand All @@ -25,7 +25,7 @@ metadata:
namespace: grafana
spec:
network:
# Expose rules generate Istio VirtualServices and related network policies
# Expose rules generate Istio VirtualServices, ServiceEntries and related network policies
expose:
- service: grafana
selector:
Expand Down Expand Up @@ -196,8 +196,8 @@ graph TD
G -->|Yes| H["Log: Skipping pkg"]
G -->|No| I["Update pkg status to Phase.Pending"]
I --> J{"Check if Istio is installed"}
J -->|Yes| K["Add injection label, process expose CRs for Virtual Services"]
J -->|No| L["Skip Virtual Service Creation"]
J -->|Yes| K["Add injection label, process expose CRs for Istio Resources"]
J -->|No| L["Skip Istio Resource Creation"]
K --> M["Create default network policies in namespace"]
L --> M
M --> N["Process allow CRs for network policies"]
Expand Down
92 changes: 92 additions & 0 deletions src/pepr/operator/controllers/istio/istio-resources.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { K8s, Log } from "pepr";

import { IstioVirtualService, IstioServiceEntry, UDSPackage } from "../../crd";
import { getOwnerRef } from "../utils";
import { generateVirtualService } from "./virtual-service";
import { generateServiceEntry } from "./service-entry";

/**
* Creates a VirtualService and ServiceEntry for each exposed service in the package
*
* @param pkg
* @param namespace
*/
export async function istioResources(pkg: UDSPackage, namespace: string) {
const pkgName = pkg.metadata!.name!;
const generation = (pkg.metadata?.generation ?? 0).toString();
const ownerRefs = getOwnerRef(pkg);

// Get the list of exposed services
const exposeList = pkg.spec?.network?.expose ?? [];

// Create a Set of processed hosts (to maintain uniqueness)
const hosts = new Set<string>();

// Track which ServiceEntries we've created
const serviceEntryNames: Map<string, boolean> = new Map();

// Iterate over each exposed service
for (const expose of exposeList) {
// Generate a VirtualService for this `expose` entry
const vsPayload = generateVirtualService(expose, namespace, pkgName, generation, ownerRefs);

Log.debug(vsPayload, `Applying VirtualService ${vsPayload.metadata?.name}`);

// Apply the VirtualService and force overwrite any existing policy
await K8s(IstioVirtualService).Apply(vsPayload, { force: true });

vsPayload.spec!.hosts!.forEach(h => hosts.add(h));

// Generate a ServiceEntry for this `expose` entry
const sePayload = generateServiceEntry(expose, namespace, pkgName, generation, ownerRefs);

// If we have already made a ServiceEntry with this name, skip (i.e. if advancedHTTP was used)
if (serviceEntryNames.get(sePayload.metadata!.name!)) {
continue;
}

Log.debug(sePayload, `Applying ServiceEntry ${sePayload.metadata?.name}`);

// Apply the ServiceEntry and force overwrite any existing policy
await K8s(IstioServiceEntry).Apply(sePayload, { force: true });

serviceEntryNames.set(sePayload.metadata!.name!, true);
}

// Get all related VirtualServices in the namespace
const virtualServices = await K8s(IstioVirtualService)
.InNamespace(namespace)
.WithLabel("uds/package", pkgName)
.Get();

// Find any orphaned VirtualServices (not matching the current generation)
const orphanedVS = virtualServices.items.filter(
vs => vs.metadata?.labels?.["uds/generation"] !== generation,
);

// Delete any orphaned VirtualServices
for (const vs of orphanedVS) {
Log.debug(vs, `Deleting orphaned VirtualService ${vs.metadata!.name}`);
await K8s(IstioVirtualService).Delete(vs);
}

// Get all related ServiceEntries in the namespace
const serviceEntries = await K8s(IstioServiceEntry)
.InNamespace(namespace)
.WithLabel("uds/package", pkgName)
.Get();

// Find any orphaned ServiceEntries (not matching the current generation)
const orphanedSE = serviceEntries.items.filter(
se => se.metadata?.labels?.["uds/generation"] !== generation,
);

// Delete any orphaned ServiceEntries
for (const se of orphanedSE) {
Log.debug(se, `Deleting orphaned ServiceEntry ${se.metadata!.name}`);
await K8s(IstioServiceEntry).Delete(se);
}

// Return the list of unique hostnames
return [...hosts];
}
53 changes: 53 additions & 0 deletions src/pepr/operator/controllers/istio/service-entry.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { describe, expect, it } from "@jest/globals";
import { UDSConfig } from "../../../config";
import { generateServiceEntry } from "./service-entry";
import { Expose, Gateway, IstioLocation, IstioResolution } from "../../crd";

describe("test generate service entry", () => {
const ownerRefs = [
{
apiVersion: "uds.dev/v1alpha1",
kind: "Package",
name: "test",
uid: "f50120aa-2713-4502-9496-566b102b1174",
},
];

const host = "test";
const port = 8080;
const service = "test-service";

const namespace = "test";
const pkgName = "test";
const generation = "1";

it("should create a simple ServiceEntry object", () => {
const expose: Expose = {
host,
port,
service,
};

const payload = generateServiceEntry(expose, namespace, pkgName, generation, ownerRefs);

expect(payload).toBeDefined();
expect(payload.metadata?.name).toEqual(`${pkgName}-${Gateway.Tenant}-${host}`);
expect(payload.metadata?.namespace).toEqual(namespace);

expect(payload.spec?.hosts).toBeDefined();
expect(payload.spec!.hosts![0]).toEqual(`${host}.${UDSConfig.domain}`);

expect(payload.spec!.location).toEqual(IstioLocation.MeshInternal);
expect(payload.spec!.resolution).toEqual(IstioResolution.DNS);

expect(payload.spec?.ports).toBeDefined();
expect(payload.spec!.ports![0].name).toEqual("https");
expect(payload.spec!.ports![0].number).toEqual(443);
expect(payload.spec!.ports![0].protocol).toEqual("HTTPS");

expect(payload.spec?.endpoints).toBeDefined();
expect(payload.spec!.endpoints![0].address).toEqual(
`${Gateway.Tenant}-ingressgateway.istio-${Gateway.Tenant}-gateway.svc.cluster.local`,
);
});
});
79 changes: 79 additions & 0 deletions src/pepr/operator/controllers/istio/service-entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { UDSConfig } from "../../../config";
import { V1OwnerReference } from "@kubernetes/client-node";
import {
Expose,
Gateway,
IstioServiceEntry,
IstioLocation,
IstioResolution,
IstioPort,
IstioEndpoint,
} from "../../crd";
import { sanitizeResourceName } from "../utils";

/**
* Creates a ServiceEntry for each exposed service in the package
*
* @param pkg
* @param namespace
*/
export function generateServiceEntry(
expose: Expose,
namespace: string,
pkgName: string,
generation: string,
ownerRefs: V1OwnerReference[],
) {
const { gateway = Gateway.Tenant, host } = expose;

const name = generateSEName(pkgName, expose);

// For the admin gateway, we need to add the path prefix
const domain = (gateway === Gateway.Admin ? "admin." : "") + UDSConfig.domain;

// Append the domain to the host
const fqdn = `${host}.${domain}`;

const serviceEntryPort: IstioPort = {
name: "https",
number: 443,
protocol: "HTTPS",
};

const serviceEntryEndpoint: IstioEndpoint = {
// Map the gateway (admin, passthrough or tenant) to the ServiceEntry
address: `${gateway}-ingressgateway.istio-${gateway}-gateway.svc.cluster.local`,
};

const payload: IstioServiceEntry = {
metadata: {
name,
namespace,
labels: {
"uds/package": pkgName,
"uds/generation": generation,
},
// Use the CR as the owner ref for each ServiceEntry
ownerReferences: ownerRefs,
},
spec: {
// Append the UDS Domain to the host
hosts: [fqdn],
location: IstioLocation.MeshInternal,
resolution: IstioResolution.DNS,
ports: [serviceEntryPort],
endpoints: [serviceEntryEndpoint],
},
};

return payload;
}

export function generateSEName(pkgName: string, expose: Expose) {
const { gateway = Gateway.Tenant, host } = expose;

// Ensure the resource name is valid
const name = sanitizeResourceName(`${pkgName}-${gateway}-${host}`);

return name;
}
Loading