Coverage for csaf/vulnerability.py: 94.51%

178 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-18 20:12:48 +00:00

1"""CSAF Vulnerability model.""" 

2 

3from __future__ import annotations 

4 

5from datetime import datetime 

6from enum import Enum 

7from typing import Annotated, List, Optional, Union, no_type_check 

8 

9from pydantic import AnyUrl, BaseModel, Field, RootModel, field_validator 

10 

11from csaf.cvss import CVSS2, CVSS30, CVSS31 

12from csaf.definitions import Acknowledgments, Flags, Ids, Notes, ProductGroupIds, Products, References 

13from csaf.product import ProductStatus 

14 

15 

16class Cwe(BaseModel): 

17 """ 

18 Holds the MITRE standard Common Weakness Enumeration (CWE) for the weakness associated. 

19 """ 

20 

21 id: Annotated[ 

22 str, 

23 Field( 

24 description='Holds the ID for the weakness associated.', 

25 examples=['CWE-22', 'CWE-352', 'CWE-79'], 

26 pattern='^CWE-[1-9]\\d{0,5}$', 

27 title='Weakness ID', 

28 ), 

29 ] 

30 name: Annotated[ 

31 str, 

32 Field( 

33 description='Holds the full name of the weakness as given in the CWE specification.', 

34 examples=[ 

35 'Cross-Site Request Forgery (CSRF)', 

36 "Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')", 

37 "Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')", 

38 ], 

39 min_length=1, 

40 title='Weakness name', 

41 ), 

42 ] 

43 

44 

45class PartyCategory(Enum): 

46 """ 

47 Defines the category of the involved party. 

48 """ 

49 

50 coordinator = 'coordinator' 

51 discoverer = 'discoverer' 

52 other = 'other' 

53 user = 'user' 

54 vendor = 'vendor' 

55 

56 

57class PartyStatus(Enum): 

58 """ 

59 Defines contact status of the involved party. 

60 """ 

61 

62 completed = 'completed' 

63 contact_attempted = 'contact_attempted' 

64 disputed = 'disputed' 

65 in_progress = 'in_progress' 

66 not_contacted = 'not_contacted' 

67 open = 'open' 

68 

69 

70class Involvement(BaseModel): 

71 """ 

72 Is a container, that allows the document producers to comment on the level of involvement (or engagement) 

73 of themselves or third parties in the vulnerability identification, scoping, and remediation process. 

74 """ 

75 

76 date: Annotated[ 

77 Optional[datetime], 

78 Field( 

79 description='Holds the date and time of the involvement entry.', 

80 title='Date of involvement', 

81 ), 

82 ] = None 

83 party: Annotated[ 

84 PartyCategory, 

85 Field( 

86 description='Defines the category of the involved party.', 

87 title='Party category', 

88 ), 

89 ] 

90 status: Annotated[ 

91 PartyStatus, 

92 Field( 

93 description='Defines contact status of the involved party.', 

94 title='Party status', 

95 ), 

96 ] 

97 summary: Annotated[ 

98 Optional[str], 

99 Field( 

100 description='Contains additional context regarding what is going on.', 

101 min_length=1, 

102 title='Summary of the involvement', 

103 ), 

104 ] = None 

105 

106 

107class RemediationCategory(Enum): 

108 """ 

109 Specifies the category which this remediation belongs to. 

110 """ 

111 

112 mitigation = 'mitigation' 

113 no_fix_planned = 'no_fix_planned' 

114 none_available = 'none_available' 

115 vendor_fix = 'vendor_fix' 

116 workaround = 'workaround' 

117 

118 

119class Entitlement( 

120 RootModel[ 

121 Annotated[ 

122 str, 

123 Field( 

124 description='Contains any possible vendor-defined constraints for obtaining fixed software or hardware' 

125 'that fully resolves the vulnerability.', 

126 min_length=1, 

127 title='Entitlement of the remediation', 

128 ), 

129 ] 

130 ] 

131): 

132 pass 

133 

134 

