mirror of
https://github.com/biersoeckli/QuickStack.git
synced 2026-02-10 05:29:23 -06:00
feat: implement delete all network policies functionality and added network policies for db tools
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { cleanupOldBuildJobs, cleanupOldTmpFiles, deleteAllFailedAndSuccededPods, deleteOldAppLogs, purgeRegistryImages, updateRegistry } from "../server/actions";
|
||||
import { cleanupOldBuildJobs, cleanupOldTmpFiles, deleteAllFailedAndSuccededPods, deleteAllNetworkPolicies, deleteOldAppLogs, purgeRegistryImages, updateRegistry } from "../server/actions";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Toast } from "@/frontend/utils/toast.utils";
|
||||
import { useConfirmDialog } from "@/frontend/states/zustand.states";
|
||||
import { LogsDialog } from "@/components/custom/logs-overlay";
|
||||
import { Constants } from "@/shared/utils/constants";
|
||||
import { RotateCcw, SquareTerminal, Trash } from "lucide-react";
|
||||
import { RotateCcw, SquareTerminal, Trash, ShieldOff } from "lucide-react";
|
||||
|
||||
export default function QuickStackMaintenanceSettings({
|
||||
qsPodName
|
||||
@@ -97,5 +97,23 @@ export default function QuickStackMaintenanceSettings({
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Network Policies</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="flex gap-4 flex-wrap">
|
||||
|
||||
<Button variant="destructive" onClick={async () => {
|
||||
if (await useConfirm.openConfirmDialog({
|
||||
title: '⚠️ Delete All Network Policies',
|
||||
description: 'WARNING: This is a bad idea! This action will delete ALL network policies across all namespaces. Your applications will lose all network security restrictions. Only use this for troubleshooting or emergency situations. Are you absolutely sure?',
|
||||
okButton: "Yes, Delete All Policies",
|
||||
})) {
|
||||
Toast.fromAction(() => deleteAllNetworkPolicies());
|
||||
}
|
||||
}}><ShieldOff /> Delete All Network Policies</Button>
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>;
|
||||
}
|
||||
@@ -19,6 +19,7 @@ import maintenanceService from "@/server/services/standalone-services/maintenanc
|
||||
import appLogsService from "@/server/services/standalone-services/app-logs.service";
|
||||
import systemBackupService from "@/server/services/standalone-services/system-backup.service";
|
||||
import backupService from "@/server/services/standalone-services/backup.service";
|
||||
import networkPolicyService from "@/server/services/network-policy.service";
|
||||
|
||||
export const updateIngressSettings = async (prevState: any, inputData: QsIngressSettingsModel) =>
|
||||
saveFormAction(inputData, qsIngressSettingsZodModel, async (validatedData) => {
|
||||
@@ -194,4 +195,13 @@ export const runSystemBackupNow = async () =>
|
||||
await backupService.runSystemBackup();
|
||||
|
||||
return new SuccessActionResult(undefined, 'System backup started successfully');
|
||||
});
|
||||
|
||||
export const deleteAllNetworkPolicies = async () =>
|
||||
simpleAction(async () => {
|
||||
await getAdminUserSession();
|
||||
|
||||
const deletedCount = await networkPolicyService.deleteAllNetworkPolicies();
|
||||
|
||||
return new SuccessActionResult(undefined, `Successfully deleted all (${deletedCount}) network policies.`);
|
||||
});
|
||||
@@ -11,6 +11,7 @@ import svcService from "../svc.service";
|
||||
import podService from "../pod.service";
|
||||
import appService from "../app.service";
|
||||
import { AppExtendedModel } from "@/shared/model/app-extended.model";
|
||||
import networkPolicyService from "../network-policy.service";
|
||||
|
||||
export class BaseDbToolService {
|
||||
|
||||
@@ -78,7 +79,7 @@ export class BaseDbToolService {
|
||||
const hostnameDnsProviderHostname = await hostnameDnsProviderService.getDomainForApp(toolAppName);
|
||||
|
||||
console.log(`Creating DB Tool ${toolAppName} deployment for app ${appId}`);
|
||||
await this.createOrUpdateDbGateDeployment(app, deplyomentBuilder);
|
||||
await this.createOrUpdateDbToolDeplyoment(app, deplyomentBuilder);
|
||||
|
||||
console.log(`Creating service for DB Tool ${toolAppName} for app ${appId}`);
|
||||
await svcService.createOrUpdateService(namespace, toolAppName, [{
|
||||
@@ -90,6 +91,9 @@ export class BaseDbToolService {
|
||||
console.log(`Creating ingress for DB Tool ${toolAppName} for app ${appId}`);
|
||||
await this.createOrUpdateIngress(toolAppName, namespace, hostnameDnsProviderHostname);
|
||||
|
||||
console.log(`Creating network policy for DB Tool ${toolAppName} for app ${appId}`);
|
||||
await networkPolicyService.reconcileDbToolNetworkPolicy(toolAppName, appId, namespace);
|
||||
|
||||
const fileBrowserPods = await podService.getPodsForApp(namespace, toolAppName);
|
||||
for (const pod of fileBrowserPods) {
|
||||
await podService.waitUntilPodIsRunningFailedOrSucceded(namespace, pod.podName);
|
||||
@@ -97,7 +101,7 @@ export class BaseDbToolService {
|
||||
}
|
||||
|
||||
|
||||
private async createOrUpdateDbGateDeployment(app: AppExtendedModel, deplyomentBuilder: (app: AppExtendedModel) => V1Deployment | Promise<V1Deployment>) {
|
||||
private async createOrUpdateDbToolDeplyoment(app: AppExtendedModel, deplyomentBuilder: (app: AppExtendedModel) => V1Deployment | Promise<V1Deployment>) {
|
||||
const body = await deplyomentBuilder(app);
|
||||
const toolAppName = this.appIdToToolNameConverter(app.id);
|
||||
await deploymentService.applyDeployment(app.projectId, toolAppName, body);
|
||||
@@ -128,6 +132,8 @@ export class BaseDbToolService {
|
||||
// do not delete ingress to reduce cert-manager issues --> todo; add cleanup function in maintenance section
|
||||
//await k3s.network.deleteNamespacedIngress(KubeObjectNameUtils.getIngressName(toolAppName), projectId);
|
||||
}
|
||||
|
||||
await networkPolicyService.deleteDbToolNetworkPolicy(toolAppName, projectId);
|
||||
}
|
||||
|
||||
private async createOrUpdateIngress(dbGateAppName: string, namespace: string, hostname: string) {
|
||||
|
||||
@@ -108,7 +108,8 @@ class DbGateService extends BaseDbToolService {
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
app: dbGateAppName
|
||||
app: dbGateAppName,
|
||||
[Constants.QS_ANNOTATION_CONTAINER_TYPE]: Constants.QS_ANNOTATION_CONTAINER_TYPE_DB_TOOL
|
||||
},
|
||||
annotations: {
|
||||
[Constants.QS_ANNOTATION_APP_ID]: app.id,
|
||||
|
||||
@@ -62,7 +62,8 @@ class PgAdminService extends BaseDbToolService {
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
app: appName
|
||||
app: appName,
|
||||
[Constants.QS_ANNOTATION_CONTAINER_TYPE]: Constants.QS_ANNOTATION_CONTAINER_TYPE_DB_TOOL
|
||||
},
|
||||
annotations: {
|
||||
[Constants.QS_ANNOTATION_APP_ID]: app.id,
|
||||
|
||||
@@ -45,7 +45,8 @@ class PhpMyAdminService extends BaseDbToolService {
|
||||
template: {
|
||||
metadata: {
|
||||
labels: {
|
||||
app: appName
|
||||
app: appName,
|
||||
[Constants.QS_ANNOTATION_CONTAINER_TYPE]: Constants.QS_ANNOTATION_CONTAINER_TYPE_DB_TOOL
|
||||
},
|
||||
annotations: {
|
||||
[Constants.QS_ANNOTATION_APP_ID]: app.id,
|
||||
|
||||
@@ -45,7 +45,6 @@ class NetworkPolicyService {
|
||||
egress: this.getEgressRules(egressPolicy)
|
||||
}
|
||||
};
|
||||
console.log(JSON.stringify(policy, null, 2));
|
||||
await this.applyNetworkPolicy(namespace, policyName, policy);
|
||||
}
|
||||
|
||||
@@ -93,6 +92,14 @@ class NetworkPolicyService {
|
||||
}
|
||||
}];
|
||||
|
||||
const dbToolPod: V1NetworkPolicyPeer[] = [{
|
||||
podSelector: {
|
||||
matchLabels: {
|
||||
[Constants.QS_ANNOTATION_CONTAINER_TYPE]: Constants.QS_ANNOTATION_CONTAINER_TYPE_DB_TOOL
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
if (policyType === 'ALLOW_ALL') {
|
||||
// Allow from same namespace and from Traefik (internet traffic comes through Traefik)
|
||||
rules.push({
|
||||
@@ -109,7 +116,8 @@ class NetworkPolicyService {
|
||||
rules.push({
|
||||
from: [
|
||||
...traefikFrom,
|
||||
...backupPodFrom
|
||||
...backupPodFrom,
|
||||
...dbToolPod
|
||||
]
|
||||
});
|
||||
} else if (policyType === 'NAMESPACE_ONLY') {
|
||||
@@ -123,7 +131,8 @@ class NetworkPolicyService {
|
||||
// No rules means deny all --> except the separate container for database backups
|
||||
rules.push({
|
||||
from: [
|
||||
...backupPodFrom
|
||||
...backupPodFrom,
|
||||
...dbToolPod
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -240,6 +249,138 @@ class NetworkPolicyService {
|
||||
const allPolicies = await k3s.network.listNamespacedNetworkPolicy(namespace);
|
||||
return allPolicies.body.items.find(np => np.metadata?.name === policyName);
|
||||
}
|
||||
|
||||
async reconcileDbToolNetworkPolicy(dbToolAppName: string, dbAppId: string, projectId: string) {
|
||||
const policyName = KubeObjectNameUtils.toNetworkPolicyName(dbToolAppName);
|
||||
const namespace = projectId;
|
||||
|
||||
const policy: V1NetworkPolicy = {
|
||||
apiVersion: "networking.k8s.io/v1",
|
||||
kind: "NetworkPolicy",
|
||||
metadata: {
|
||||
name: policyName,
|
||||
namespace: namespace,
|
||||
labels: {
|
||||
app: dbToolAppName,
|
||||
'db-tool': 'true'
|
||||
},
|
||||
annotations: {
|
||||
[Constants.QS_ANNOTATION_APP_ID]: dbAppId,
|
||||
[Constants.QS_ANNOTATION_PROJECT_ID]: projectId,
|
||||
}
|
||||
},
|
||||
spec: {
|
||||
podSelector: {
|
||||
matchLabels: {
|
||||
app: dbToolAppName
|
||||
}
|
||||
},
|
||||
policyTypes: ["Ingress", "Egress"],
|
||||
ingress: [
|
||||
{
|
||||
// Allow from Traefik (internet traffic)
|
||||
from: [
|
||||
{
|
||||
namespaceSelector: {
|
||||
matchLabels: {
|
||||
'kubernetes.io/metadata.name': 'kube-system'
|
||||
}
|
||||
},
|
||||
podSelector: {
|
||||
matchLabels: {
|
||||
'app.kubernetes.io/name': 'traefik'
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
egress: [
|
||||
{
|
||||
// Allow DNS
|
||||
to: [
|
||||
{
|
||||
namespaceSelector: {
|
||||
matchLabels: {
|
||||
"kubernetes.io/metadata.name": "kube-system"
|
||||
}
|
||||
},
|
||||
podSelector: {
|
||||
matchLabels: {
|
||||
"k8s-app": "kube-dns"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
namespaceSelector: {
|
||||
matchLabels: {
|
||||
"kubernetes.io/metadata.name": "kube-system"
|
||||
}
|
||||
},
|
||||
podSelector: {
|
||||
matchLabels: {
|
||||
"k8s-app": "coredns"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
ports: [
|
||||
{ protocol: 'UDP', port: 53 as any },
|
||||
{ protocol: 'TCP', port: 53 as any }
|
||||
]
|
||||
},
|
||||
{
|
||||
// Allow only to database pod in same namespace
|
||||
to: [
|
||||
{
|
||||
podSelector: {
|
||||
matchLabels: {
|
||||
app: dbAppId
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
console.log('Creating DB Tool Network Policy:', JSON.stringify(policy, null, 2));
|
||||
await this.applyNetworkPolicy(namespace, policyName, policy);
|
||||
}
|
||||
|
||||
async deleteDbToolNetworkPolicy(dbToolAppName: string, projectId: string) {
|
||||
const policyName = KubeObjectNameUtils.toNetworkPolicyName(dbToolAppName);
|
||||
const existingNetworkPolicy = await this.getExistingNetworkPolicy(projectId, policyName);
|
||||
if (!existingNetworkPolicy) {
|
||||
return;
|
||||
}
|
||||
await k3s.network.deleteNamespacedNetworkPolicy(policyName, projectId);
|
||||
}
|
||||
|
||||
async deleteAllNetworkPolicies() {
|
||||
const namespaces = await k3s.core.listNamespace();
|
||||
let deletedCount = 0;
|
||||
|
||||
for (const ns of namespaces.body.items) {
|
||||
const namespace = ns.metadata?.name;
|
||||
if (!namespace) continue;
|
||||
|
||||
try {
|
||||
const policies = await k3s.network.listNamespacedNetworkPolicy(namespace);
|
||||
for (const policy of policies.body.items) {
|
||||
const policyName = policy.metadata?.name;
|
||||
if (policyName) {
|
||||
await k3s.network.deleteNamespacedNetworkPolicy(policyName, namespace);
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error deleting network policies in namespace ${namespace}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
return deletedCount;
|
||||
}
|
||||
}
|
||||
|
||||
const networkPolicyService = new NetworkPolicyService();
|
||||
|
||||
@@ -82,6 +82,6 @@ export class KubeObjectNameUtils {
|
||||
}
|
||||
|
||||
static toNetworkPolicyName(appId: string): string {
|
||||
return `np-${appId}`;
|
||||
return `np-${appId}`.substring(0, 63); // not more than 63 characters
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ export class Constants {
|
||||
static readonly QS_ANNOTATION_PROJECT_ID = 'qs-project-id';
|
||||
static readonly QS_ANNOTATION_CONTAINER_TYPE = 'qs-containter-type';
|
||||
static readonly QS_ANNOTATION_CONTAINER_TYPE_DB_BACKUP_JOB = 'qs-job-database-backup';
|
||||
static readonly QS_ANNOTATION_CONTAINER_TYPE_DB_TOOL = 'qs-container-db-tool';
|
||||
static readonly QS_ANNOTATION_DEPLOYMENT_ID = 'qs-deplyoment-id';
|
||||
static readonly QS_ANNOTATION_GIT_COMMIT = 'qs-git-commit';
|
||||
static readonly K3S_JOIN_TOKEN = 'k3sJoinToken';
|
||||
|
||||
Reference in New Issue
Block a user