From e301eb58167da40d35e69076e388f9418a470659 Mon Sep 17 00:00:00 2001 From: JohnBlackwell Date: Tue, 22 Apr 2025 13:20:18 -0400 Subject: [PATCH 1/3] mcp auth/authz doc --- pages/plural-features/flows/mcp-auth.md | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 pages/plural-features/flows/mcp-auth.md diff --git a/pages/plural-features/flows/mcp-auth.md b/pages/plural-features/flows/mcp-auth.md new file mode 100644 index 00000000..6e524bf2 --- /dev/null +++ b/pages/plural-features/flows/mcp-auth.md @@ -0,0 +1,40 @@ +--- +title: MCP Server Authentication and Authorization +description: Understanding how Plural authenticates and authorizes requests to custom MCP servers. +--- + +# MCP Server Authentication and Authorization + +When integrating Plural Flows with a custom MCP server, such as the example provided in the `/mcp` folder of the Plural repository, it's crucial to understand how authentication and authorization are handled to secure your operations. Plural leverages JSON Web Tokens (JWTs) for secure communication between the Plural platform and your MCP server. + +## Authentication + +The authentication mechanism relies on JWTs signed using a standard algorithm. The public key required to verify these tokens is fetched from a JSON Web Key Set (JWKS) endpoint provided by your Plural console instance. + +1. **Initialization**: Upon startup, the MCP server (as shown in `mcp/src/index.ts`) calls `initializeJWKS()` (from `mcp/src/auth.ts`). +2. **JWKS Fetching**: The `initializeJWKS` function retrieves the public signing keys from the JWKS URI specified by the `JWKS_URI` environment variable (e.g., `https://your-console-url/.well-known/jwks.json`). It uses the `jwks-rsa` library to fetch and cache the public key. If no signing keys are found, the server fails to start. +3. **Middleware**: The `authenticateJWT` function acts as Express middleware for the `/sse` and `/messages` endpoints. +4. **JWT Verification**: + * It checks if JWT authentication is enabled via the `JWT_AUTH_ENABLED` environment variable. If not enabled, it skips authentication. + * It extracts the Bearer token from the `Authorization` header. + * It verifies the token's signature using the fetched public key (`jsonwebtoken` library). + * If the token is missing, malformed, invalid, or expired, it returns a `401 Unauthorized` response. + +## Authorization + +Once a token is successfully authenticated, the server performs authorization based on group membership claims within the JWT payload. + +1. **Group Claim**: The `authenticateJWT` middleware inspects the decoded JWT payload for a `groups` claim, which should be an array of strings representing the groups the authenticated user belongs to within Plural. +2. **Required Groups**: The server checks the `REQUIRED_GROUPS` environment variable. This variable should contain a comma-separated list of Plural group names that are authorized to interact with this specific MCP server. +3. **Membership Check**: The middleware verifies if the user's `groups` claim contains at least one of the groups listed in `REQUIRED_GROUPS`. +4. **Access Control**: If the user belongs to at least one required group, the request is allowed to proceed (by calling `next()`). Otherwise, a `401 Unauthorized` response is returned, indicating the user lacks the necessary permissions. + +## Configuration + +To enable and configure authentication and authorization in your MCP server based on the `/mcp` example, you need to set the following environment variables: + +* `JWT_AUTH_ENABLED`: Set to `"true"` to enable JWT verification. +* `JWKS_URI`: The full URL to your Plural console's JWKS endpoint (e.g., `https://your-console-url/.well-known/jwks.json`). +* `REQUIRED_GROUPS`: A comma-separated string of Plural group names allowed to access the MCP server (e.g., `"sre,devops"`). + +By implementing this JWT-based authentication and group-based authorization, you ensure that only authorized users and services within your Plural environment can interact with your custom MCP server, maintaining security for your automated operational tasks. \ No newline at end of file From f172df288a61a8df33abe33e97fd9cb2f2bf72b4 Mon Sep 17 00:00:00 2001 From: JohnBlackwell Date: Tue, 22 Apr 2025 13:49:06 -0400 Subject: [PATCH 2/3] link example mcp repo and paste code --- pages/plural-features/flows/mcp-auth.md | 109 +++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/pages/plural-features/flows/mcp-auth.md b/pages/plural-features/flows/mcp-auth.md index 6e524bf2..37ac8d5e 100644 --- a/pages/plural-features/flows/mcp-auth.md +++ b/pages/plural-features/flows/mcp-auth.md @@ -5,7 +5,7 @@ description: Understanding how Plural authenticates and authorizes requests to c # MCP Server Authentication and Authorization -When integrating Plural Flows with a custom MCP server, such as the example provided in the `/mcp` folder of the Plural repository, it's crucial to understand how authentication and authorization are handled to secure your operations. Plural leverages JSON Web Tokens (JWTs) for secure communication between the Plural platform and your MCP server. +When integrating Plural Flows with a custom MCP server, such as the example provided in our [example MCP repository](https://github.com/pluralsh/mcp), it's crucial to understand how authentication and authorization are handled to secure your operations. Plural leverages JSON Web Tokens (JWTs) for secure communication between the Plural platform and your MCP server. ## Authentication @@ -13,7 +13,70 @@ The authentication mechanism relies on JWTs signed using a standard algorithm. T 1. **Initialization**: Upon startup, the MCP server (as shown in `mcp/src/index.ts`) calls `initializeJWKS()` (from `mcp/src/auth.ts`). 2. **JWKS Fetching**: The `initializeJWKS` function retrieves the public signing keys from the JWKS URI specified by the `JWKS_URI` environment variable (e.g., `https://your-console-url/.well-known/jwks.json`). It uses the `jwks-rsa` library to fetch and cache the public key. If no signing keys are found, the server fails to start. -3. **Middleware**: The `authenticateJWT` function acts as Express middleware for the `/sse` and `/messages` endpoints. + + ```typescript + import jwksClient from "jwks-rsa"; + + let publicKey: string | null = null; + + async function initializeJWKS() { + const JWKS_URI = process.env.JWKS_URI || "https://your-console-url/.well-known/jwks.json"; + const client = jwksClient({ jwksUri: JWKS_URI }); + + const signingKeys = await client.getSigningKeys(); + if (signingKeys.length === 0) { + throw new Error("No signing keys found in JWKS"); + } + publicKey = signingKeys[0].getPublicKey(); + } + + export { initializeJWKS }; + ``` + +3. **Middleware**: The `authenticateJWT` function acts as Express middleware for the `/sse` and `/messages` endpoints. This function handles both JWT verification and group-based authorization checks. + + ```typescript + // import express and MCP servers + + import { authenticateJWT, initializeJWKS } from "./auth.js"; + + await initializeJWKS(); + + // setup MCP server, prompts, tools, etc + + const app = express(); + + const transports: { [sessionId: string]: SSEServerTransport } = {}; + + app.get("/sse", authenticateJWT, async (_: Request, res: Response) => { + try { + const transport = new SSEServerTransport('/messages', res); + transports[transport.sessionId] = transport; + res.on("close", () => { + delete transports[transport.sessionId]; + }); + console.error("Starting MCP server.connect with session:", transport.sessionId); + await server.connect(transport); + console.error("MCP connection complete for session:", transport.sessionId); + } catch (err) { + console.error("Error during server.connect:", err); + res.status(500).send("Internal server error"); + } + }); + + app.post("/messages", authenticateJWT, async (req: Request, res: Response) => { + const sessionId = req.query.sessionId as string; + const transport = transports[sessionId]; + if (transport) { + await transport.handlePostMessage(req, res); + } else { + res.status(400).send('No transport found for sessionId'); + } + }); + + console.error("Creating MCP Server on port 3000") + app.listen(3000); + ``` 4. **JWT Verification**: * It checks if JWT authentication is enabled via the `JWT_AUTH_ENABLED` environment variable. If not enabled, it skips authentication. * It extracts the Bearer token from the `Authorization` header. @@ -29,6 +92,48 @@ Once a token is successfully authenticated, the server performs authorization ba 3. **Membership Check**: The middleware verifies if the user's `groups` claim contains at least one of the groups listed in `REQUIRED_GROUPS`. 4. **Access Control**: If the user belongs to at least one required group, the request is allowed to proceed (by calling `next()`). Otherwise, a `401 Unauthorized` response is returned, indicating the user lacks the necessary permissions. +Here is the core `authenticateJWT` middleware function from `mcp/src/auth.ts`: + +```typescript +import jwtPkg 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(",") ?? []; + + if (!JWT_AUTH_ENABLED) return next(); + if (!publicKey) return res.status(500).json({ message: "Server not initialized (JWKS public key missing)" }); + + const authHeader = req.headers.authorization; + if (!authHeader?.startsWith("Bearer ")) { + return res.status(401).json({ message: "Missing or malformed token" }); + } + + const token = authHeader.split(" ")[1]; + try { + const decoded = jwtPkg.verify(token, publicKey); + const groups = (decoded as any).groups; + + if (!Array.isArray(groups)) { + return res.status(401).json({ message: "Missing 'groups' claim in token" }); + } + + // Check if user belongs to any required group + if (REQUIRED_GROUPS.length > 0 && !REQUIRED_GROUPS.some(g => groups.includes(g))) { + return res.status(401).json({ message: "User does not belong to any required group" }); + } + + (req as any).user = decoded; + next(); // Authentication and Authorization successful + } catch (err) { + return res.status(401).json({ message: "Invalid or expired token" }); + } +} +``` + ## Configuration To enable and configure authentication and authorization in your MCP server based on the `/mcp` example, you need to set the following environment variables: From a577a41801ab88cfa9a3a158e668bbffce70b75b Mon Sep 17 00:00:00 2001 From: JohnBlackwell Date: Tue, 22 Apr 2025 15:47:02 -0400 Subject: [PATCH 3/3] docs for establishing SCM webhook and linking prs wrt flows --- .../flows/scm-webhooks-and-pr-linking.md | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 pages/plural-features/flows/scm-webhooks-and-pr-linking.md diff --git a/pages/plural-features/flows/scm-webhooks-and-pr-linking.md b/pages/plural-features/flows/scm-webhooks-and-pr-linking.md new file mode 100644 index 00000000..1a039ecf --- /dev/null +++ b/pages/plural-features/flows/scm-webhooks-and-pr-linking.md @@ -0,0 +1,62 @@ +--- +title: SCM Webhooks and PR Linking for Plural Flows +description: Setting up SCM webhooks and automatically linking Pull Requests to Plural Flows +--- + +# Overview + +Plural utilizes SCM (Source Control Management) webhooks to monitor pull request events and integrate with your development workflow. This allows Plural Flows to automatically link associated pull requests, providing seamless tracking and visibility. + +## Setting up SCM Webhooks + +To enable features like automatic PR status labeling and linking PRs to Plural Flows, you need to configure an SCM webhook. + +### Prerequisites + +* **Plural Console `admin` Permissions** +* **SCM Provider Personal Access Token** (Refer to provider documentation for creation: [Github](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic), [GitLab](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token), [Bitbucket](https://support.atlassian.com/bitbucket-cloud/docs/access-tokens/)) +* **SCM Provider Organization `admin` Permissions** (Required only when creating the webhook initially) + +### Creating the Webhook in Plural + +1. **Navigate to `https://{your-console-domain}/pr/scm-webhooks`** in your Plural Console. +2. **Click the `Create Webhook` Button** at the top right. +3. **Fill in the Required Fields**: + * **Provider Type**: Select the SCM Provider (GitHub, GitLab, Bitbucket). + * **Owner**: The Organization or Group within the SCM Provider. + * **Secret**: A shared secret for webhook verification. You can generate one using `plural crypto random`. + ![Create SCM Webhook Modal Step 1](/images/how-to/create-scm-webhook-modal-0.png) +4. **Click `Create`**. +5. **Copy the generated Webhook URL** and note the secret you provided. + ![Create SCM Webhook Modal Step 2](/images/how-to/create-scm-webhook-modal-1.png) + +### Configuring the Webhook in your SCM Provider + +Now, use the copied Webhook URL and secret to create a webhook within your SCM provider's settings. You can typically configure this at the repository or organization/group level. + +* **Payload URL**: Paste the Webhook URL copied from the Plural Console. +* **Content type**: Set to `application/json`. +* **Secret**: Enter the secret you created in the Plural Console. +* **Events**: Configure the webhook to trigger for **Pull Request** events. Ensure it includes events for opening, closing, reopening, and editing pull requests. + +Refer to your SCM provider's documentation for specific instructions: + +* [Github Repository Webhooks](https://docs.github.com/en/webhooks/using-webhooks/creating-webhooks#creating-a-repository-webhook) +* [GitHub Organization Webhooks](https://docs.github.com/en/webhooks/using-webhooks/creating-webhooks#creating-an-organization-webhook) +* [GitLab Project Webhooks](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#configure-a-webhook-in-gitlab) (Ensure "Pull request events" is checked) +* [GitLab Group Webhooks](https://docs.gitlab.com/ee/user/project/integrations/webhooks.html#group-webhooks) +* [Bitbucket Webhooks](https://confluence.atlassian.com/bitbucketserver/manage-webhooks-938025878.html) (Select "Pull request" > "Created", "Updated", "Merged", "Declined" triggers) + +## Linking Pull Requests to Plural Flows + +Once the SCM webhook is correctly configured, Plural can automatically link pull requests to their corresponding Plural Flows. + +To link a pull request, simply include the following text anywhere in the **pull request body (description)**: + +``` +Plural Flow: {flow-name} +``` + +Replace `{flow-name}` with the exact name of the Plural Flow you want to link the PR to. + +When Plural receives the webhook event for the PR creation or update, it will parse the body, find this text, and establish the link between the PR and the specified Flow within the Plural UI. This allows you to easily navigate between a Flow and its related development work. \ No newline at end of file