diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 5418639d0b..6e5521eb28 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -8,6 +8,7 @@ All notable changes to the **Prowler UI** are documented in this file. - `Cloud Provider` type filter to providers page [(#8473)](https://github.com/prowler-cloud/prowler/pull/8473) - New menu item under Configuration section for quick access to the Mutelist [(#8444)](https://github.com/prowler-cloud/prowler/pull/8444) +- Resource agent to Lighthouse for querying resource information [(#8509)](https://github.com/prowler-cloud/prowler/pull/8509) ### 🔄 Changed @@ -15,6 +16,7 @@ All notable changes to the **Prowler UI** are documented in this file. - Provider connection filter now shows "Connected/Disconnected" instead of "true/false" for better UX [(#8520)](https://github.com/prowler-cloud/prowler/pull/8520) ### 🐞 Fixed + - DataTable column headers set to single-line [(#8480)](https://github.com/prowler-cloud/prowler/pull/8480) ### ❌ Removed @@ -29,7 +31,6 @@ All notable changes to the **Prowler UI** are documented in this file. - `GitHub` submenu to High Risk Findings [(#8488)](https://github.com/prowler-cloud/prowler/pull/8488) - Improved Overview chart `Findings by Severity` spacing [(#8491)](https://github.com/prowler-cloud/prowler/pull/8491) - ## [1.10.0] (Prowler v5.10.0) ### 🚀 Added diff --git a/ui/actions/lighthouse/resources.ts b/ui/actions/lighthouse/resources.ts index 21beaabc19..cf763909e0 100644 --- a/ui/actions/lighthouse/resources.ts +++ b/ui/actions/lighthouse/resources.ts @@ -1,12 +1,18 @@ import { apiBaseUrl, getAuthHeaders, parseStringify } from "@/lib/helper"; -export async function getLighthouseResources( - page: number = 1, - query: string = "", - sort: string = "", - filters: any = {}, - fields: string[] = [], -) { +export async function getLighthouseResources({ + page = 1, + query = "", + sort = "", + filters = {}, + fields = [], +}: { + page?: number; + query?: string; + sort?: string; + filters?: any; + fields?: string[]; +}) { const headers = await getAuthHeaders({ contentType: false }); const url = new URL(`${apiBaseUrl}/resources`); @@ -29,7 +35,7 @@ export async function getLighthouseResources( if (filters) { for (const [key, value] of Object.entries(filters)) { - url.searchParams.append(`filter[${key}]`, value as string); + url.searchParams.append(`${key}`, value as string); } } @@ -46,11 +52,67 @@ export async function getLighthouseResources( } } -export async function getLighthouseResourceById( - id: string, - fields: string[] = [], - include: string[] = [], -) { +export async function getLighthouseLatestResources({ + page = 1, + query = "", + sort = "", + filters = {}, + fields = [], +}: { + page?: number; + query?: string; + sort?: string; + filters?: any; + fields?: string[]; +}) { + const headers = await getAuthHeaders({ contentType: false }); + + const url = new URL(`${apiBaseUrl}/resources/latest`); + + if (page) { + url.searchParams.append("page[number]", page.toString()); + } + + if (sort) { + url.searchParams.append("sort", sort); + } + + if (query) { + url.searchParams.append("filter[search]", query); + } + + if (fields.length > 0) { + url.searchParams.append("fields[resources]", fields.join(",")); + } + + if (filters) { + for (const [key, value] of Object.entries(filters)) { + url.searchParams.append(`${key}`, value as string); + } + } + + try { + const response = await fetch(url.toString(), { + headers, + }); + const data = await response.json(); + const parsedData = parseStringify(data); + return parsedData; + } catch (error) { + console.error("Error fetching resources:", error); + return undefined; + } +} + +export async function getLighthouseResourceById({ + id, + fields = [], + include = [], +}: { + id: string; + fields?: string[]; + include?: string[]; +}) { const headers = await getAuthHeaders({ contentType: false }); const url = new URL(`${apiBaseUrl}/resources/${id}`); diff --git a/ui/lib/lighthouse/prompts.ts b/ui/lib/lighthouse/prompts.ts index 318ac1479d..5c5ddfd9d1 100644 --- a/ui/lib/lighthouse/prompts.ts +++ b/ui/lib/lighthouse/prompts.ts @@ -136,6 +136,11 @@ You operate in an agent loop, iterating through these steps: - Fetches available user roles in Prowler - Can get detailed information about the role +### resources_agent + +- Fetches information about resources found during Prowler scans +- Can get detailed information about a specific resource + ## Interacting with Agents - Don't invoke agents if you have the necessary information in your prompt. @@ -469,11 +474,39 @@ const rolesAgentPrompt = `You are Prowler's Roles Agent, specializing in role an - Mentioning all keys in the function call is mandatory. Don't skip any keys. - Don't add empty filters in the function call.`; +const resourcesAgentPrompt = `You are Prowler's Resource Agent, specializing in fetching resource information within Prowler. + +## Available Tools + +- getResourcesTool: List available resource with filtering options +- getResourceTool: Get detailed information about a specific resource by its UUID +- getLatestResourcesTool: List available resources from the latest scans across all providers without scan UUID + +## Response Guidelines + +- Keep the response concise +- Only share information relevant to the query +- Answer directly without unnecessary introductions or conclusions +- Ensure all responses are based on tools' output and information available in the prompt + +## Additional Guidelines + +- Focus only on resource-related information +- Format resource IDs, permissions, and descriptions consistently +- When user asks for resources without a specific scan UUID, use getLatestResourcesTool tool to fetch the resources +- To get the resource UUID, use getResourcesTool if scan UUID is present. If scan UUID is not present, use getLatestResourcesTool. + +## Tool Calling Guidelines + +- Mentioning all keys in the function call is mandatory. Don't skip any keys. +- Don't add empty filters in the function call.`; + export { complianceAgentPrompt, findingsAgentPrompt, overviewAgentPrompt, providerAgentPrompt, + resourcesAgentPrompt, rolesAgentPrompt, scansAgentPrompt, supervisorPrompt, diff --git a/ui/lib/lighthouse/tools/resources.ts b/ui/lib/lighthouse/tools/resources.ts index 62b0bcc2e6..408ad5fc6d 100644 --- a/ui/lib/lighthouse/tools/resources.ts +++ b/ui/lib/lighthouse/tools/resources.ts @@ -1,6 +1,7 @@ import { tool } from "@langchain/core/tools"; import { + getLighthouseLatestResources, getLighthouseResourceById, getLighthouseResources, } from "@/actions/lighthouse/resources"; @@ -8,22 +9,42 @@ import { getResourceSchema, getResourcesSchema } from "@/types/lighthouse"; export const getResourcesTool = tool( async ({ page, query, sort, filters, fields }) => { - return await getLighthouseResources(page, query, sort, filters, fields); + return await getLighthouseResources({ page, query, sort, filters, fields }); }, { name: "getResources", - description: "Fetches all resource information", + description: + "Retrieve a list of all resources found during scans with options for filtering by various criteria. Mandatory to pass in scan UUID.", schema: getResourcesSchema, }, ); export const getResourceTool = tool( async ({ id, fields, include }) => { - return await getLighthouseResourceById(id, fields, include); + return await getLighthouseResourceById({ id, fields, include }); }, { name: "getResource", - description: "Fetches information about a resource by its UUID.", + description: + "Fetch detailed information about a specific resource by their Prowler assigned UUID. A Resource is an object that is discovered by Prowler. It can be anything from a single host to a whole VPC.", schema: getResourceSchema, }, ); + +export const getLatestResourcesTool = tool( + async ({ page, query, sort, filters, fields }) => { + return await getLighthouseLatestResources({ + page, + query, + sort, + filters, + fields, + }); + }, + { + name: "getLatestResources", + description: + "Retrieve a list of the latest resources from the latest scans across all providers with options for filtering by various criteria.", + schema: getResourcesSchema, // Schema is same as getResourcesSchema + }, +); diff --git a/ui/lib/lighthouse/workflow.ts b/ui/lib/lighthouse/workflow.ts index faa7f02f4c..ff5996b95b 100644 --- a/ui/lib/lighthouse/workflow.ts +++ b/ui/lib/lighthouse/workflow.ts @@ -8,6 +8,7 @@ import { findingsAgentPrompt, overviewAgentPrompt, providerAgentPrompt, + resourcesAgentPrompt, rolesAgentPrompt, scansAgentPrompt, supervisorPrompt, @@ -35,6 +36,11 @@ import { getProvidersTool, getProviderTool, } from "@/lib/lighthouse/tools/providers"; +import { + getLatestResourcesTool, + getResourcesTool, + getResourceTool, +} from "@/lib/lighthouse/tools/resources"; import { getRolesTool, getRoleTool } from "@/lib/lighthouse/tools/roles"; import { getScansTool, getScanTool } from "@/lib/lighthouse/tools/scans"; import { @@ -127,6 +133,13 @@ export async function initLighthouseWorkflow() { prompt: rolesAgentPrompt, }); + const resourcesAgent = createReactAgent({ + llm: llm, + tools: [getResourceTool, getResourcesTool, getLatestResourcesTool], + name: "resources_agent", + prompt: resourcesAgentPrompt, + }); + const agents = [ userInfoAgent, providerAgent, @@ -135,6 +148,7 @@ export async function initLighthouseWorkflow() { complianceAgent, findingsAgent, rolesAgent, + resourcesAgent, ]; // Create supervisor workflow diff --git a/ui/types/lighthouse/resources.ts b/ui/types/lighthouse/resources.ts index 7b70dfe96b..354cbb89d0 100644 --- a/ui/types/lighthouse/resources.ts +++ b/ui/types/lighthouse/resources.ts @@ -11,6 +11,7 @@ const resourceFieldsEnum = z.enum([ "tags", "provider", "findings", + "failed_findings_count", "url", "type", ]); @@ -129,6 +130,10 @@ export const getResourcesSchema = z.object({ .optional() .describe("Filter by multiple tags separated by commas."), "filter[type]": z.string().optional().describe("Filter by type."), + "filter[type__icontains]": z + .string() + .optional() + .describe("Filter by substring."), "filter[type__in]": z .string() .optional() @@ -138,18 +143,15 @@ export const getResourcesSchema = z.object({ .string() .optional() .describe("Filter by substring."), - "filter[updated_at]": z - .string() - .optional() - .describe("The uid to filter by."), + "filter[updated_at]": z.string().optional().describe("Filter by date."), "filter[updated_at__gte]": z .string() .optional() - .describe("The uid to filter by."), + .describe("Filter by date greater than or equal to."), "filter[updated_at__lte]": z .string() .optional() - .describe("The uid to filter by."), + .describe("Filter by date less than or equal to."), }) .optional() .describe("The filters to apply to the resources."),