Skip to content

Commit

Permalink
feat: investigate and restrict network policies (#719)
Browse files Browse the repository at this point in the history
## Description
Our package should operate under a "least privilege" type model for
network access, and specifically egress network access should be limited
to specific services/addresses rather than "anywhere".

Investigated current `anywhere` policies, updated restrictions where
necessary. Added a new package CR field `remoteCidr` for defining a
custom cidr to be used in place of the anywhere cidr. Add some
validations to verify the use the `remoteGenerated`, `remoteSelector`,
`remoteNamespace`, and `remoteCidr` don't overlap or break each other.
They should be used individually except `remoteSelector` and
`remoteNamespace` being used together.

Potentially follow on issues for _KubeAPI ingress relation network
policy management, as well as utilizing service entries for known things
like S3 buckets.

## Related Issue

Fixes #558

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Other (security config, docs update, etc)

## Checklist before merging

- [x] Test, docs, adr added or updated as needed
- [x] [Contributor
Guide](https://github.com/defenseunicorns/uds-template-capability/blob/main/CONTRIBUTING.md)
followed

---------

Co-authored-by: Micah Nagel <micah.nagel@defenseunicorns.com>
  • Loading branch information
UnicornChance and mjnagel authored Sep 10, 2024
1 parent 1fd8ef3 commit b6ebc49
Show file tree
Hide file tree
Showing 21 changed files with 210 additions and 55 deletions.
17 changes: 16 additions & 1 deletion src/authservice/chart/templates/uds-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,23 @@ spec:

# Egress must be allowed to the external facing Keycloak endpoint
- direction: Egress
remoteSelector:
app: tenant-ingressgateway
remoteNamespace: istio-tenant-gateway
description: "SSO Provider"

{{- if .Values.redis.uri }}
- direction: Egress
description: Redis Session Store
{{- if .Values.redis.internal.enabled }}
remoteSelector: {{ .Values.redis.internal.remoteSelector }}
remoteNamespace: {{ .Values.redis.internal.remoteNamespace }}
{{- else if .Values.redis.egressCidr }}
remoteCidr: {{ .Values.redis.egressCidr }}
{{- else }}
remoteGenerated: Anywhere
description: "SSO Provider & Redis Session Store"
{{- end }}
{{- end }}

- direction: Ingress
selector:
Expand Down
8 changes: 8 additions & 0 deletions src/authservice/chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ image:

nameOverride: "authservice"

redis:
uri: "###ZARF_VAR_AUTHSERVICE_REDIS_URI###"
egressCidr: ""
internal:
enabled: false
remoteSelector: {}
remoteNamespace: ""

podAnnotations: {}

podSecurityContext: {}
Expand Down
28 changes: 18 additions & 10 deletions src/grafana/chart/templates/uds-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,31 @@ spec:
targetPort: 3000

allow:
- direction: Ingress
# Egress allowed to Loki
- direction: Egress
selector:
app.kubernetes.io/name: grafana
remoteNamespace: tempo
remoteNamespace: loki
remoteSelector:
app.kubernetes.io/name: tempo
port: 9090
description: "Tempo Datasource"
app.kubernetes.io/name: loki
description: "Loki Datasource"
port: 8080

# Egress allowed to Prometheus
- direction: Egress
selector:
app.kubernetes.io/name: grafana
remoteGenerated: Anywhere
remoteNamespace: monitoring
remoteSelector:
app.kubernetes.io/name: prometheus
description: "Prometheus Datasource"
port: 9090

# Egress allowed to Keyclaok
- direction: Egress
remoteNamespace: tempo
selector:
app.kubernetes.io/name: grafana
remoteNamespace: keycloak
remoteSelector:
app.kubernetes.io/name: tempo
port: 9411
description: "Tempo"
app.kubernetes.io/name: keycloak
description: "SSO Provider"
2 changes: 1 addition & 1 deletion src/keycloak/chart/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ Check external PostgreSQL connection information. Fails when required values are
{{- else -}}{{fail "You must define \"username\", \"password\", \"database\", \"host\", and \"port\" for \"postgresql\"."}}
{{- end -}}
{{- default "true" "" }}
{{- else if not (empty (compact (values (omit .Values.postgresql "port")))) -}}
{{- else if not (empty (compact (values (omit .Values.postgresql "port" "internal")))) -}}
{{ fail "Cannot use an external PostgreSQL Database when devMode is enabled." -}}
{{- else -}}
{{ default "false" "" }}
Expand Down
11 changes: 10 additions & 1 deletion src/keycloak/chart/templates/uds-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@ spec:
port: 8080

# Temp workaround for any cluster pod
# @todo: remove this once cluster pods is a remote generated target
# todo: remove this once cluster pods is a remote generated target
- description: "Keycloak backchannel access"
direction: Ingress
selector:
app.kubernetes.io/name: keycloak
remoteGenerated: Anywhere
port: 8080

# Keycloak OCSP to check certs cannot guarantee a static IP
- description: "OCSP Lookup"
direction: Egress
selector:
Expand All @@ -58,8 +59,16 @@ spec:
selector:
app.kubernetes.io/name: keycloak
port: {{ .Values.postgresql.port }}
{{- if .Values.postgresql.internal.enabled }}
remoteSelector: {{ .Values.postgresql.internal.remoteSelector }}
remoteNamespace: {{ .Values.postgresql.internal.remoteNamespace }}
{{- else if .Values.postgresql.egressCidr }}
remoteCidr: {{ .Values.postgresql.egressCidr }}
{{- else }}
remoteGenerated: Anywhere
{{- end }}
{{- end }}

{{- if .Values.autoscaling.enabled }}
# HA for keycloak
- direction: Ingress
Expand Down
6 changes: 6 additions & 0 deletions src/keycloak/chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ postgresql:
host: ""
# Port the database is listening on
port: 5432
egressCidr: ""
# Configure internal postgresql deployment, requires keycloak not be deployed in dev-mode
internal:
enabled: false
remoteSelector: {}
remoteNamespace: ""

serviceMonitor:
# If `true`, a ServiceMonitor resource for the prometheus-operator is created
Expand Down
17 changes: 9 additions & 8 deletions src/loki/chart/templates/uds-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,16 @@ spec:
- 8080
description: "Promtail Log Storage"

# Todo: wide open for now for pushing to s3
# Egress for S3 connections
- direction: Egress
selector:
app.kubernetes.io/name: loki
description: Storage
{{- if .Values.storage.internal.enabled }}
remoteSelector: {{ .Values.storage.internal.remoteSelector }}
remoteNamespace: {{ .Values.storage.internal.remoteNamespace }}
{{- else if .Values.storage.egressCidr }}
remoteCidr: {{ .Values.storage.egressCidr }}
{{- else }}
remoteGenerated: Anywhere

- direction: Egress
remoteNamespace: tempo
remoteSelector:
app.kubernetes.io/name: tempo
port: 9411
description: "Tempo"
{{- end }}
6 changes: 6 additions & 0 deletions src/loki/chart/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
storage:
internal:
enabled: false
remoteSelector: {}
remoteNamespace: ""
egressCidr: ""
12 changes: 4 additions & 8 deletions src/neuvector/chart/templates/uds-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ spec:

# Access to SSO for OIDC
- direction: Egress
remoteGenerated: Anywhere
selector:
app: neuvector-controller-pod
remoteSelector:
app: tenant-ingressgateway
remoteNamespace: istio-tenant-gateway
description: "SSO Provider"

- direction: Egress
remoteGenerated: KubeAPI
Expand All @@ -79,10 +82,3 @@ spec:
app: neuvector-controller-pod
port: 30443
description: "Webhook"

- direction: Egress
remoteNamespace: tempo
remoteSelector:
app.kubernetes.io/name: tempo
port: 9411
description: "Tempo"
7 changes: 0 additions & 7 deletions src/pepr/operator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,6 @@ spec:
app.kubernetes.io/name: grafana
remoteGenerated: Anywhere

- direction: Egress
remoteNamespace: tempo
remoteSelector:
app.kubernetes.io/name: tempo
port: 9411
description: "Tempo"

# SSO allows for the creation of Keycloak clients and with automatic secret generation
sso:
- name: Grafana Dashboard
Expand Down
58 changes: 58 additions & 0 deletions src/pepr/operator/controllers/network/generate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,61 @@ describe("network policy generate", () => {
policyTypes: ["Egress"],
} as kind.NetworkPolicy["spec"]);
});