135class RestartRequiredCategory(Enum): 

136 """ 

137 Specifies what category of restart is required by this remediation to become effective. 

138 """ 

139 

140 connected = 'connected' 

141 dependencies = 'dependencies' 

142 machine = 'machine' 

143 none = 'none' 

144 parent = 'parent' 

145 service = 'service' 

146 system = 'system' 

147 vulnerable_component = 'vulnerable_component' 

148 zone = 'zone' 

149 

150 

151class RestartRequired(BaseModel): 

152 """ 

153 Provides information on category of restart is required by this remediation to become effective. 

154 """ 

155 

156 category: Annotated[ 

157 RestartRequiredCategory, 

158 Field( 

159 description='Specifies what category of restart is required by this remediation to become effective.', 

160 title='Category of restart', 

161 ), 

162 ] 

163 details: Annotated[ 

164 Optional[str], 

165 Field( 

166 description='Provides additional information for the restart. This can include details on procedures,' 

167 'scope or impact.', 

168 min_length=1, 

169 title='Additional restart information', 

170 ), 

171 ] = None 

172 

173 

174class ThreatCategory(Enum): 

175 """ 

176 Categorizes the threat according to the rules of the specification. 

177 """ 

178 

179 exploit_status = 'exploit_status' 

180 impact = 'impact' 

181 target_set = 'target_set' 

182 

183 

184class ScoreType(BaseModel): 

185 value: Annotated[float, Field(ge=0.0, le=10.0)] 

186 

187 

188class AttackVectorType(Enum): 

189 network = 'NETWORK' 

190 adjacent_network = 'ADJACENT_NETWORK' 

191 local = 'LOCAL' 

192 physical = 'PHYSICAL' 

193 

194 

195class ModifiedAttackVectorType(Enum): 

196 network = 'NETWORK' 

197 adjacent_network = 'ADJACENT_NETWORK' 

198 local = 'LOCAL' 

199 physical = 'PHYSICAL' 

200 not_defined = 'NOT_DEFINED' 

201 

202 

203class AttackComplexityType(Enum): 

204 high = 'HIGH' 

205 low = 'LOW' 

206 

207 

208class ModifiedAttackComplexityType(Enum): 

209 high = 'HIGH' 

210 low = 'LOW' 

211 not_defined = 'NOT_DEFINED' 

212 

213 

214class PrivilegesRequiredType(Enum): 

215 high = 'HIGH' 

216 low = 'LOW' 

217 none = 'NONE' 

218 

219 

220class ModifiedPrivilegesRequiredType(Enum): 

221 high = 'HIGH' 

222 low = 'LOW' 

223 none = 'NONE' 

224 not_defined = 'NOT_DEFINED' 

225 

226 

227class UserInteractionType(Enum): 

228 none = 'NONE' 

229 required = 'REQUIRED' 

230 

231 

232class ModifiedUserInteractionType(Enum): 

233 none = 'NONE' 

234 required = 'REQUIRED' 

235 not_defined = 'NOT_DEFINED' 

236 

237 

238class ScopeType(Enum): 

239 unchanged = 'UNCHANGED' 

240 changed = 'CHANGED' 

241 

242 

243class ModifiedScopeType(Enum): 

244 unchanged = 'UNCHANGED' 

245 changed = 'CHANGED' 

246 not_defined = 'NOT_DEFINED' 

247 

248 

249class CiaTypeModel(Enum): 

250 none = 'NONE' 

251 low = 'LOW' 

252 high = 'HIGH' 

253 

254 

255class ModifiedCiaType(Enum): 

256 none = 'NONE' 

257 low = 'LOW' 

258 high = 'HIGH' 

259 not_defined = 'NOT_DEFINED' 

260 

261 

262class ConfidenceType(Enum): 

263 unknown = 'UNKNOWN' 

264 reasonable = 'REASONABLE' 

265 confirmed = 'CONFIRMED' 

266 not_defined = 'NOT_DEFINED' 

267 

268 

269class ScoreTypeModel(ScoreType): 

