-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
expose
service entry for internal cluster traffic (#356)
## Description This adds a service entry to allow traffic to stay inside the cluster and enable things like proper network policies when clients need to access this endpoint. ## Related Issue Fixes #N/A ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [X] Other (security config, docs update, etc) ## Checklist before merging - [X] Test, docs, adr added or updated as needed - [X] [Contributor Guide Steps](https://github.com/defenseunicorns/uds-template-capability/blob/main/CONTRIBUTING.md)(https://github.com/defenseunicorns/uds-template-capability/blob/main/CONTRIBUTING.md#submitting-a-pull-request) followed --------- Co-authored-by: Chance <139784371+UnicornChance@users.noreply.github.com> Co-authored-by: Micah Nagel <micah.nagel@defenseunicorns.com>
- Loading branch information
1 parent
e7cb33e
commit 1bde4cc
Showing
16 changed files
with
600 additions
and
126 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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`, | ||
); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.