diff --git a/generated/routes.json b/generated/routes.json index 0b7c04a7..25819fcb 100644 --- a/generated/routes.json +++ b/generated/routes.json @@ -13,11 +13,11 @@ }, "/overview/management-api-reference": { "relPath": "/overview/management-api-reference.md", - "lastmod": "2025-12-15T08:09:57.000Z" + "lastmod": "2025-12-22T07:46:07.000Z" }, "/overview/agent-api-reference": { "relPath": "/overview/agent-api-reference.md", - "lastmod": "2025-11-26T09:01:03.000Z" + "lastmod": "2025-12-29T11:00:04.000Z" }, "/getting-started": { "relPath": "/getting-started/index.md", @@ -117,23 +117,23 @@ }, "/plural-features/continuous-deployment/git-service": { "relPath": "/plural-features/continuous-deployment/git-service.md", - "lastmod": "2025-03-12T14:59:41.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/plural-features/continuous-deployment/helm-service": { "relPath": "/plural-features/continuous-deployment/helm-service.md", - "lastmod": "2025-06-09T19:18:57.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/plural-features/continuous-deployment/resource-application-logic": { "relPath": "/plural-features/continuous-deployment/resource-application-logic.md", - "lastmod": "2025-12-09T11:18:11.000Z" + "lastmod": "2025-12-22T19:54:38.000Z" }, "/plural-features/continuous-deployment/service-templating": { "relPath": "/plural-features/continuous-deployment/service-templating/index.md", - "lastmod": "2025-12-19T10:52:07.039Z" + "lastmod": "2025-12-30T05:57:04.477Z" }, "/plural-features/continuous-deployment/service-templating/supporting-liquid-filters": { "relPath": "/plural-features/continuous-deployment/service-templating/supporting-liquid-filters.md", - "lastmod": "2025-12-19T10:52:07.055Z" + "lastmod": "2025-12-30T05:57:04.494Z" }, "/plural-features/continuous-deployment/lua": { "relPath": "/plural-features/continuous-deployment/lua.md", @@ -141,7 +141,7 @@ }, "/plural-features/continuous-deployment/global-service": { "relPath": "/plural-features/continuous-deployment/global-service.md", - "lastmod": "2025-03-12T14:59:41.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/plural-features/continuous-deployment/observer": { "relPath": "/plural-features/continuous-deployment/observer.md", @@ -149,7 +149,7 @@ }, "/plural-features/continuous-deployment/pipelines": { "relPath": "/plural-features/continuous-deployment/pipelines.md", - "lastmod": "2025-05-12T06:30:23.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/plural-features/continuous-deployment/github-actions-ci": { "relPath": "/plural-features/continuous-deployment/github-actions-ci.md", @@ -253,7 +253,7 @@ }, "/plural-features/flows": { "relPath": "/plural-features/flows/index.md", - "lastmod": "2025-05-11T23:04:59.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/plural-features/flows/create-a-flow": { "relPath": "/plural-features/flows/create-a-flow.md", @@ -273,7 +273,7 @@ }, "/plural-features/flows/mcp-auth": { "relPath": "/plural-features/flows/mcp-auth.md", - "lastmod": "2025-05-27T02:01:02.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/plural-features/flows/scm-webhooks-and-pr-linking": { "relPath": "/plural-features/flows/scm-webhooks-and-pr-linking.md", @@ -349,39 +349,19 @@ }, "/examples/continuous-deployment/helm-basic-with-inline-values": { "relPath": "/examples/continuous-deployment/helm-basic-with-inline-values.md", - "lastmod": "2025-05-10T04:28:20.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/examples/continuous-deployment/helm-basic-with-values-file": { "relPath": "/examples/continuous-deployment/helm-basic-with-values-file.md", - "lastmod": "2025-05-10T04:28:20.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/examples/continuous-deployment/kustomize-inflate-helm": { "relPath": "/examples/continuous-deployment/kustomize-inflate-helm.md", - "lastmod": "2025-05-10T04:28:20.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/examples/continuous-deployment/kustomize-stack-with-liquid": { "relPath": "/examples/continuous-deployment/kustomize-stack-with-liquid.md", - "lastmod": "2025-05-10T04:28:20.000Z" - }, - "/faq": { - "relPath": "/faq/index.md", - "lastmod": "2025-03-12T14:59:41.000Z" - }, - "/faq/security": { - "relPath": "/faq/security.md", - "lastmod": "2025-03-12T14:59:41.000Z" - }, - "/faq/plural-oidc": { - "relPath": "/faq/plural-oidc.md", - "lastmod": "2025-03-12T14:59:41.000Z" - }, - "/faq/certifications": { - "relPath": "/faq/certifications.md", - "lastmod": "2025-03-12T14:59:41.000Z" - }, - "/faq/paid-tiers": { - "relPath": "/faq/paid-tiers.md", - "lastmod": "2025-03-12T14:59:41.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/resources": { "relPath": "/resources/index.md", @@ -395,6 +375,10 @@ "relPath": "/resources/product-updates.md", "lastmod": "2025-05-01T19:37:01.000Z" }, + "/resources/security": { + "relPath": null, + "lastmod": null + }, "/resources/architecture": { "relPath": "/resources/architecture.md", "lastmod": "2025-08-11T16:38:17.000Z" @@ -405,7 +389,7 @@ }, "/getting-started/agent-api-reference": { "relPath": "/overview/agent-api-reference.md", - "lastmod": "2025-11-26T09:01:03.000Z" + "lastmod": "2025-12-29T11:00:04.000Z" }, "/getting-started/readme": { "relPath": "/getting-started/index.md", @@ -493,15 +477,15 @@ }, "/deployments/operator/git-service": { "relPath": "/plural-features/continuous-deployment/git-service.md", - "lastmod": "2025-03-12T14:59:41.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/deployments/operator/helm-service": { "relPath": "/plural-features/continuous-deployment/helm-service.md", - "lastmod": "2025-06-09T19:18:57.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/deployments/operator/global-service": { "relPath": "/plural-features/continuous-deployment/global-service.md", - "lastmod": "2025-03-12T14:59:41.000Z" + "lastmod": "2025-12-30T05:54:16.000Z" }, "/deployments/using-operator": { "relPath": "/plural-features/continuous-deployment/index.md", diff --git a/pages/examples/continuous-deployment/helm-basic-with-inline-values.md b/pages/examples/continuous-deployment/helm-basic-with-inline-values.md index 0177f080..815c6104 100644 --- a/pages/examples/continuous-deployment/helm-basic-with-inline-values.md +++ b/pages/examples/continuous-deployment/helm-basic-with-inline-values.md @@ -36,6 +36,7 @@ metadata: name: plrl-01-grafana namespace: examples spec: + cluster: mgmt helm: # Remote helm repository url: https://grafana.github.io/helm-charts @@ -56,11 +57,6 @@ spec: - hosts: - grafana-test.your-domain.com secretName: grafana-tls - # A reference to the cluster resource instance we've created in the previous step - clusterRef: - kind: Cluster - name: mgmt - namespace: examples ``` ### Step 2: Check Plural Console and access Grafana diff --git a/pages/examples/continuous-deployment/helm-basic-with-values-file.md b/pages/examples/continuous-deployment/helm-basic-with-values-file.md index 1d47a5ba..1bb27dd4 100644 --- a/pages/examples/continuous-deployment/helm-basic-with-values-file.md +++ b/pages/examples/continuous-deployment/helm-basic-with-values-file.md @@ -55,12 +55,9 @@ metadata: name: plrl-02-grafana namespace: examples spec: - # GitRepository reference that points to a git repo where values file is stored - repositoryRef: - kind: GitRepository - name: example - namespace: examples + cluster: mgmt git: + url: https://github.com/yourorg/example.git # the url for your example git repo # A directory in the git repository with the values file folder: helm-values ref: main @@ -78,11 +75,6 @@ spec: # inline configuration that will be passed as a context to the liquid helm values file configuration: host: 'grafana-test.your-domain.com' - # A reference to the cluster resource instance we've created in the previous step - clusterRef: - kind: Cluster - name: mgmt - namespace: examples ``` ### Step 3: Check Plural Console and access Grafana diff --git a/pages/examples/continuous-deployment/kustomize-inflate-helm.md b/pages/examples/continuous-deployment/kustomize-inflate-helm.md index bde6ce43..560356f5 100644 --- a/pages/examples/continuous-deployment/kustomize-inflate-helm.md +++ b/pages/examples/continuous-deployment/kustomize-inflate-helm.md @@ -107,11 +107,9 @@ metadata: name: plrl-04-wordpress namespace: examples spec: - repositoryRef: - kind: GitRepository - name: example - namespace: examples + cluster: mgmt git: + url: https://github.com/yourorg/example.git # the url for your example git repo folder: services/examples/kustomize-inflate-helm ref: main kustomize: @@ -120,9 +118,6 @@ spec: configurationRef: name: plrl-04-wordpress-config namespace: examples - clusterRef: - name: mgmt - namespace: examples ``` ### Step 4: Check Plural Console and access WordPress diff --git a/pages/examples/continuous-deployment/kustomize-stack-with-liquid.md b/pages/examples/continuous-deployment/kustomize-stack-with-liquid.md index 29599ee4..ef77f365 100644 --- a/pages/examples/continuous-deployment/kustomize-stack-with-liquid.md +++ b/pages/examples/continuous-deployment/kustomize-stack-with-liquid.md @@ -238,19 +238,14 @@ metadata: name: plrl-03-wordpress namespace: examples spec: - repositoryRef: - kind: GitRepository - name: example - namespace: examples + cluster: mgmt git: + url: https://github.com/yourorg/example.git # the url for your example git repo folder: services/examples/kustomize-stack-with-liquid ref: main configuration: wordpressTag: "4.8-apache" mysqlTag: "5.6" - clusterRef: - name: mgmt - namespace: examples ``` ### Step 5: Check Plural Console and access WordPress diff --git a/pages/faq/security.md b/pages/faq/security.md index a3b93d8d..91954f97 100644 --- a/pages/faq/security.md +++ b/pages/faq/security.md @@ -3,6 +3,17 @@ title: Is Plural secure? description: Learn about what Plural has access to at various steps of deployment. --- +# Certifications + + +Plural is currently a part of the **Cloud Native Computing Foundation** and **Cloud Native Landscape**. In addition we maintain the following certifications: + +* **GDPR** +* **SOC 2 Type 2** + + +In more detail we maintain a few key guarantees around our usage of cloud data and access to your infrastructure for our two main distribution channels. + # Plural Console The Plural Console by default has access to nothing in your cloud. To grant it access you'll have to do one of the following: @@ -17,10 +28,11 @@ In addition, the Console only will make two outbound network requests, outside o # Plural Cloud -A Plural Console running in Plural Cloud can collect creds in a few ways: +A Plural Console running in Plural Cloud is functionally equivalent to any other instance of Plural, but since it sits on our servers, it's worth being aware that we can collect creds in a few ways: 1. Plural-managed terraform state could have various credentials inside it 2. SCM credentials are stored row-encrypted in our database (but can be revoked at any time). 3. Service secrets are stored row-encrypted in our database (but you can use cloud-native secret managers if you prefer robustness over convenience). Since you'll still need to create a small management cluster to attach to your cloud console, that will be what is bound any cloud creds for executing terraform, etc, and so you do not need to exchange any cloud credentials with Plural to use Plural Cloud. + \ No newline at end of file diff --git a/pages/plural-features/continuous-deployment/git-service.md b/pages/plural-features/continuous-deployment/git-service.md index 8c47e932..fd270986 100644 --- a/pages/plural-features/continuous-deployment/git-service.md +++ b/pages/plural-features/continuous-deployment/git-service.md @@ -3,7 +3,26 @@ title: Git-sourced services description: Source manifests directly from git --- -The simplest service you can deploy is just referencing a folder within a git repository. This pattern is great for deploying a simple microservice owned by a dev team, or perhaps setting up an app-of-apps. The CRs needed to make this work would be: +The simplest service you can deploy is just referencing a folder within a git repository. This pattern is great for deploying a simple microservice owned by a dev team, or perhaps setting up an app-of-apps. + +There are a few different ways this can be done, here is the simplest: + +```yaml +apiVersion: deployments.plural.sh/v1alpha1 +kind: ServiceDeployment +metadata: + name: git-folder +spec: + namespace: examples + name: git-folder + cluster: k3s # handle of the git repository + git: + url: git@github.com:your-org/example.git # this git repository needs to be created either via a GitRepository cr or in the Plural UI + folder: kubernetes/manifests # or whatever folder you wish + ref: main +``` + +You can also use custom resource references if you don't want to type out the git repository url each time (this can also be useful to provide a stable name for that repository). The CRs needed to make this work would be: ```yaml # the GitRepository and Cluster resources should ideally be defined elsewhere in your infra repo diff --git a/pages/plural-features/continuous-deployment/global-service.md b/pages/plural-features/continuous-deployment/global-service.md index c994b926..2e2edcae 100644 --- a/pages/plural-features/continuous-deployment/global-service.md +++ b/pages/plural-features/continuous-deployment/global-service.md @@ -7,32 +7,6 @@ Plural natively supports a concept of global services, e.g. a service that's rep ```yaml apiVersion: deployments.plural.sh/v1alpha1 -kind: Cluster -metadata: - name: k3s-prod - namespace: infra -spec: - handle: k3s-prod ---- -apiVersion: deployments.plural.sh/v1alpha1 -kind: ServiceDeployment -metadata: - name: system-upgrade - namespace: infra -spec: - namespace: system-upgrade - helm: - version: '0.1.1' - chart: system-upgrade-controller - repository: - namespace: infra - name: console - clusterRef: - kind: Cluster - name: k3s-prod - namespace: infra ---- -apiVersion: deployments.plural.sh/v1alpha1 kind: GlobalService metadata: name: system-upgrade @@ -40,9 +14,14 @@ metadata: spec: tags: fabric: edge - serviceRef: - name: system-upgrade - namespace: infra + template: + namespace: system-upgrade + helm: + version: '0.1.1' + chart: system-upgrade-controller + repository: + namespace: infra + name: console ``` In this specific case, we set up the k3s system upgrade controller on a single cluster, then replicate it using the `GlobalService` CR to all clusters with the `edge: fabric` tags. You can also target on kubernetes distribution (eg EKS, AKS, GKE, K3s, etc) or cluster api provider, and we're welcome to other targeting/organization mechanisms as well if those are not sufficient. @@ -53,6 +32,22 @@ A very common problem you'll face when redeploying apps across your fleet is the Take an example of setting up external-dns fleet wide. You'll likely need to configure a different domain and iam arn for each cluster. You can leverage the cluster resources built-in metadata field alongside templating to accomplish that easily, like so: +The ideal way to provide metadata is via terraform, since very often that is the source of truth for the initial values. This would be done via code like this: + +```terraform +resource "plural_cluster" "this" { + name = "eks-prod" + + metadata = jsonencode({ + externaldnsRoleArn = "arn:..." + domain = "my.domain.com" + ... + }) +} +``` + +That said, you can also do it in yaml using a `Cluster` custom resource, like what's presented below: + ```yaml apiVersion: deployments.plural.sh/v1alpha1 kind: Cluster @@ -66,34 +61,11 @@ spec: metadata: externaldnsRoleArn: arn:...//externaldns domain: my.domain.com # can add any nested map structure as metadata ---- -apiVersion: deployments.plural.sh/v1alpha1 -kind: ServiceDeployment -metadata: - name: externaldns - namespace: infra -spec: - namespace: externaldns - repositoryRef: - kind: GitRepository - name: infra - namespace: infra - git: - ref: main - folder: helm-values # or wherever else you want to store the helm values - helm: - version: 6.31.4 - chart: externaldns - valuesFiles: - - externaldns.yaml.liquid # use a liquid extension to enable templating in this file - repository: - namespace: infra - name: externaldns - clusterRef: - kind: Cluster - name: eks-prod - namespace: infra ---- +``` + +From there, you can define your global service and the helm values needed for it: + +```yaml apiVersion: deployments.plural.sh/v1alpha1 kind: GlobalService metadata: @@ -102,12 +74,22 @@ metadata: spec: tags: fabric: eks - serviceRef: - name: externaldns - namespace: infra + template: + git: + url: git@github.com:pluralsh/infra-example.git + ref: main + folder: helm-values # or wherever else you want to store the helm values + helm: + version: 6.31.4 + chart: externaldns + valuesFiles: + - externaldns.yaml.liquid # use a liquid extension to enable templating in this file + repository: + namespace: infra + name: externaldns ``` -From there, your `externaldns.yaml.liquid` might look something like: +From there, your `helm-values/externaldns.yaml.liquid` might look something like: ```yaml domainFilters: @@ -121,4 +103,4 @@ serviceAccount: If you added secrets, you can access them with something like `{{ configuration.YOUR_SECRET_NAME }}` and if you want to use service contexts, we recommend checking the docs {% doclink to="terraform-interop" %}here{% /doclink %}. -Very commonly there's actually a lot of overlap between configuration on the same cluster, so the metadata blob will be smaller than you might expect as well. We've found this is an elegant and maintainable solution for users that are fine with the manual copy paste into yaml, whereas service contexts go a step further and automate the entire process. +Very commonly there's actually a lot of overlap between configuration on the same cluster, so the metadata blob will be smaller than you might expect. \ No newline at end of file diff --git a/pages/plural-features/continuous-deployment/helm-service.md b/pages/plural-features/continuous-deployment/helm-service.md index 86f51d6b..b8b44993 100644 --- a/pages/plural-features/continuous-deployment/helm-service.md +++ b/pages/plural-features/continuous-deployment/helm-service.md @@ -4,20 +4,10 @@ description: Source manifests from a helm repository registered anywhere --- You can also source manifests from a https or OCI-compatible helm repository. This is very useful for provisioning -kubernetes add-ons, which are usually packaged using helm, or occasionally for complex release processes where helms -versioning independent of git can be valuable. The path here requires creation of a Flux `HelmRepository` CR first, then -the service, e.g.: +kubernetes add-ons, which are usually packaged using helm, or occasionally for complex release processes where Helm's +versioning independent of git can be valuable. ```yaml -# the Cluster resource should ideally be defined in separate files in your infra repo -apiVersion: deployments.plural.sh/v1alpha1 -kind: Cluster -metadata: - name: k3s - namespace: infra -spec: - handle: k3s ---- apiVersion: deployments.plural.sh/v1alpha1 kind: ServiceDeployment metadata: @@ -26,6 +16,7 @@ metadata: spec: namespace: ingress-nginx name: ingress-nginx + cluster: k3s helm: version: 4.4.x chart: ingress-nginx @@ -38,13 +29,6 @@ spec: digestChroot: null admissionWebhooks: enabled: false - repository: - namespace: infra - name: nginx # referenced helm repository above - clusterRef: - kind: Cluster - name: k3s - namespace: infra ``` ## Dynamic Helm Configuration via luaScript @@ -61,6 +45,7 @@ metadata: spec: namespace: ingress-nginx name: ingress-nginx + cluster: k3s helm: version: 4.4.x chart: ingress-nginx @@ -75,13 +60,6 @@ spec: values["maxConnections"] = 100 valuesFiles = {"config.json", "secrets.yaml"} - repository: - namespace: infra - name: nginx # referenced helm repository above - clusterRef: - kind: Cluster - name: k3s - namespace: infra ``` For more information, see [Dynamic Helm Configuration with Lua Scripts](lua.md). @@ -106,11 +84,9 @@ metadata: spec: namespace: ingress-nginx name: ingress-nginx - repositoryRef: - kind: GitRepository - name: infra # points to the CRD above - namespace: infra + cluster: k3s git: + url: git@github.com:pluralsh/infra-example.git ref: main folder: helm # where helm values files are stored helm: @@ -127,10 +103,6 @@ spec: enabled: false valuesFiles: - ingress-nginx.values.yaml # using helm/ingress-nginx.values.yaml as our values file - clusterRef: - kind: Cluster - name: k3s - namespace: infra ``` ## Helm Repositories Stored in Git @@ -146,19 +118,13 @@ metadata: spec: namespace: helm-app name: helm-app - repositoryRef: - kind: GitRepository - name: infra # points to the CRD above - namespace: infra + cluster: k3s git: + url: git@github.com:pluralsh/infra-example.git ref: main folder: chart # where the helm chart lives helm: values: image: tag: override-tag # example in-line helm values - clusterRef: - kind: Cluster - name: k3s - namespace: infra ``` \ No newline at end of file diff --git a/pages/plural-features/continuous-deployment/pipelines.md b/pages/plural-features/continuous-deployment/pipelines.md index d1e55e43..f0a80e23 100644 --- a/pages/plural-features/continuous-deployment/pipelines.md +++ b/pages/plural-features/continuous-deployment/pipelines.md @@ -52,39 +52,25 @@ apiVersion: deployments.plural.sh/v1alpha1 kind: ServiceDeployment metadata: name: guestbook-dev - namespace: default + namespace: infra spec: - version: 0.0.1 + cluster: dev git: folder: guestbook ref: master - repositoryRef: - kind: GitRepository - name: guestbook - namespace: default - clusterRef: - kind: Cluster - name: dev - namespace: default + url: git@github.com:pluralsh/guestbook.git # replace with valid git repo --- apiVersion: deployments.plural.sh/v1alpha1 kind: ServiceDeployment metadata: name: guestbook-prod - namespace: default + namespace: infra spec: - version: 0.0.1 + cluster: prod git: folder: guestbook ref: master - repositoryRef: - kind: GitRepository - name: guestbook - namespace: default - clusterRef: - kind: Cluster - name: prod - namespace: default + url: git@github.com:pluralsh/guestbook.git # replace with valid git repo ``` ### Pipeline CRD @@ -93,62 +79,39 @@ apiVersion: deployments.plural.sh/v1alpha1 kind: Pipeline metadata: name: test - namespace: default + namespace: infra spec: stages: - name: dev services: - serviceRef: name: guestbook-dev - namespace: default + namespace: infra - name: prod services: - serviceRef: name: guestbook-prod - namespace: default + namespace: infra criteria: serviceRef: name: guestbook-dev - namespace: default + namespace: infra secrets: - test-secret edges: - from: dev to: prod gates: - - name: job-gate - type: JOB - clusterRef: - name: dev - namespace: default - spec: - job: - namespace: default - labels: - test: test - annotations: - plural.sh/annotation: test - serviceAccount: default - containers: - - image: alpine:3.7 - args: - - /bin/sh - - -c - - sleep 40 - env: - - name: TEST_ENV_VAR - value: pipeline + - name: sentinel + type: SENTINEL # organize integration testing with a sentinel + sentinelRef: + name: dev-sentinel + namespace: infra - name: approval-gate type: APPROVAL ``` This edge controls the transition from dev → prod. It includes: - 1. Job Gate: A container runs in the dev cluster and must complete successfully - - In this case, it’s an alpine container sleeping for 40 seconds, but in practice this could run tests, linting, etc. + 1. Sentinel Gate: leverages a [Plural Sentinel](/plural-features/plural-ai/sentinels) to do deep integration testing for dev. 2. Approval Gate: Requires manual approval before deploying to prod -You can view the complete working example of a Plural Pipeline including clusters, services, promotion logic, and gates on GitHub: - - [pipeline.yaml on GitHub](https://github.com/pluralsh/console/blob/master/go/controller/config/samples/pipeline.yaml) - -This example demonstrates a multi-stage deployment from `dev` to `prod`, using both job and approval gates to control promotion. diff --git a/pages/plural-features/flows/index.md b/pages/plural-features/flows/index.md index 7e09c2bd..1220a70d 100644 --- a/pages/plural-features/flows/index.md +++ b/pages/plural-features/flows/index.md @@ -9,7 +9,7 @@ description: 'Developer-Facing Portal for your Kubernetes Infrastructure' If you just want to skip the text and see it in action, skip to the demo video below. {% /callout %} -Kubernetes is often overwhelming for developers to comprehend the state of the applications they're managing, which are often simple stateless services, static frontends, or small sets of microservices. Platform engineers often solve for this by providing a basic "abstraction layer" on top of their kubernetes clusters, often accomplished with a slapdash of tools like internal UIs, Backstage, ArgoCD and others. +Kubernetes is often too overwhelming for developers to comprehend the state of the applications they're managing, despite the fact that these are often simple stateless services, static frontends, or small sets of microservices. Platform engineers often solve for this by providing a basic "abstraction layer" on top of their kubernetes clusters, often accomplished with a slapdash of tools like internal UIs, Backstage, ArgoCD and others. Plural Flows provide a first-order way to do this, natively integrated with the rest of the Plural experience. It encompasses: @@ -28,7 +28,7 @@ Further, because it gives us a clear circumference to understand what's related * Ability to call out to Model Context Protocol (MCP) for external operations tasks * A built in knowledge graph layer to continuously enrich the context available to the AI -Further, Plural Flows can vector index prs, query app log data and respond to incoming alerts, extending the capabilities of our AI insight engine to be able to be a general troubleshooting tool for application code errors alongside the built-in support for Kubernetes misconfiguration. +Further, Plural Flows can vector index prs, query app log data and respond to incoming alerts, extending the capabilities of our AI insight engine to be able to be a general troubleshooting tool for application code errors alongside our already built-in support for Kubernetes misconfiguration. # Demo Video diff --git a/pages/plural-features/flows/mcp-auth.md b/pages/plural-features/flows/mcp-auth.md index 37ac8d5e..3c923d71 100644 --- a/pages/plural-features/flows/mcp-auth.md +++ b/pages/plural-features/flows/mcp-auth.md @@ -19,7 +19,7 @@ The authentication mechanism relies on JWTs signed using a standard algorithm. T let publicKey: string | null = null; - async function initializeJWKS() { + async function pollJWKS() { const JWKS_URI = process.env.JWKS_URI || "https://your-console-url/.well-known/jwks.json"; const client = jwksClient({ jwksUri: JWKS_URI }); @@ -30,6 +30,10 @@ The authentication mechanism relies on JWTs signed using a standard algorithm. T publicKey = signingKeys[0].getPublicKey(); } + function initializeJWKS() { + return setInterval(pollJWKS, 30_000) + } + export { initializeJWKS }; ``` @@ -40,7 +44,7 @@ The authentication mechanism relies on JWTs signed using a standard algorithm. T import { authenticateJWT, initializeJWKS } from "./auth.js"; - await initializeJWKS(); + initializeJWKS(); // setup MCP server, prompts, tools, etc @@ -95,15 +99,15 @@ Once a token is successfully authenticated, the server performs authorization ba Here is the core `authenticateJWT` middleware function from `mcp/src/auth.ts`: ```typescript -import jwtPkg from "jsonwebtoken"; +import jwt from "jsonwebtoken"; import type { Request, Response, NextFunction } from "express"; // Assumes publicKey has been initialized by initializeJWKS() -export function authenticateJWT(req: Request, res: Response, next: NextFunction) { - const JWT_AUTH_ENABLED = process.env.JWT_AUTH_ENABLED === "true"; - const REQUIRED_GROUPS = process.env.REQUIRED_GROUPS?.split(",") ?? []; +const REQUIRED_GROUPS = new Set(process.env.REQUIRED_GROUPS?.split(",") ?? []); +const JWT_AUTH_ENABLED = process.env.JWT_AUTH_ENABLED === "true"; +export function authenticateJWT(req: Request, res: Response, next: NextFunction) { if (!JWT_AUTH_ENABLED) return next(); if (!publicKey) return res.status(500).json({ message: "Server not initialized (JWKS public key missing)" }); @@ -114,7 +118,7 @@ export function authenticateJWT(req: Request, res: Response, next: NextFunction) const token = authHeader.split(" ")[1]; try { - const decoded = jwtPkg.verify(token, publicKey); + const decoded = jwt.verify(token, publicKey); const groups = (decoded as any).groups; if (!Array.isArray(groups)) { @@ -122,12 +126,11 @@ export function authenticateJWT(req: Request, res: Response, next: NextFunction) } // Check if user belongs to any required group - if (REQUIRED_GROUPS.length > 0 && !REQUIRED_GROUPS.some(g => groups.includes(g))) { + if (REQUIRED_GROUPS.length > 0 && REQUIRED_GROUPS.isDisjointFrom(new Set(groups))) { return res.status(401).json({ message: "User does not belong to any required group" }); } - (req as any).user = decoded; - next(); // Authentication and Authorization successful + next(); } catch (err) { return res.status(401).json({ message: "Invalid or expired token" }); } diff --git a/src/routing/docs-structure.ts b/src/routing/docs-structure.ts index 7c5a8769..5cb50f63 100644 --- a/src/routing/docs-structure.ts +++ b/src/routing/docs-structure.ts @@ -247,25 +247,14 @@ export const docsStructure: DocSection[] = [ }, ], }, - { - path: 'faq', - title: 'Faq', - sections: [ - { path: 'security', title: 'Is Plural secure?' }, - { path: 'plural-oidc', title: 'Does Plural support OpenID Connect?' }, - { - path: 'certifications', - title: 'What certifications does Plural have?', - }, - { path: 'paid-tiers', title: 'How do Plural paid tiers work?' }, - ], - }, { path: 'resources', title: 'Resources', sections: [ { path: 'release-notes', title: 'Release Notes' }, { path: 'product-updates', title: 'Product Updates' }, + { path: 'security', title: 'Plural Security Certifications' }, + // { path: 'paid-tiers', title: 'Plural Pricing Model' }, { path: 'architecture', title: 'Advanced Architecture',