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 finalizer for UDS Package CRs #953

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
3 changes: 2 additions & 1 deletion src/pepr/operator/controllers/istio/injection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ async function killPods(ns: string, enableInjection: boolean) {
}

for (const pod of group) {
log.info(`Deleting pod ${ns}/${pod.metadata?.name} to enable the istio sidecar`);
const action = enableInjection ? "enable" : "remove";
log.info(`Deleting pod ${ns}/${pod.metadata?.name} to ${action} the istio sidecar`);
await K8s(kind.Pod).Delete(pod);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/pepr/operator/controllers/keycloak/client-sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export async function purgeSSOClients(pkg: UDSPackage, newClients: string[] = []
const token = Store.getItem(storeKey);
if (token) {
await apiCall({ clientId: ref }, "DELETE", token);
Store.removeItem(storeKey);
rjferguson21 marked this conversation as resolved.
Show resolved Hide resolved
await Store.removeItemAndWait(storeKey);
} else {
log.warn(pkg.metadata, `Failed to remove client ${ref}, token not found`);
}
Expand Down
2 changes: 0 additions & 2 deletions src/pepr/operator/crd/generated/exemption-v1alpha1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
*/

// This file is auto-generated by kubernetes-fluent-client, do not edit manually

import { GenericKind, RegisterKind } from "kubernetes-fluent-client";

export class Exemption extends GenericKind {
spec?: Spec;
}
Expand Down
11 changes: 5 additions & 6 deletions src/pepr/operator/crd/generated/package-v1alpha1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
*/

// This file is auto-generated by kubernetes-fluent-client, do not edit manually

import { GenericKind, RegisterKind } from "kubernetes-fluent-client";

export class Package extends GenericKind {
spec?: Spec;
status?: Status;
Expand Down Expand Up @@ -635,14 +633,14 @@ export interface Sso {
* A template for the generated secret
*/
secretTemplate?: { [key: string]: string };
/**
* Enables the standard OpenID Connect redirect based authentication with authorization code.
*/
standardFlowEnabled?: boolean;
/**
* Enables the client credentials grant based authentication via OpenID Connect protocol.
*/
serviceAccountsEnabled?: boolean;
/**
* Enables the standard OpenID Connect redirect based authentication with authorization code.
*/
standardFlowEnabled?: boolean;
/**
* Allowed CORS origins. To permit all origins of Valid Redirect URIs, add '+'. This does
* not include the '*' wildcard though. To permit all origins, explicitly add '*'.
Expand Down Expand Up @@ -716,6 +714,7 @@ export enum Phase {
Failed = "Failed",
Pending = "Pending",
Ready = "Ready",
Removing = "Removing",
Retrying = "Retrying",
}

Expand Down
2 changes: 1 addition & 1 deletion src/pepr/operator/crd/sources/package/v1alpha1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@ export const v1alpha1: V1CustomResourceDefinitionVersion = {
type: "integer",
},
phase: {
enum: ["Pending", "Ready", "Failed", "Retrying"],
enum: ["Pending", "Ready", "Failed", "Retrying", "Removing"],
type: "string",
},
ssoClients: {
Expand Down
21 changes: 4 additions & 17 deletions src/pepr/operator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ import { a } from "pepr";
import { When } from "./common";

// Controller imports
import { cleanupNamespace } from "./controllers/istio/injection";
import { purgeSSOClients } from "./controllers/keycloak/client-sync";
import {
initAPIServerCIDR,
updateAPIServerCIDRFromEndpointSlice,
Expand All @@ -23,9 +21,8 @@ import { validator } from "./crd/validators/package-validator";
// Reconciler imports
import { UDSConfig } from "../config";
import { Component, setupLogger } from "../logger";
import { purgeAuthserviceClients } from "./controllers/keycloak/authservice/authservice";
import { exemptValidator } from "./crd/validators/exempt-validator";
import { packageReconciler } from "./reconcilers/package-reconciler";
import { packageFinalizer, packageReconciler } from "./reconcilers/package-reconciler";

// Export the operator capability for registration in the root pepr.ts
export { operator } from "./common";
Expand All @@ -50,25 +47,15 @@ When(a.Service)
.WithName("kubernetes")
.Reconcile(updateAPIServerCIDRFromService);

// Watch for changes to the UDSPackage CRD and cleanup the namespace mutations
When(UDSPackage)
.IsDeleted()
.Watch(async pkg => {
// Cleanup the namespace
await cleanupNamespace(pkg);

// Remove any SSO clients
await purgeSSOClients(pkg, []);
await purgeAuthserviceClients(pkg, []);
});

// Watch for changes to the UDSPackage CRD to enqueue a package for processing
When(UDSPackage)
.IsCreatedOrUpdated()
// Advanced CR validation
.Validate(validator)
// Enqueue the package for processing
.Reconcile(packageReconciler);
.Reconcile(packageReconciler)
// Handle finalizer (deletions) for the package
.Finalize(packageFinalizer);

// Watch for Exemptions and validate
When(UDSExemption).IsCreatedOrUpdated().Validate(exemptValidator);
Expand Down
10 changes: 9 additions & 1 deletion src/pepr/operator/reconcilers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ const log = setupLogger(Component.OPERATOR_RECONCILERS);
* Checks if the CRD is pending or the current generation has been processed
*
* @param cr The custom resource to check
* @returns true if the CRD is pending or the current generation has been processed
* @returns true if the CRD is removing, pending, or the current generation has already been processed
*/
export function shouldSkip(cr: UDSPackage) {
const isRetrying = cr.status?.phase === Phase.Retrying;
const isPending = cr.status?.phase === Phase.Pending;
// Check for status removing OR a deletion timestamp present
const isRemoving = cr.status?.phase === Phase.Removing || cr.metadata?.deletionTimestamp;
const isCurrentGeneration = cr.metadata?.generation === cr.status?.observedGeneration;

// First check if the CR has been seen before and return false if it has not
Expand All @@ -38,6 +40,12 @@ export function shouldSkip(cr: UDSPackage) {
return false;
}

// If the CR is removing, it should be skipped
if (isRemoving) {
log.debug(cr, `Should skip? Yes, removing`);
return true;
}

// This is the second time the CR has been seen, so check if it is pending or the current generation
if (isPending || isCurrentGeneration) {
log.trace(cr, `Should skip? Yes, pending or current generation and not first time seen`);
Expand Down
57 changes: 54 additions & 3 deletions src/pepr/operator/reconcilers/package-reconciler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
import { handleFailure, shouldSkip, updateStatus, writeEvent } from ".";
import { UDSConfig } from "../../config";
import { Component, setupLogger } from "../../logger";
import { enableInjection } from "../controllers/istio/injection";
import { cleanupNamespace, enableInjection } from "../controllers/istio/injection";
import { istioResources } from "../controllers/istio/istio-resources";
import { authservice } from "../controllers/keycloak/authservice/authservice";
import { keycloak } from "../controllers/keycloak/client-sync";
import {
authservice,
purgeAuthserviceClients,
} from "../controllers/keycloak/authservice/authservice";
import { keycloak, purgeSSOClients } from "../controllers/keycloak/client-sync";
import { Client } from "../controllers/keycloak/types";
import { podMonitor } from "../controllers/monitoring/pod-monitor";
import { serviceMonitor } from "../controllers/monitoring/service-monitor";
Expand Down Expand Up @@ -107,3 +110,51 @@ export async function packageReconciler(pkg: UDSPackage) {
void handleFailure(err, pkg);
}
}

/**
* The finalizer is called when an update with a deletion timestamp happens.
* On completion the finalizer is removed from the Package CR.
* This function removes any SSO/Authservice clients and ensures that Istio Injection is restored to the original state.
*
* @param pkg the package to finalize
*/
export async function packageFinalizer(pkg: UDSPackage) {
log.debug(`Processing removal of package ${pkg.metadata?.namespace}/${pkg.metadata?.name}`);

// In order to avoid triggering a second call of this finalizer, we just write events for each removal piece
// This could be switched to updateStatus once https://github.com/defenseunicorns/pepr/issues/1316 is resolved
// await updateStatus(pkg, { phase: Phase.Removing });

try {
await writeEvent(pkg, {
message: `Restoring original istio injection status on namespace`,
reason: "RemovalInProgress",
type: "Normal",
});
// Cleanup the namespace
await cleanupNamespace(pkg);
} catch (e) {
await writeEvent(pkg, {
message: `Restoration of istio injection status failed, ${e.message}`,
reason: "RemovalFailed",
});
}
mjnagel marked this conversation as resolved.
Show resolved Hide resolved

try {
await writeEvent(pkg, {
message: `Removing SSO / AuthService clients for package`,
reason: "RemovalInProgress",
type: "Normal",
});
// Remove any SSO clients
await purgeSSOClients(pkg, []);
await purgeAuthserviceClients(pkg, []);
} catch (e) {
await writeEvent(pkg, {
message: `Removal of SSO / AuthService clients failed, ${e.message}`,
reason: "RemovalFailed",
});
}

log.debug(`Package ${pkg.metadata?.namespace}/${pkg.metadata?.name} removed`);
}
14 changes: 14 additions & 0 deletions src/pepr/tasks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ tasks:

- cmd: "npx kubernetes-fluent-client crd exemptions.uds.dev src/pepr/operator/crd/generated"

- description: "Add license headers to generated CRD files"
shell:
darwin: bash
linux: bash
cmd: |
# check for addlicense bin
if [ -x "$HOME/go/bin/addlicense" ]; then
echo "addlicense installed in $HOME/go/bin"
else
echo "Error: addlicense is not installed in $HOME/go/bin" >&2
exit 1
fi
$HOME/go/bin/addlicense -l "AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial" -s=only -v -c "Defense Unicorns" src/pepr/operator/crd/generated

- task: gen-docs

- cmd: "npx pepr format"
Expand Down
Loading