270 pass 

271 

272 

273class SeverityType(Enum): 

274 none = 'NONE' 

275 low = 'LOW' 

276 medium = 'MEDIUM' 

277 high = 'HIGH' 

278 critical = 'CRITICAL' 

279 

280 

281class ScoreTypeModel1(ScoreType): 

282 pass 

283 

284 

285class Remediation(BaseModel): 

286 """ 

287 Specifies details on how to handle (and presumably, fix) a vulnerability. 

288 """ 

289 

290 category: Annotated[ 

291 RemediationCategory, 

292 Field( 

293 description='Specifies the category which this remediation belongs to.', 

294 title='Category of the remediation', 

295 ), 

296 ] 

297 date: Annotated[ 

298 Optional[datetime], 

299 Field( 

300 description='Contains the date from which the remediation is available.', 

301 title='Date of the remediation', 

302 ), 

303 ] = None 

304 details: Annotated[ 

305 str, 

306 Field( 

307 description='Contains a thorough human-readable discussion of the remediation.', 

308 min_length=1, 

309 title='Details of the remediation', 

310 ), 

311 ] 

312 entitlements: Annotated[ 

313 Optional[List[Entitlement]], 

314 Field( 

315 description='Contains a list of entitlements.', 

316 min_length=1, 

317 title='List of entitlements', 

318 ), 

319 ] = None 

320 group_ids: Optional[ProductGroupIds] = None 

321 product_ids: Optional[Products] = None 

322 restart_required: Annotated[ 

323 Optional[RestartRequired], 

324 Field( 

325 description='Provides information on category of restart is required by this remediation to' 

326 ' become effective.', 

327 title='Restart required by remediation', 

328 ), 

329 ] = None 

330 url: Annotated[ 

331 Optional[AnyUrl], 

332 Field( 

333 description='Contains the URL where to obtain the remediation.', 

334 title='URL to the remediation', 

335 ), 

336 ] = None 

337 

338 @classmethod 

339 @no_type_check 

340 @field_validator('entitlements') 

341 def check_len(cls, v): 

342 if not v: 

343 raise ValueError('optional element present but empty') 

344 return v 

345 

346 

347class Threat(BaseModel): 

348 """ 

349 Contains the vulnerability kinetic information. This information can change as the vulnerability ages and new 

350 information becomes available. 

351 """ 

352 

353 category: Annotated[ 

354 ThreatCategory, 

355 Field( 

356 description='Categorizes the threat according to the rules of the specification.', 

357 title='Category of the threat', 

358 ), 

359 ] 

360 date: Annotated[ 

361 Optional[datetime], 

362 Field( 

363 description='Contains the date when the assessment was done or the threat appeared.', 

364 title='Date of the threat', 

365 ), 

366 ] = None 

367 details: Annotated[ 

368 str, 

369 Field( 

370 description='Represents a thorough human-readable discussion of the threat.', 

371 min_length=1, 

372 title='Details of the threat', 

373 ), 

374 ] 

375 group_ids: Optional[ProductGroupIds] = None 

376 product_ids: Optional[Products] = None 

377 

378 

379class Score(BaseModel): 

380 """ 

381 Specifies information about (at least one) score of the vulnerability and for 

382 which products the given value applies. 

383 """ 

384 

385 cvss_v2: Optional[CVSS2] = None 

386 cvss_v3: Optional[Union[CVSS30, CVSS31]] = None 

387 products: Products 

388 

389 @no_type_check 

390 def model_dump_json(self, *args, **kwargs): 

391 kwargs.setdefault('by_alias', True) 

392 return super().model_dump_json(*args, **kwargs) 

393 

394 

395class Vulnerability(BaseModel): 

396 """ 

397 Is a container for the aggregation of all fields that are related to a single vulnerability in the document. 

398 """ 

399 

400 acknowledgments: Annotated[ 

401 Optional[Acknowledgments], 

402 Field( 

403 description='Contains a list of acknowledgment elements associated with this vulnerability item.', 

404 title='Vulnerability acknowledgments', 

405 ), 

406 ] = None 

