main
vue 275 lines 8.33 KB
Raw
1 <template>
2 <div class="flex flex-col gap-4 pb-1">
3 <!-- Policy Information -->
4 <PropsList :list="infoFields" embedded title="Policy Information" />
5
6 <!-- Description -->
7 <CardKV>
8 <template #key>Description</template>
9 <template #value>{{ policy.description }}</template>
10 </CardKV>
11
12 <!-- Deployment Instructions -->
13 <CardKV>
14 <template #key>
15 <div class="flex items-center gap-2">
16 <Icon :name="DeployIcon" :size="14" />
17 <span>Deployment Instructions</span>
18 </div>
19 </template>
20 <template #value>
21 <div class="flex flex-col gap-2 text-sm">
22 <p>
23 Deploy this policy to a Wazuh agent by downloading the
24 <code>.yml</code>
25 file to
26 <code>/var/ossec/ruleset/sca/</code>
27 , setting the correct ownership, and restarting the agent:
28 </p>
29 <CodeSource :code="deploymentCommands" lang="bash" />
30 </div>
31 </template>
32 </CardKV>
33
34 <!-- Agent Detection -->
35 <CardKV>
36 <template #key>
37 <div class="flex items-center justify-between gap-2">
38 <div class="flex items-center gap-2">
39 <Icon :name="AgentsIcon" :size="14" />
40 <span>Agents with {{ policy.application }}</span>
41 </div>
42 <n-button
43 v-if="!agentsLoaded"
44 size="tiny"
45 type="primary"
46 secondary
47 :loading="loadingAgents"
48 @click="detectAgents"
49 >
50 <template #icon>
51 <Icon :name="SearchIcon" />
52 </template>
53 Detect Agents
54 </n-button>
55 </div>
56 </template>
57 <template #value>
58 <n-spin :show="loadingAgents">
59 <div v-if="agentsLoaded">
60 <div v-if="agentsResponse && agentsResponse.matched_agents.length" class="flex flex-col gap-3">
61 <div class="text-xs opacity-60">
62 Found
63 <strong>{{ agentsResponse.total }}</strong>
64 agent(s) with
65 <strong>{{ agentsResponse.display_name }}</strong>
66 installed
67 </div>
68 <div class="grid grid-cols-1 gap-3 py-1 lg:grid-cols-2">
69 <CardEntity
70 v-for="agent in agentsResponse.matched_agents"
71 :key="`${agent.agent_id}-${agent.package_name}`"
72 embedded
73 size="small"
74 class="h-full"
75 main-box-class="grow"
76 card-entity-wrapper-class="h-full"
77 >
78 <template #headerMain>
79 <div class="text-default flex items-center gap-2">
80 <span class="text-sm font-semibold">
81 {{ agent.agent_name || "Unknown" }}
82 </span>
83 <Badge size="small">
84 <template #value>ID: {{ agent.agent_id }}</template>
85 </Badge>
86 </div>
87 </template>
88 <template #default>
89 <div class="flex flex-wrap gap-2 text-xs">
90 <Badge type="splitted" size="small">
91 <template #label>Package</template>
92 <template #value>{{ agent.package_name }}</template>
93 </Badge>
94 <Badge v-if="agent.package_version" type="splitted" size="small">
95 <template #label>Version</template>
96 <template #value>{{ agent.package_version }}</template>
97 </Badge>
98 <Badge v-if="agent.package_architecture" type="splitted" size="small">
99 <template #label>Arch</template>
100 <template #value>{{ agent.package_architecture }}</template>
101 </Badge>
102 </div>
103 </template>
104 </CardEntity>
105 </div>
106 </div>
107 <n-empty
108 v-else
109 description="No agents found with this package installed"
110 class="h-24 justify-center"
111 />
112 </div>
113 <div v-else class="text-secondary text-sm">
114 Click "Detect Agents" to search for agents running {{ policy.application }}
115 </div>
116 </n-spin>
117 </template>
118 </CardKV>
119
120 <!-- Applicable Policies -->
121 <CardKV v-if="agentsResponse?.applicable_policies?.length">
122 <template #key>Applicable SCA Policies</template>
123 <template #value>
124 <div class="flex flex-wrap gap-2">
125 <Badge v-for="ap of agentsResponse.applicable_policies" :key="ap.id" color="primary">
126 <template #value>{{ ap.name }}</template>
127 </Badge>
128 </div>
129 </template>
130 </CardKV>
131
132 <!-- Policy YAML Content -->
133 <CardKV>
134 <template #key>
135 <div class="flex items-center justify-between gap-2">
136 <div class="flex items-center gap-2">
137 <Icon :name="CodeIcon" :size="14" />
138 <span>Policy YAML</span>
139 </div>
140 <n-button
141 v-if="!yamlLoaded"
142 size="tiny"
143 type="primary"
144 secondary
145 :loading="loadingYaml"
146 @click="loadYamlContent"
147 >
148 <template #icon>
149 <Icon :name="DownloadIcon" />
150 </template>
151 Load YAML
152 </n-button>
153 </div>
154 </template>
155 <template #value>
156 <n-spin :show="loadingYaml">
157 <div v-if="yamlLoaded">
158 <CodeSource v-if="yamlContent" :code="yamlContent" lang="yaml" />
159 <n-empty v-else description="Failed to load YAML content" class="h-24 justify-center" />
160 </div>
161 <div v-else class="text-secondary text-sm">Click "Load YAML" to preview the policy content</div>
162 </n-spin>
163 </template>
164 </CardKV>
165 </div>
166 </template>
167
168 <script setup lang="ts">
169 import type { ScaPackageAgentsResponse, ScaPolicyItem } from "@/types/sca.d"
170 import { NButton, NEmpty, NSpin, useMessage } from "naive-ui"
171 import { computed, ref } from "vue"
172 import Api from "@/api"
173 import Badge from "@/components/common/Badge.vue"
174 import CardEntity from "@/components/common/cards/CardEntity.vue"
175 import CardKV from "@/components/common/cards/CardKV.vue"
176 import CodeSource from "@/components/common/CodeSource.vue"
177 import Icon from "@/components/common/Icon.vue"
178 import PropsList from "@/components/common/PropsList.vue"
179
180 const props = defineProps<{ policy: ScaPolicyItem }>()
181
182 const message = useMessage()
183
184 const loadingAgents = ref(false)
185 const agentsLoaded = ref(false)
186 const agentsResponse = ref<ScaPackageAgentsResponse | null>(null)
187
188 const loadingYaml = ref(false)
189 const yamlLoaded = ref(false)
190 const yamlContent = ref<string | null>(null)
191
192 const AgentsIcon = "carbon:devices"
193 const SearchIcon = "carbon:search"
194 const CodeIcon = "carbon:code"
195 const DownloadIcon = "carbon:download"
196 const DeployIcon = "carbon:deploy"
197
198 const fileName = computed(() => props.policy.file.split("/").pop())
199
200 const infoFields = computed(() => ({
201 id: props.policy.id,
202 application: props.policy.application,
203 app_version: props.policy.app_version,
204 platform: props.policy.platform,
205 cis_version: props.policy.cis_version
206 }))
207
208 const deploymentCommands = computed(() =>
209 [
210 `# Download the SCA policy`,
211 `wget https://raw.githubusercontent.com/socfortress/CoPilot-SCA/main/${props.policy.file} -O /var/ossec/ruleset/sca/${fileName.value}`,
212 ``,
213 `# Set correct ownership`,
214 `chown root:wazuh /var/ossec/ruleset/sca/${fileName.value}`,
215 ``,
216 `# Restart the Wazuh agent`,
217 `systemctl restart wazuh-agent`,
218 ``,
219 `# Verify the policy is loaded`,
220 `tail -f /var/ossec/logs/ossec.log`
221 ].join("\n")
222 )
223
224 function getRegistryKey(application: string): string | null {
225 const app = application.toLowerCase()
226 if (app.includes("apache")) return "apache"
227 if (app.includes("nginx")) return "nginx"
228 if (app.includes("iis")) return "iis"
229 if (app.includes("mysql") || app.includes("mariadb")) return "mysql"
230 if (app.includes("postgresql") || app.includes("postgres")) return "postgresql"
231 if (app === "sqlserver" || app.includes("sql server") || app.includes("mssql")) return "sqlserver"
232 return null
233 }
234
235 async function detectAgents() {
236 const registryKey = getRegistryKey(props.policy.application)
237 if (!registryKey) {
238 message.warning(`No package registry mapping for "${props.policy.application}"`)
239 agentsLoaded.value = true
240 return
241 }
242
243 loadingAgents.value = true
244 try {
245 const res = await Api.sca.getAgentsForPackage(registryKey)
246 if (res.data.success) {
247 agentsResponse.value = res.data
248 } else {
249 message.warning(res.data?.message || "Failed to detect agents")
250 }
251 } catch (err: any) {
252 message.error(err.response?.data?.message || "Failed to detect agents")
253 } finally {
254 loadingAgents.value = false
255 agentsLoaded.value = true
256 }
257 }
258
259 async function loadYamlContent() {
260 loadingYaml.value = true
261 try {
262 const res = await Api.sca.getPolicyContent(props.policy.id)
263 if (res.data.success) {
264 yamlContent.value = res.data.content
265 } else {
266 message.warning(res.data?.message || "Failed to load policy content")
267 }
268 } catch (err: any) {
269 message.error(err.response?.data?.message || "Failed to load policy content")
270 } finally {
271 loadingYaml.value = false
272 yamlLoaded.value = true
273 }
274 }
275 </script>