Repository: cdk8s-team/cdk8s-plus
Pull request #1177 (2022-09-20 22:56:12)

feat(pod): isolate pod of all network connections
Kubernetes has [functionality](https://kubernetes.io/docs/concepts/services-networking/network-policies/#default-deny-all-ingress-and-all-egress-traffic) to set the default behavior of network policy to disable all egress and ingress connections of pods. Updating our default behavior in this PR. 

Resolves https://github.com/cdk8s-team/cdk8s-plus/issues/891

vinayak-kukreja: **Question**

I was initially trying to do this with the help of [Namespaces](https://github.com/cdk8s-team/cdk8s-plus/blob/k8s-24/main/src/namespace.ts#L45). But I see we do not create this resource explicitly in our workflow and just use the [string](https://github.com/cdk8s-team/cdk8s-core/blob/2.x/src/metadata.ts#L53) instead. Having the default network policy initialized when a namespace was created would help to have this policy work for each pod within that namespace. 

The problem with doing this currently is,
* If we have multiple resources(like deployment, job, etc) using the same name for the namespace. Then if we add logic to create a namespace in each of these resources, it would duplicate namespace in the manifest, due to multiple instances of it. I am not sure if there is a single place where the other resources can keep track of an already created namespace.
* I did not find any place where we can create a default network policy for `default` namespace. 

The solution I could think of was to create a Namespace instance and pass it to the resources instead of the string in metadata. Let me know what you think? 
ryparker: FYI there's now a v25 branch, I'd consider changing the target to v25 and adding the `backport-to-k8s-24/main` label.

Patch:
diff --git a/src/cron-job.ts b/src/cron-job.ts
index ae69650b..8ddca09c 100644
--- a/src/cron-job.ts
+++ b/src/cron-job.ts
@@ -166,6 +166,10 @@ export class CronJob extends workload.Workload {
       throw new Error('The \'ttlAfterFinished\' property cannot be set if \'successfulJobsRetained\' property or \'failedJobsRetained\' property is set. This would cause the retention of jobs to not work properly since it would delete the job based on its value.');
     }
 
+    if (this.isolate) {
+      this.connections.isolate();
+    }
+
     this.schedule = props.schedule;
     this.timeZone = props.timeZone;
     this.concurrencyPolicy = props.concurrencyPolicy ?? ConcurrencyPolicy.FORBID;
diff --git a/src/daemon-set.ts b/src/daemon-set.ts
index b010d15d..c62294b2 100644
--- a/src/daemon-set.ts
+++ b/src/daemon-set.ts
@@ -55,6 +55,9 @@ export class DaemonSet extends workload.Workload {
 
     this.minReadySeconds = props.minReadySeconds ?? 0;
 
+    if (this.isolate) {
+      this.connections.isolate();
+    }
   }
 
   /**
diff --git a/src/deployment.ts b/src/deployment.ts
index 75b3838e..730018b2 100644
--- a/src/deployment.ts
+++ b/src/deployment.ts
@@ -158,6 +158,10 @@ export class Deployment extends workload.Workload {
 
     this.replicas = props.replicas ?? 2;
     this.strategy = props.strategy ?? DeploymentStrategy.rollingUpdate();
+
+    if (this.isolate) {
+      this.connections.isolate();
+    }
   }
 
   /**
diff --git a/src/job.ts b/src/job.ts
index 4a5cc933..a5a31e70 100644
--- a/src/job.ts
+++ b/src/job.ts
@@ -85,6 +85,9 @@ export class Job extends workload.Workload {
     this.backoffLimit = props.backoffLimit;
     this.ttlAfterFinished = props.ttlAfterFinished;
 
+    if (this.isolate) {
+      this.connections.isolate();
+    }
   }
 
   /**
diff --git a/src/pod.ts b/src/pod.ts
index 635dd743..21db0f3a 100644
--- a/src/pod.ts
+++ b/src/pod.ts
@@ -20,6 +20,8 @@ export abstract class AbstractPod extends base.Resource implements IPodSelector,
   public readonly dockerRegistryAuth?: secret.DockerConfigSecret;
   public readonly automountServiceAccountToken: boolean;
 
+  protected readonly isolate: boolean;
+
   private readonly _containers: container.Container[] = [];
   private readonly _initContainers: container.Container[] = [];
   private readonly _hostAliases: HostAlias[] = [];
@@ -36,6 +38,7 @@ export abstract class AbstractPod extends base.Resource implements IPodSelector,
     this.dns = new PodDns(props.dns);
     this.dockerRegistryAuth = props.dockerRegistryAuth;
     this.automountServiceAccountToken = props.automountServiceAccountToken ?? false;
+    this.isolate = props.isolate ?? false;
 
     if (props.containers) {
       props.containers.forEach(c => this.addContainer(c));
@@ -413,6 +416,13 @@ export interface AbstractPodProps extends base.ResourceProps {
    */
   readonly automountServiceAccountToken?: boolean;
 
+  /**
+   * Isolates the pod. This will prevent any ingress or egress connections to / from this pod.
+   * You can however allow explicit connections post instantiation by using the `.connections` property.
+   *
+   * @default false
+   */
+  readonly isolate?: boolean;
 }
 
 /**
@@ -528,6 +538,10 @@ export class Pod extends AbstractPod {
 
     this.scheduling = new PodScheduling(this);
     this.connections = new PodConnections(this);
+
+    if (this.isolate) {
+      this.connections.isolate();
+    }
   }
 
   public get podMetadata(): ApiObjectMetadataDefinition {
@@ -539,6 +553,7 @@ export class Pod extends AbstractPod {
    */
   public _toKube(): k8s.PodSpec {
     const scheduling = this.scheduling._toKube();
+
     return {
       ...this._toPodSpec(),
       affinity: scheduling.affinity,
@@ -1753,4 +1768,22 @@ export class PodConnections {
   private extractPorts(selector?: networkpolicy.INetworkPolicyPeer): networkpolicy.NetworkPolicyPort[] {
     return container.extractContainerPorts(selector).map(n => networkpolicy.NetworkPolicyPort.tcp(n.number));
   }
+
+  /**
+   * Sets the default network policy for Pod/Workload to have all egress and ingress connections as disabled
+   */
+  public isolate() {
+    new networkpolicy.NetworkPolicy(this.instance, 'DefaultDenyAll', {
+      selector: this.instance,
+      // the policy must be defined in the namespace of the pod
+      // so it can select it.
+      metadata: { namespace: this.instance.metadata.namespace },
+      egress: {
+        default: networkpolicy.NetworkPolicyTrafficDefault.DENY,
+      },
+      ingress: {
+        default: networkpolicy.NetworkPolicyTrafficDefault.DENY,
+      },
+    });
+  }
 }
diff --git a/src/stateful-set.ts b/src/stateful-set.ts
index efc04779..fd423b35 100644
--- a/src/stateful-set.ts
+++ b/src/stateful-set.ts
@@ -137,6 +137,10 @@ export class StatefulSet extends workload.Workload {
     this.minReady = props.minReady ?? Duration.seconds(0);
 
     this._service.select(this);
+
+    if (this.isolate) {
+      this.connections.isolate();
+    }
   }
 
   /**
diff --git a/src/workload.ts b/src/workload.ts
index a6eff5b7..e96db5bc 100644
--- a/src/workload.ts
+++ b/src/workload.ts
@@ -149,6 +149,7 @@ export abstract class Workload extends pod.AbstractPod {
     };
 
     const scheduling = this.scheduling._toKube();
+
     return {
       ...super._toPodSpec(),
       affinity: scheduling.affinity,


Test patch:
diff --git a/test/__snapshots__/cron-job.test.ts.snap b/test/__snapshots__/cron-job.test.ts.snap
index 07943571..01fd46f5 100644
--- a/test/__snapshots__/cron-job.test.ts.snap
+++ b/test/__snapshots__/cron-job.test.ts.snap
@@ -1,5 +1,89 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`Can be isolated 1`] = `
+Array [
+  Object {
+    "apiVersion": "batch/v1",
+    "kind": "CronJob",
+    "metadata": Object {
+      "name": "test-cronjob-c812e113",
+    },
+    "spec": Object {
+      "concurrencyPolicy": "Forbid",
+      "failedJobsHistoryLimit": 1,
+      "jobTemplate": Object {
+        "spec": Object {
+          "template": Object {
+            "metadata": Object {
+              "labels": Object {
+                "cdk8s.io/metadata.addr": "test-CronJob-c80e4ce0",
+              },
+            },
+            "spec": Object {
+              "automountServiceAccountToken": false,
+              "containers": Array [
+                Object {
+                  "image": "foobar",
+                  "imagePullPolicy": "Always",
+                  "name": "main",
+                  "resources": Object {
+                    "limits": Object {
+                      "cpu": "1500m",
+                      "memory": "2048Mi",
+                    },
+                    "requests": Object {
+                      "cpu": "1000m",
+                      "memory": "512Mi",
+                    },
+                  },
+                  "securityContext": Object {
+                    "allowPrivilegeEscalation": false,
+                    "privileged": false,
+                    "readOnlyRootFilesystem": true,
+                    "runAsGroup": 26000,
+                    "runAsNonRoot": true,
+                    "runAsUser": 25000,
+                  },
+                },
+              ],
+              "dnsPolicy": "ClusterFirst",
+              "restartPolicy": "Never",
+              "securityContext": Object {
+                "fsGroupChangePolicy": "Always",
+                "runAsNonRoot": true,
+              },
+              "setHostnameAsFQDN": false,
+            },
+          },
+        },
+      },
+      "schedule": "0 0 * * *",
+      "startingDeadlineSeconds": 10,
+      "successfulJobsHistoryLimit": 3,
+      "suspend": false,
+    },
+  },
+  Object {
+    "apiVersion": "networking.k8s.io/v1",
+    "kind": "NetworkPolicy",
+    "metadata": Object {
+      "name": "test-cronjob-defaultdenyall-c81b49ed",
+    },
+    "spec": Object {
+      "podSelector": Object {
+        "matchLabels": Object {
+          "cdk8s.io/metadata.addr": "test-CronJob-c80e4ce0",
+        },
+      },
+      "policyTypes": Array [
+        "Egress",
+        "Ingress",
+      ],
+    },
+  },
+]
+`;
+
 exports[`custom configuration 1`] = `
 Array [
   Object {
diff --git a/test/__snapshots__/daemon-set.test.ts.snap b/test/__snapshots__/daemon-set.test.ts.snap
index 015d560f..b8b6db91 100644
--- a/test/__snapshots__/daemon-set.test.ts.snap
+++ b/test/__snapshots__/daemon-set.test.ts.snap
@@ -1,5 +1,85 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`Can be isolated 1`] = `
+Array [
+  Object {
+    "apiVersion": "apps/v1",
+    "kind": "DaemonSet",
+    "metadata": Object {
+      "name": "test-daemonset-c8482ea2",
+    },
+    "spec": Object {
+      "minReadySeconds": 0,
+      "selector": Object {
+        "matchLabels": Object {
+          "cdk8s.io/metadata.addr": "test-DaemonSet-c8f77186",
+        },
+      },
+      "template": Object {
+        "metadata": Object {
+          "labels": Object {
+            "cdk8s.io/metadata.addr": "test-DaemonSet-c8f77186",
+          },
+        },
+        "spec": Object {
+          "automountServiceAccountToken": false,
+          "containers": Array [
+            Object {
+              "image": "foobar",
+              "imagePullPolicy": "Always",
+              "name": "main",
+              "resources": Object {
+                "limits": Object {
+                  "cpu": "1500m",
+                  "memory": "2048Mi",
+                },
+                "requests": Object {
+                  "cpu": "1000m",
+                  "memory": "512Mi",
+                },
+              },
+              "securityContext": Object {
+                "allowPrivilegeEscalation": false,
+                "privileged": false,
+                "readOnlyRootFilesystem": true,
+                "runAsGroup": 26000,
+                "runAsNonRoot": true,
+                "runAsUser": 25000,
+              },
+            },
+          ],
+          "dnsPolicy": "ClusterFirst",
+          "restartPolicy": "Always",
+          "securityContext": Object {
+            "fsGroupChangePolicy": "Always",
+            "runAsNonRoot": true,
+          },
+          "setHostnameAsFQDN": false,
+        },
+      },
+    },
+  },
+  Object {
+    "apiVersion": "networking.k8s.io/v1",
+    "kind": "NetworkPolicy",
+    "metadata": Object {
+      "name": "test-daemonset-defaultdenyall-c826616a",
+    },
+    "spec": Object {
+      "podSelector": Object {
+        "matchLabels": Object {
+          "cdk8s.io/metadata.addr": "test-DaemonSet-c8f77186",
+        },
+      },
+      "policyTypes": Array [
+        "Egress",
+        "Ingress",
+      ],
+    },
+  },
+]
+`;
+
 exports[`custom 1`] = `
 Array [
   Object {
diff --git a/test/__snapshots__/deployment.test.ts.snap b/test/__snapshots__/deployment.test.ts.snap
index 20e5e40e..66876fb5 100644
--- a/test/__snapshots__/deployment.test.ts.snap
+++ b/test/__snapshots__/deployment.test.ts.snap
@@ -131,6 +131,95 @@ Array [
 ]
 `;
 
+exports[`Can be isolated 1`] = `
+Array [
+  Object {
+    "apiVersion": "apps/v1",
+    "kind": "Deployment",
+    "metadata": Object {
+      "name": "test-deployment-c898c72d",
+    },
+    "spec": Object {
+      "minReadySeconds": 0,
+      "progressDeadlineSeconds": 600,
+      "replicas": 5,
+      "selector": Object {
+        "matchLabels": Object {
+          "cdk8s.io/metadata.addr": "test-Deployment-c83f5e59",
+        },
+      },
+      "strategy": Object {
+        "rollingUpdate": Object {
+          "maxSurge": "25%",
+          "maxUnavailable": "25%",
+        },
+        "type": "RollingUpdate",
+      },
+      "template": Object {
+        "metadata": Object {
+          "labels": Object {
+            "cdk8s.io/metadata.addr": "test-Deployment-c83f5e59",
+          },
+        },
+        "spec": Object {
+          "automountServiceAccountToken": false,
+          "containers": Array [
+            Object {
+              "image": "foobar",
+              "imagePullPolicy": "Always",
+              "name": "main",
+              "resources": Object {
+                "limits": Object {
+                  "cpu": "1500m",
+                  "memory": "2048Mi",
+                },
+                "requests": Object {
+                  "cpu": "1000m",
+                  "memory": "512Mi",
+                },
+              },
+              "securityContext": Object {
+                "allowPrivilegeEscalation": false,
+                "privileged": false,
+                "readOnlyRootFilesystem": true,
+                "runAsGroup": 26000,
+                "runAsNonRoot": true,
+                "runAsUser": 25000,
+              },
+            },
+          ],
+          "dnsPolicy": "ClusterFirst",
+          "restartPolicy": "Always",
+          "securityContext": Object {
+            "fsGroupChangePolicy": "Always",
+            "runAsNonRoot": true,
+          },
+          "setHostnameAsFQDN": false,
+        },
+      },
+    },
+  },
+  Object {
+    "apiVersion": "networking.k8s.io/v1",
+    "kind": "NetworkPolicy",
+    "metadata": Object {
+      "name": "test-deployment-defaultdenyall-c8a461c9",
+    },
+    "spec": Object {
+      "podSelector": Object {
+        "matchLabels": Object {
+          "cdk8s.io/metadata.addr": "test-Deployment-c83f5e59",
+        },
+      },
+      "policyTypes": Array [
+        "Egress",
+        "Ingress",
+      ],
+    },
+  },
+]
+`;
+
 exports[`expose captures all container ports 1`] = `
 Array [
   Object {
diff --git a/test/__snapshots__/job.test.ts.snap b/test/__snapshots__/job.test.ts.snap
new file mode 100644
index 00000000..b1d9d422
--- /dev/null
+++ b/test/__snapshots__/job.test.ts.snap
@@ -0,0 +1,75 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Can be isolated 1`] = `
+Array [
+  Object {
+    "apiVersion": "batch/v1",
+    "kind": "Job",
+    "metadata": Object {
+      "name": "test-job-c8ab3340",
+    },
+    "spec": Object {
+      "template": Object {
+        "metadata": Object {
+          "labels": Object {
+            "cdk8s.io/metadata.addr": "test-Job-c8ebb678",
+          },
+        },
+        "spec": Object {
+          "automountServiceAccountToken": false,
+          "containers": Array [
+            Object {
+              "image": "foobar",
+              "imagePullPolicy": "Always",
+              "name": "main",
+              "resources": Object {
+                "limits": Object {
+                  "cpu": "1500m",
+                  "memory": "2048Mi",
+                },
+                "requests": Object {
+                  "cpu": "1000m",
+                  "memory": "512Mi",
+                },
+              },
+              "securityContext": Object {
+                "allowPrivilegeEscalation": false,
+                "privileged": false,
+                "readOnlyRootFilesystem": true,
+                "runAsGroup": 26000,
+                "runAsNonRoot": true,
+                "runAsUser": 25000,
+              },
+            },
+          ],
+          "dnsPolicy": "ClusterFirst",
+          "restartPolicy": "Never",
+          "securityContext": Object {
+            "fsGroupChangePolicy": "Always",
+            "runAsNonRoot": true,
+          },
+          "setHostnameAsFQDN": false,
+        },
+      },
+    },
+  },
+  Object {
+    "apiVersion": "networking.k8s.io/v1",
+    "kind": "NetworkPolicy",
+    "metadata": Object {
+      "name": "test-job-defaultdenyall-c8a9d814",
+    },
+    "spec": Object {
+      "podSelector": Object {
+        "matchLabels": Object {
+          "cdk8s.io/metadata.addr": "test-Job-c8ebb678",
+        },
+      },
+      "policyTypes": Array [
+        "Egress",
+        "Ingress",
+      ],
+    },
+  },
+]
+`;
diff --git a/test/__snapshots__/pod.test.ts.snap b/test/__snapshots__/pod.test.ts.snap
index 063cc0ff..2502aa96 100644
--- a/test/__snapshots__/pod.test.ts.snap
+++ b/test/__snapshots__/pod.test.ts.snap
@@ -3794,6 +3794,74 @@ Array [
 ]
 `;
 
+exports[`connections | can isolate pod 1`] = `
+Array [
+  Object {
+    "apiVersion": "v1",
+    "kind": "Pod",
+    "metadata": Object {
+      "labels": Object {
+        "cdk8s.io/metadata.addr": "test-Pod-c815bc91",
+      },
+      "name": "test-pod-c890e1b8",
+    },
+    "spec": Object {
+      "automountServiceAccountToken": false,
+      "containers": Array [
+        Object {
+          "image": "pod",
+          "imagePullPolicy": "Always",
+          "name": "main",
+          "resources": Object {
+            "limits": Object {
+              "cpu": "1500m",
+              "memory": "2048Mi",
+            },
+            "requests": Object {
+              "cpu": "1000m",
+              "memory": "512Mi",
+            },
+          },
+          "securityContext": Object {
+            "allowPrivilegeEscalation": false,
+            "privileged": false,
+            "readOnlyRootFilesystem": true,
+            "runAsGroup": 26000,
+            "runAsNonRoot": true,
+            "runAsUser": 25000,
+          },
+        },
+      ],
+      "dnsPolicy": "ClusterFirst",
+      "restartPolicy": "Always",
+      "securityContext": Object {
+        "fsGroupChangePolicy": "Always",
+        "runAsNonRoot": true,
+      },
+      "setHostnameAsFQDN": false,
+    },
+  },
+  Object {
+    "apiVersion": "networking.k8s.io/v1",
+    "kind": "NetworkPolicy",
+    "metadata": Object {
+      "name": "test-pod-defaultdenyall-c882408f",
+    },
+    "spec": Object {
+      "podSelector": Object {
+        "matchLabels": Object {
+          "cdk8s.io/metadata.addr": "test-Pod-c815bc91",
+        },
+      },
+      "policyTypes": Array [
+        "Egress",
+        "Ingress",
+      ],
+    },
+  },
+]
+`;
+
 exports[`defaults 1`] = `
 Array [
   Object {
diff --git a/test/__snapshots__/statefulset.test.ts.snap b/test/__snapshots__/statefulset.test.ts.snap
new file mode 100644
index 00000000..bf7c1239
--- /dev/null
+++ b/test/__snapshots__/statefulset.test.ts.snap
@@ -0,0 +1,109 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Can be isolated 1`] = `
+Array [
+  Object {
+    "apiVersion": "v1",
+    "kind": "Service",
+    "metadata": Object {
+      "name": "test-service-c85b0531",
+    },
+    "spec": Object {
+      "externalIPs": Array [],
+      "ports": Array [
+        Object {
+          "port": 80,
+        },
+      ],
+      "selector": Object {
+        "cdk8s.io/metadata.addr": "test-StatefulSet-c809b559",
+      },
+      "type": "ClusterIP",
+    },
+  },
+  Object {
+    "apiVersion": "apps/v1",
+    "kind": "StatefulSet",
+    "metadata": Object {
+      "name": "test-statefulset-c8a6ec86",
+    },
+    "spec": Object {
+      "minReadySeconds": 0,
+      "podManagementPolicy": "OrderedReady",
+      "replicas": 1,
+      "selector": Object {
+        "matchLabels": Object {
+          "cdk8s.io/metadata.addr": "test-StatefulSet-c809b559",
+        },
+      },
+      "serviceName": "test-service-c85b0531",
+      "template": Object {
+        "metadata": Object {
+          "labels": Object {
+            "cdk8s.io/metadata.addr": "test-StatefulSet-c809b559",
+          },
+        },
+        "spec": Object {
+          "automountServiceAccountToken": false,
+          "containers": Array [
+            Object {
+              "image": "foobar",
+              "imagePullPolicy": "Always",
+              "name": "main",
+              "resources": Object {
+                "limits": Object {
+                  "cpu": "1500m",
+                  "memory": "2048Mi",
+                },
+                "requests": Object {
+                  "cpu": "1000m",
+                  "memory": "512Mi",
+                },
+              },
+              "securityContext": Object {
+                "allowPrivilegeEscalation": false,
+                "privileged": false,
+                "readOnlyRootFilesystem": true,
+                "runAsGroup": 26000,
+                "runAsNonRoot": true,
+                "runAsUser": 25000,
+              },
+            },
+          ],
+          "dnsPolicy": "ClusterFirst",
+          "restartPolicy": "Always",
+          "securityContext": Object {
+            "fsGroupChangePolicy": "Always",
+            "runAsNonRoot": true,
+          },
+          "setHostnameAsFQDN": false,
+        },
+      },
+      "updateStrategy": Object {
+        "rollingUpdate": Object {
+          "partition": 0,
+        },
+        "type": "RollingUpdate",
+      },
+    },
+  },
+  Object {
+    "apiVersion": "networking.k8s.io/v1",
+    "kind": "NetworkPolicy",
+    "metadata": Object {
+      "name": "test-statefulset-defaultdenyall-c8b814dd",
+    },
+    "spec": Object {
+      "podSelector": Object {
+        "matchLabels": Object {
+          "cdk8s.io/metadata.addr": "test-StatefulSet-c809b559",
+        },
+      },
+      "policyTypes": Array [
+        "Egress",
+        "Ingress",
+      ],
+    },
+  },
+]
+`;
diff --git a/test/cron-job.test.ts b/test/cron-job.test.ts
index f798fafd..a66158b9 100644
--- a/test/cron-job.test.ts
+++ b/test/cron-job.test.ts
@@ -91,4 +91,22 @@ test('custom configuration', () => {
   expect(spec.jobTemplate.spec.activeDeadlineSeconds).toEqual(60);
   expect(spec.jobTemplate.spec.backoffLimit).toEqual(4);
   expect(spec.jobTemplate.spec.template.spec.containers[0].image).toEqual('image');
+});
+
+test('Can be isolated', () => {
+
+  const chart = Testing.chart();
+
+  new kplus.CronJob(chart, 'CronJob', {
+    containers: [{ image: 'foobar' }],
+    schedule: Cron.daily(),
+    isolate: true,
+  });
+
+  const manifest = Testing.synth(chart);
+  expect(manifest).toMatchSnapshot();
+
+  const networkPolicy = manifest[1].spec;
+  expect(networkPolicy.podSelector.matchLabels).toBeDefined;
+  expect(networkPolicy.policyTypes).toEqual(['Egress', 'Ingress']);
 });
\ No newline at end of file
diff --git a/test/daemon-set.test.ts b/test/daemon-set.test.ts
index 55c6ce29..10940441 100644
--- a/test/daemon-set.test.ts
+++ b/test/daemon-set.test.ts
@@ -94,3 +94,20 @@ test('can select by label', () => {
   expect(ds.matchLabels).toEqual(expectedSelector);
 
 });
+
+test('Can be isolated', () => {
+
+  const chart = Testing.chart();
+
+  new kplus.DaemonSet(chart, 'DaemonSet', {
+    containers: [{ image: 'foobar' }],
+    isolate: true,
+  });
+
+  const manifest = Testing.synth(chart);
+  expect(manifest).toMatchSnapshot();
+
+  const networkPolicy = manifest[1].spec;
+  expect(networkPolicy.podSelector.matchLabels).toBeDefined;
+  expect(networkPolicy.policyTypes).toEqual(['Egress', 'Ingress']);
+});
\ No newline at end of file
diff --git a/test/deployment.test.ts b/test/deployment.test.ts
index b4e015ac..67b333d0 100644
--- a/test/deployment.test.ts
+++ b/test/deployment.test.ts
@@ -33,6 +33,25 @@ test('A label selector is automatically allocated', () => {
 
 });
 
+
+test('Can be isolated', () => {
+
+  const chart = Testing.chart();
+
+  new kplus.Deployment(chart, 'Deployment', {
+    containers: [{ image: 'foobar' }],
+    replicas: 5,
+    isolate: true,
+  });
+
+  const manifest = Testing.synth(chart);
+  expect(manifest).toMatchSnapshot();
+
+  const networkPolicy = manifest[1].spec;
+  expect(networkPolicy.podSelector.matchLabels).toBeDefined;
+  expect(networkPolicy.policyTypes).toEqual(['Egress', 'Ingress']);
+});
+
 test('No selector is generated if "select" is false', () => {
 
   const chart = Testing.chart();
diff --git a/test/job.test.ts b/test/job.test.ts
index f00e6b01..0768e78b 100644
--- a/test/job.test.ts
+++ b/test/job.test.ts
@@ -91,3 +91,20 @@ test('Synthesizes spec lazily', () => {
   expect(container.image).toEqual('image');
 
 });
+
+test('Can be isolated', () => {
+
+  const chart = Testing.chart();
+
+  new kplus.Job(chart, 'Job', {
+    containers: [{ image: 'foobar' }],
+    isolate: true,
+  });
+
+  const manifest = Testing.synth(chart);
+  expect(manifest).toMatchSnapshot();
+
+  const networkPolicy = manifest[1].spec;
+  expect(networkPolicy.podSelector.matchLabels).toBeDefined;
+  expect(networkPolicy.policyTypes).toEqual(['Egress', 'Ingress']);
+});
\ No newline at end of file
diff --git a/test/pod.test.ts b/test/pod.test.ts
index 29f6e30b..32bab00a 100644
--- a/test/pod.test.ts
+++ b/test/pod.test.ts
@@ -781,6 +781,22 @@ describe('connections |', () => {
 
   });
 
+  test('can isolate pod', () => {
+    const chart = Testing.chart();
+
+    new kplus.Pod(chart, 'Pod', {
+      containers: [{ image: 'pod' }],
+      isolate: true,
+    });
+
+    const manifest = Testing.synth(chart);
+    expect(manifest).toMatchSnapshot();
+
+    const networkPolicy = manifest[1].spec;
+    expect(networkPolicy.podSelector.matchLabels).toBeDefined;
+    expect(networkPolicy.policyTypes).toEqual(['Egress', 'Ingress']);
+  });
+
   test('can allow to managed pod', () => {
 
     const chart = Testing.chart();
diff --git a/test/statefulset.test.ts b/test/statefulset.test.ts
index 6a84eb3b..96f71d8b 100644
--- a/test/statefulset.test.ts
+++ b/test/statefulset.test.ts
@@ -190,4 +190,26 @@ test('custom update strategy', () => {
   expect(set.strategy).toEqual(kplus.StatefulSetUpdateStrategy.onDelete());
   expect(spec.updateStrategy).toEqual({ type: 'OnDelete' });
 
+});
+
+test('Can be isolated', () => {
+
+  const chart = Testing.chart();
+
+  const service = new kplus.Service(chart, 'Service', {
+    ports: [{ port: 80 }],
+  });
+
+  new kplus.StatefulSet(chart, 'StatefulSet', {
+    containers: [{ image: 'foobar' }],
+    service: service,
+    isolate: true,
+  });
+
+  const manifest = Testing.synth(chart);
+  expect(manifest).toMatchSnapshot();
+
+  const networkPolicy = manifest[2].spec;
+  expect(networkPolicy.podSelector.matchLabels).toBeDefined;
+  expect(networkPolicy.policyTypes).toEqual(['Egress', 'Ingress']);
 });
\ No newline at end of file