407 cve: Annotated[ 

408 Optional[str], 

409 Field( 

410 description='Holds the MITRE standard Common Vulnerabilities and Exposures (CVE) tracking number for' 

411 ' the vulnerability.', 

412 pattern='^CVE-[0-9]{4}-[0-9]{4,}$', 

413 title='CVE', 

414 ), 

415 ] = None 

416 cwe: Annotated[ 

417 Optional[Cwe], 

418 Field( 

419 description='Holds the MITRE standard Common Weakness Enumeration (CWE) for the weakness associated.', 

420 title='CWE', 

421 ), 

422 ] = None 

423 discovery_date: Annotated[ 

424 Optional[datetime], 

425 Field( 

426 description='Holds the date and time the vulnerability was originally discovered.', 

427 title='Discovery date', 

428 ), 

429 ] = None 

430 flags: Annotated[ 

431 Optional[Flags], 

432 Field( 

433 description=( 

434 'Contains product specific information in regard to this vulnerability as' 

435 ' a single machine readable flag.' 

436 ), 

437 title='List of flags', 

438 ), 

439 ] = None 

440 ids: Annotated[ 

441 Optional[Ids], 

442 Field( 

443 description=( 

444 'Represents a list of unique labels or tracking IDs for the vulnerability' 

445 ' (if such information exists).' 

446 ), 

447 title='List of IDs', 

448 ), 

449 ] = None 

450 involvements: Annotated[ 

451 Optional[List[Involvement]], 

452 Field( 

453 description='Contains a list of involvements.', 

454 min_length=1, 

455 title='List of involvements', 

456 ), 

457 ] = None 

458 notes: Annotated[ 

459 Optional[Notes], 

460 Field( 

461 description='Holds notes associated with this vulnerability item.', 

462 title='Vulnerability notes', 

463 ), 

464 ] = None 

465 product_status: Annotated[ 

466 Optional[ProductStatus], 

467 Field( 

468 description='Contains different lists of product_ids which provide details on the status of the' 

469 ' referenced product related to the current vulnerability. ', 

470 title='Product status', 

471 ), 

472 ] = None 

473 references: Annotated[ 

474 Optional[References], 

475 Field( 

476 description='Holds a list of references associated with this vulnerability item.', 

477 title='Vulnerability references', 

478 ), 

479 ] = None 

480 release_date: Annotated[ 

481 Optional[datetime], 

482 Field( 

483 description='Holds the date and time the vulnerability was originally released into the wild.', 

484 title='Release date', 

485 ), 

486 ] = None 

487 remediations: Annotated[ 

488 Optional[List[Remediation]], 

489 Field( 

490 description='Contains a list of remediations.', 

491 min_length=1, 

492 title='List of remediations', 

493 ), 

494 ] = None 

495 scores: Annotated[ 

496 Optional[List[Score]], 

497 Field( 

498 description='Contains score objects for the current vulnerability.', 

499 min_length=1, 

500 title='List of scores', 

501 ), 

502 ] = None 

503 threats: Annotated[ 

504 Optional[List[Threat]], 

505 Field( 

506 description='Contains information about a vulnerability that can change with time.', 

507 min_length=1, 

508 title='List of threats', 

509 ), 

510 ] = None 

511 title: Annotated[ 

512 Optional[str], 

513 Field( 

514 description='Gives the document producer the ability to apply a canonical name or title to' 

515 ' the vulnerability.', 

516 min_length=1, 

517 title='Title', 

518 ), 

519 ] = None 

520 

521 @no_type_check 

522 def model_dump_json(self, *args, **kwargs): 

523 kwargs.setdefault('by_alias', True) 

524 return super().model_dump_json(*args, **kwargs) 

525 

526 @classmethod 

527 @no_type_check 

528 @field_validator('involvements', 'remediations', 'scores', 'threats') 

529 def check_len(cls, v): 

530 if not v: 

531 raise ValueError('optional element present but empty') 

532 return v