diff --git a/package.json b/package.json index 5ad07f9..2245b80 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "lint": "npm run lint:helm && npm run lint:prettier", "lint:helm": "npm run deps && find parcellab -type d -maxdepth 1 -mindepth 1 | xargs -I {} helm lint {}", "lint:prettier": "prettier --check --ignore-unknown .", + "lint:prettier:fix": "prettier --write --ignore-unknown .", "lint:staged": "lint-staged", "format": "prettier --write --ignore-unknown .", "prepare": "husky install", diff --git a/parcellab/common/Chart.yaml b/parcellab/common/Chart.yaml index a97e73d..d408d3b 100644 --- a/parcellab/common/Chart.yaml +++ b/parcellab/common/Chart.yaml @@ -2,7 +2,7 @@ apiVersion: v2 name: common description: A Helm chart library for parcelLab charts type: library -version: 1.2.4 +version: 1.3.0 maintainers: - name: parcelLab email: engineering@parcellab.com diff --git a/parcellab/common/templates/_httproutes.tpl b/parcellab/common/templates/_httproutes.tpl new file mode 100644 index 0000000..9d539ce --- /dev/null +++ b/parcellab/common/templates/_httproutes.tpl @@ -0,0 +1,57 @@ +{{/* vim: set filetype=mustache: */}} +{{/* + Common HTTPRoute definition with deterministic names and labels: + {{ include "common.httproutes" . }} +*/}} + +{{- define "common.httproutes" -}} +{{- $envoy := .Values.envoy | default dict -}} +{{- if $envoy.enabled -}} +{{- $gateway := default (dict "name" "gateway-api" "namespace" "envoy-gateway") $envoy.gateway -}} +{{- $httproutes := default (list) $envoy.httpRoutes -}} +{{- $baseName := include "common.fullname" . -}} +{{- $globalLabels := include "common.labels" . -}} +{{- $serviceNamespace := .Release.Namespace -}} +{{- $security := default dict $envoy.security -}} +{{- $securityEnabled := default false $security.enabled -}} +{{- $securityLabelKey := printf "%s/security-required" (include "common.parcellabtagsdomain" .) -}} + +{{- range $index, $route := $httproutes }} +{{- $hosts := required (printf "envoy.httpRoutes[%d].hosts is required" $index) $route.hosts -}} +{{- if eq (len $hosts) 0 -}} +{{- fail (printf "envoy.httpRoutes[%d].hosts cannot be empty" $index) -}} +{{- end -}} +{{- $rawRouteName := default (printf "%s-%d" $baseName $index) $route.name -}} +{{- $sanitizedRouteName := trunc 63 (trimSuffix "-" (regexReplaceAll "[^a-z0-9-]" (lower $rawRouteName) "-")) -}} +{{- $routeName := default (printf "%s-%d" $baseName $index) $sanitizedRouteName }} +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: {{ $routeName }} + namespace: {{ $serviceNamespace }} + labels: + {{- $globalLabels | nindent 4 }} + {{ $securityLabelKey }}: {{ (ternary "true" "false" $securityEnabled) | quote }} + {{- with $route.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + external-dns.alpha.kubernetes.io/hostname: "{{ join "," $route.hosts }}" +spec: + parentRefs: + - name: {{ $gateway.name }} + namespace: {{ $gateway.namespace }} + group: gateway.networking.k8s.io + kind: Gateway + hostnames: + {{- range $hosts }} + - {{ . | quote }} + {{- end }} + {{- with $route.rules }} + rules: + {{ toYaml . | nindent 4 }} + {{ end }} +{{ end }} +{{- end }} +{{- end }} diff --git a/parcellab/common/templates/_referencegrant.tpl b/parcellab/common/templates/_referencegrant.tpl new file mode 100644 index 0000000..0224b12 --- /dev/null +++ b/parcellab/common/templates/_referencegrant.tpl @@ -0,0 +1,51 @@ +{{/* vim: set filetype=mustache: */}} +{{/* + Common ReferenceGrant definition: + {{ include "common.referencegrant" ( + dict + "Values" "the values scope" + "Release" .Release + ) }} +*/}} + +{{- define "common.referencegrant" -}} +{{- $envoy := .Values.envoy | default dict -}} +{{- $referenceGrant := .Values.envoy.referenceGrant | default dict -}} +{{- $gateway := $envoy.gateway | default dict -}} +{{- $name := include "common.fullname" . }} +{{- $serviceNamespace := .Release.Namespace }} +{{- $from := $referenceGrant.from | default list -}} +{{- $to := $referenceGrant.to | default list -}} +{{- if and $envoy.enabled (gt (len $from) 0) (gt (len $to) 0) -}} +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: ReferenceGrant +metadata: + name: {{ (printf "%s-reference-grant" $name) }} + namespace: {{ $gateway.namespace | quote }} + labels: + {{- include "common.labels" . | nindent 4 }} + {{- with $referenceGrant.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + from: + {{- range $from }} + - group: {{ .group | default "gateway.networking.k8s.io" | quote }} + kind: {{ required "referenceGrant.from.kind is required" .kind | quote }} + namespace: {{ $serviceNamespace | quote }} + {{- with .name }} + name: {{ . | quote }} + {{- end }} + {{- end }} + to: + {{- range $to }} + - group: {{ .group | default "" | quote }} + kind: {{ required "referenceGrant.to.kind is required" .kind | quote }} + {{- with .name }} + name: {{ . | quote }} + {{- end }} + {{- end }} +{{- end -}} +{{- end -}} diff --git a/parcellab/common/templates/_securitypolicies.tpl b/parcellab/common/templates/_securitypolicies.tpl new file mode 100644 index 0000000..3e0666f --- /dev/null +++ b/parcellab/common/templates/_securitypolicies.tpl @@ -0,0 +1,121 @@ +{{/* vim: set filetype=mustache: */}} +{{/* + Render Envoy Gateway SecurityPolicy resources defined under + .Values.envoy.security.policies. Each policy renders as a complete resource + (OIDC, JWT, authorization) while inheriting defaults from envoy.security.*. +*/}} +{{- define "common.securitypolicies" -}} +{{- $values := .Values -}} +{{- $envoy := default (dict "enabled" false) $values.envoy -}} +{{- if $envoy.enabled }} +{{- $security := default dict $envoy.security -}} +{{- $policies := default (list) $security.policies -}} +{{- if $policies }} +{{- $scope := dict "Values" $values "Release" .Release -}} +{{- $serviceName := default (include "common.fullname" $scope) $values.name -}} +{{- $policyNamespace := .Release.Namespace -}} +{{- $securityLabelKey := printf "%s/security-required" (include "common.parcellabtagsdomain" .) -}} +{{- $globalIssuer := $security.issuer -}} +{{- $globalRedirectURL := $security.redirectURL -}} +{{- $globalCookieDomain := $security.cookieDomain -}} +{{- $globalLogoutPath := $security.logoutPath -}} +{{- $globalClientID := $security.clientID -}} +{{- $globalClientSecretName := $security.clientSecretName -}} +{{- $globalScopes := $security.scopes -}} +{{- $globalClaimHeaders := $security.claimToHeaders -}} +{{- $globalJwtProviderName := $security.jwtProviderName -}} +{{- $globalJwksURI := $security.jwksURI -}} + +{{ range $policyIndex, $policy := $policies }} +{{- $policyName := required (printf "envoy.security.policies[%d].name is required" $policyIndex) $policy.name -}} +{{- $issuer := required (printf "SecurityPolicy %q requires envoy.security.issuer or policies[].issuer" $policyName) (coalesce $policy.issuer $globalIssuer) -}} +{{- $redirectURL := required (printf "SecurityPolicy %q requires redirectURL (set envoy.security.redirectURL or policies[].redirectURL)" $policyName) (coalesce $policy.redirectURL $globalRedirectURL) -}} +{{- $cookieDomain := required (printf "SecurityPolicy %q requires cookieDomain (set envoy.security.cookieDomain or policies[].cookieDomain)" $policyName) (coalesce $policy.cookieDomain $globalCookieDomain) -}} +{{- $logoutPath := coalesce $policy.logoutPath $globalLogoutPath "/logout" -}} +{{- $clientID := coalesce $policy.clientID $globalClientID $serviceName -}} +{{- $defaultSecretName := printf "%s-oidc-secret" $serviceName -}} +{{- $clientSecretName := coalesce $policy.clientSecretName $globalClientSecretName $defaultSecretName -}} +{{- $scopes := coalesce $policy.scopes $globalScopes -}} +{{- $claimToHeaders := coalesce $policy.claimToHeaders $globalClaimHeaders -}} +{{- $jwtProviderName := coalesce $policy.jwtProviderName $globalJwtProviderName "keycloak" -}} +{{- $jwksURI := coalesce $policy.jwksURI $globalJwksURI (printf "%s/protocol/openid-connect/certs" $issuer) -}} +{{- $targetRef := $policy.targetRef -}} +{{- $targetRefs := $policy.targetRefs -}} +{{- $rawSelectors := list -}} +{{- if $policy.targetSelectors }} + {{- if kindIs "slice" $policy.targetSelectors }} + {{- $rawSelectors = $policy.targetSelectors -}} + {{- else }} + {{- $rawSelectors = list $policy.targetSelectors -}} + {{- end }} +{{- else if $policy.targetSelector }} + {{- $rawSelectors = list $policy.targetSelector -}} +{{- end }} +{{- if and (not $targetRef) (not $targetRefs) (eq (len $rawSelectors) 0) }} + {{- $rawSelectors = list (dict "matchLabels" (dict $securityLabelKey "true")) -}} +{{- end }} +{{- $targetSelectors := list -}} +{{- range $rawSelectors }} + {{- $group := default "gateway.networking.k8s.io" .group -}} + {{- $kind := default "HTTPRoute" .kind -}} + {{- $matchLabels := default (dict) .matchLabels -}} + {{- $targetSelectors = append $targetSelectors (dict "group" $group "kind" $kind "matchLabels" $matchLabels) -}} +{{- end }} +{{- $defaultAction := default "Deny" $policy.defaultAction -}} +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: SecurityPolicy +metadata: + name: {{ $policyName }} + namespace: {{ $policyNamespace | quote }} + annotations: + oidc.autoregistrar.parcellab.dev/sync-enabled: 'true' +spec: + {{- if $targetRef }} + targetRef: + {{- toYaml $targetRef | nindent 4 }} + {{- else if $targetRefs }} + targetRefs: + {{- toYaml $targetRefs | nindent 4 }} + {{- else }} + targetSelectors: + {{- toYaml $targetSelectors | nindent 4 }} + {{- end }} + oidc: + provider: + issuer: {{ $issuer | quote }} + clientID: {{ $clientID | quote }} + clientSecret: + name: {{ $clientSecretName | quote }} + redirectURL: {{ $redirectURL | quote }} + logoutPath: {{ $logoutPath | quote }} + {{- with $scopes }} + scopes: + {{ toYaml . | nindent 6 }} + {{- end }} + cookieDomain: {{ $cookieDomain | quote }} + forwardAccessToken: true + passThroughAuthHeader: true + jwt: + optional: false + providers: + - name: {{ $jwtProviderName | quote }} + issuer: {{ $issuer | quote }} + remoteJWKS: + cacheDuration: 300s + uri: {{ $jwksURI | quote }} + {{- with $claimToHeaders }} + claimToHeaders: + {{ toYaml . | nindent 8 }} + {{- end }} + + authorization: + defaultAction: {{ $defaultAction }} + {{- with $policy.authorizationRules }} + rules: + {{ toYaml . | nindent 6 }} + {{- end }} +{{ end }} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/parcellab/common/values.yaml b/parcellab/common/values.yaml index 5940732..63b2d13 100644 --- a/parcellab/common/values.yaml +++ b/parcellab/common/values.yaml @@ -23,6 +23,13 @@ image: tag: stable ingress: enabled: false + +envoy: + enabled: false + gateway: {} + referenceGrant: {} + httpRoutes: [] + name: common terminationGracePeriodSeconds: 30 nodeSelector: {} diff --git a/parcellab/cronjob/Chart.yaml b/parcellab/cronjob/Chart.yaml index 7eff6a0..3ba4d4c 100644 --- a/parcellab/cronjob/Chart.yaml +++ b/parcellab/cronjob/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: cronjob description: Single cron job -version: 0.4.1 +version: 0.4.2 dependencies: - name: common version: "*" diff --git a/parcellab/microservice/Chart.yaml b/parcellab/microservice/Chart.yaml index 08c69a6..dfd2797 100644 --- a/parcellab/microservice/Chart.yaml +++ b/parcellab/microservice/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: microservice description: Simple microservice -version: 0.4.3 +version: 0.5.0 dependencies: - name: common version: "*" diff --git a/parcellab/microservice/README.md b/parcellab/microservice/README.md index 5e268d3..a53956d 100644 --- a/parcellab/microservice/README.md +++ b/parcellab/microservice/README.md @@ -31,6 +31,8 @@ needs. Its generated secret's data values will be loaded as environment variables to the target pod. - `hpa` - Horizontal automatic scaling rules of pods. Can be defined with the `autoscaling` setting. +- `envoy` + - Envoy Gateway resources (HTTPRoute, ReferenceGrant). Defined under `envoy.*`. - `ingress` - Rules to open external access to the workload. Can be defined with `ingress`. - `poddisruptionbudget` diff --git a/parcellab/microservice/templates/httproutes.yaml b/parcellab/microservice/templates/httproutes.yaml new file mode 100644 index 0000000..d4687b9 --- /dev/null +++ b/parcellab/microservice/templates/httproutes.yaml @@ -0,0 +1 @@ +{{- include "common.httproutes" . }} diff --git a/parcellab/microservice/templates/referencegrant.yaml b/parcellab/microservice/templates/referencegrant.yaml new file mode 100644 index 0000000..d64fae3 --- /dev/null +++ b/parcellab/microservice/templates/referencegrant.yaml @@ -0,0 +1 @@ +{{- include "common.referencegrant" . }} diff --git a/parcellab/microservice/templates/securitypolicies.yaml b/parcellab/microservice/templates/securitypolicies.yaml new file mode 100644 index 0000000..6022ef1 --- /dev/null +++ b/parcellab/microservice/templates/securitypolicies.yaml @@ -0,0 +1 @@ +{{- include "common.securitypolicies" . }} diff --git a/parcellab/microservice/values.yaml b/parcellab/microservice/values.yaml index befc739..e55fb28 100644 --- a/parcellab/microservice/values.yaml +++ b/parcellab/microservice/values.yaml @@ -46,6 +46,71 @@ ingress: # hosts: # - chart-example.local +## +## Envoy Gateway +## + +envoy: + enabled: false + gateway: + namespace: envoy-gateway + name: gateway-api + referenceGrant: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: SecurityPolicy + to: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-api + httpRoutes: [] + # - name: my-default-route + # hosts: + # - my-app.example.com + # rules: + # - name: default-route + # matches: + # - path: + # type: PathPrefix + # value: "/" + # backendRefs: + # - name: my-app + # port: 5000 + # group: "" + # kind: Service + # labels: + # foo: bar # optional + security: + enabled: false + # enabled: true + # issuer: "https://my-issuer-domain.example.com" + # redirectURL: "https://my-app.example.com/oauth2/callback" + # cookieDomain: "my-app.example.com" + # scopes: + # - profile + # - email + # claimToHeaders: + # - header: "x-user-email" + # claim: "email" + # policies: + # - name: staff-only + # targetRef: + # kind: HTTPRoute + # name: my-default-route + # group: "gateway.networking.k8s.io" + # authorizationRules: + # - name: member-of-staff-group + # action: Allow + # principal: + # jwt: + # provider: my-provider + # claims: + # - name: groups + # valueType: StringArray + # values: ["staff"] + ## ## Cronjob ## diff --git a/parcellab/monolith/Chart.yaml b/parcellab/monolith/Chart.yaml index 72d6d54..1de13c6 100644 --- a/parcellab/monolith/Chart.yaml +++ b/parcellab/monolith/Chart.yaml @@ -1,7 +1,7 @@ apiVersion: v2 name: monolith description: Application that may define multiple services and cronjobs -version: 0.4.2 +version: 0.5.0 dependencies: - name: common version: "*" diff --git a/parcellab/monolith/README.md b/parcellab/monolith/README.md index 678dee3..465e252 100644 --- a/parcellab/monolith/README.md +++ b/parcellab/monolith/README.md @@ -31,6 +31,8 @@ needs. Its generated secret's data values will be loaded as environment variables to the target pod. - `hpa` - Horizontal automatic scaling rules of pods. Can be defined with the `autoscaling` setting. +- `envoy` + - Envoy Gateway resources (HTTPRoute, ReferenceGrant, SecurityPolicy). Defined under `envoy.*`. - `ingress` - Rules to open external access to the workload. Can be defined with `ingress`. - `poddisruptionbudget` diff --git a/parcellab/monolith/templates/httproutes.yaml b/parcellab/monolith/templates/httproutes.yaml new file mode 100644 index 0000000..d4687b9 --- /dev/null +++ b/parcellab/monolith/templates/httproutes.yaml @@ -0,0 +1 @@ +{{- include "common.httproutes" . }} diff --git a/parcellab/monolith/templates/referencegrant.yaml b/parcellab/monolith/templates/referencegrant.yaml new file mode 100644 index 0000000..d64fae3 --- /dev/null +++ b/parcellab/monolith/templates/referencegrant.yaml @@ -0,0 +1 @@ +{{- include "common.referencegrant" . }} diff --git a/parcellab/monolith/templates/securitypolicies.yaml b/parcellab/monolith/templates/securitypolicies.yaml new file mode 100644 index 0000000..6022ef1 --- /dev/null +++ b/parcellab/monolith/templates/securitypolicies.yaml @@ -0,0 +1 @@ +{{- include "common.securitypolicies" . }} diff --git a/parcellab/monolith/values.yaml b/parcellab/monolith/values.yaml index d51738e..71a3b98 100644 --- a/parcellab/monolith/values.yaml +++ b/parcellab/monolith/values.yaml @@ -75,6 +75,71 @@ ingress: # hosts: # - chart-example.local +## +## Envoy Gateway +## + +envoy: + enabled: false + gateway: + namespace: envoy-gateway + name: gateway-api + referenceGrant: + from: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: SecurityPolicy + to: + - group: gateway.networking.k8s.io + kind: Gateway + name: gateway-api + httpRoutes: [] + # - name: my-default-route + # hosts: + # - my-app.example.com + # rules: + # - name: default-route + # matches: + # - path: + # type: PathPrefix + # value: "/" + # backendRefs: + # - name: my-app + # port: 5000 + # group: "" + # kind: Service + # labels: + # foo: bar # optional + security: + enabled: false + # enabled: true + # issuer: "https://my-issuer-domain.example.com" + # redirectURL: "https://my-app.example.com/oauth2/callback" + # cookieDomain: "my-app.example.com" + # scopes: + # - profile + # - email + # claimToHeaders: + # - header: "x-user-email" + # claim: "email" + # policies: + # - name: staff-only + # targetRef: + # kind: HTTPRoute + # name: my-default-route + # group: "gateway.networking.k8s.io" + # authorizationRules: + # - name: member-of-staff-group + # action: Allow + # principal: + # jwt: + # provider: my-provider + # claims: + # - name: groups + # valueType: StringArray + # values: ["staff"] + ## ## Cronjob ## diff --git a/parcellab/worker-group/README.md b/parcellab/worker-group/README.md index a2a083b..f678ab1 100644 --- a/parcellab/worker-group/README.md +++ b/parcellab/worker-group/README.md @@ -24,7 +24,7 @@ a service, as such workloads are expected to run indefinitely without network tr These resources can be configured or totally skipped depending on your business needs. - + - `configmap` - The attributes defined in `config` will automatically be loaded as environment variables to the target pod.