describe("network policy generate with remoteCidr", () => {
it("should generate correct network policy with remoteCidr for Egress", async () => {
const policy = generate("test", {
description: "test",
direction: Direction.Egress,
selector: { app: "test" },
remoteCidr: "192.168.0.0/16",
});

expect(policy.metadata?.name).toEqual("Egress-test");
expect(policy.spec).toEqual({
egress: [
{
to: [
{
ipBlock: {
cidr: "192.168.0.0/16",
except: ["169.254.169.254/32"], // Include the except field here
},
},
],
ports: [],
},
],
podSelector: { matchLabels: { app: "test" } },
policyTypes: ["Egress"],
} as kind.NetworkPolicy["spec"]);
});

it("should generate correct network policy with remoteCidr for Ingress", async () => {
const policy = generate("test", {
description: "test",
direction: Direction.Ingress,
selector: { app: "test" },
remoteCidr: "10.0.0.0/8",
});

expect(policy.metadata?.name).toEqual("Ingress-test");
expect(policy.spec).toEqual({
ingress: [
{
from: [
{
ipBlock: {
cidr: "10.0.0.0/8",
except: ["169.254.169.254/32"], // Include the except field here
},
},
],
ports: [],
},
],
podSelector: { matchLabels: { app: "test" } },
policyTypes: ["Ingress"],
} as kind.NetworkPolicy["spec"]);
});
});
3 changes: 3 additions & 0 deletions src/pepr/operator/controllers/network/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { anywhere } from "./generators/anywhere";
import { cloudMetadata } from "./generators/cloudMetadata";
import { intraNamespace } from "./generators/intraNamespace";
import { kubeAPI } from "./generators/kubeAPI";
import { remoteCidr } from "./generators/remoteCidr";

function isWildcardNamespace(namespace: string) {
return namespace === "" || namespace === "*";
Expand Down Expand Up @@ -52,6 +53,8 @@ function getPeers(policy: Allow): V1NetworkPolicyPeer[] {
}

peers.push(peer);
} else if (policy.remoteCidr !== undefined) {
peers = [remoteCidr(policy.remoteCidr)];
}

return peers;
Expand Down
12 changes: 12 additions & 0 deletions src/pepr/operator/controllers/network/generators/remoteCidr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { V1NetworkPolicyPeer } from "@kubernetes/client-node";
import { META_IP } from "./cloudMetadata";

/** Matches a specific custom cidr EXCEPT the Cloud Meta endpoint */
export function remoteCidr(cidr: string): V1NetworkPolicyPeer {
return {
ipBlock: {
cidr,
except: [META_IP],
},
};
}
4 changes: 4 additions & 0 deletions src/pepr/operator/crd/generated/package-v1alpha1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ export interface Allow {
* A list of ports to allow (protocol is always TCP)
*/
ports?: number[];
/**
* Custom generated policy CIDR
*/
remoteCidr?: string;
/**
* Custom generated remote selector for the policy
*/
Expand Down
4 changes: 4 additions & 0 deletions src/pepr/operator/crd/sources/package/v1alpha1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ const allow = {
type: "string",
enum: ["KubeAPI", "IntraNamespace", "CloudMetadata", "Anywhere"],
},
remoteCidr: {
description: "Custom generated policy CIDR",
type: "string",
},
port: {
description: "The port to allow (protocol is always TCP)",
minimum: 1,
Expand Down
31 changes: 28 additions & 3 deletions src/pepr/operator/crd/validators/package-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,34 @@ export async function validator(req: PeprValidateRequest<UDSPackage>) {
const networkPolicyNames = new Set<string>();

for (const policy of networkPolicy) {
// remoteGenerated cannot be combined with remoteNamespace or remoteSelector
if (policy.remoteGenerated && (policy.remoteNamespace || policy.remoteSelector)) {
return req.Deny("remoteGenerated cannot be combined with remoteNamespace or remoteSelector");
// If 'remoteGenerated' is set, it cannot be combined with 'remoteNamespace', 'remoteSelector', or 'remoteCidr'.
if (
policy.remoteGenerated &&
(policy.remoteNamespace || policy.remoteSelector || policy.remoteCidr)
) {
return req.Deny(
"remoteGenerated cannot be combined with remoteNamespace, remoteSelector, or remoteCidr",
);
}

// If either 'remoteNamespace' or 'remoteSelector' is set, they cannot be combined with 'remoteGenerated' or 'remoteCidr'.
if (
(policy.remoteNamespace || policy.remoteSelector) &&
(policy.remoteGenerated || policy.remoteCidr)
) {
return req.Deny(
"remoteNamespace and remoteSelector cannot be combined with remoteGenerated or remoteCidr",
);
}

// If 'remoteCidr' is set, it cannot be combined with 'remoteGenerated', 'remoteNamespace', or 'remoteSelector'.
if (
policy.remoteCidr &&
(policy.remoteGenerated || policy.remoteNamespace || policy.remoteSelector)
) {
return req.Deny(
"remoteCidr cannot be combined with remoteGenerated, remoteNamespace, or remoteSelector",
);
}

// Ensure the policy name is unique
Expand Down
10 changes: 2 additions & 8 deletions src/prometheus-stack/chart/templates/uds-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ spec:
port: 10250
description: "Webhook"

# todo: lockdown egress to scrape targets
# Prometheus scrape targets
- direction: Egress
remoteNamespace: ""
remoteNamespace: "" # todo: restrict this overly permissive netpol
selector:
app.kubernetes.io/name: prometheus
description: "Metrics Scraping"
Expand All @@ -62,9 +62,3 @@ spec:
port: 9090
description: "Grafana Metrics Queries"

- direction: Egress
remoteNamespace: tempo
remoteSelector:
app.kubernetes.io/name: tempo
port: 9411
description: "Tempo"
7 changes: 0 additions & 7 deletions src/promtail/chart/templates/uds-package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,6 @@ spec:
app.kubernetes.io/name: promtail
remoteGenerated: KubeAPI

- direction: Egress
remoteNamespace: tempo
remoteSelector:
app.kubernetes.io/name: tempo
port: 9411
description: "Tempo"

- direction: Egress
selector:
app.kubernetes.io/name: promtail
Expand Down
Loading

0 comments on commit b6ebc49

Please sign in to comment.