Coverage for turvallisuusneuvonta/csaf/vulnerability.py: 94.87%

230 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-18 20:29:38 +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 turvallisuusneuvonta.csaf.cvss.cvss import CVSS2, CVSS30, CVSS31 

12from turvallisuusneuvonta.csaf.definitions import Acknowledgments, Flags, Ids, Notes 

13from turvallisuusneuvonta.csaf.definitions import ProductGroupIds, Products, References 

14from turvallisuusneuvonta.csaf.product import ProductStatus 

15 

16 

17class Cwe(BaseModel): 

18 """ 

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

20 """ 

21 

22 id: Annotated[ 

23 str, 

24 Field( 

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

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

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

28 title='Weakness ID', 

29 ), 

30 ] 

31 name: Annotated[ 

32 str, 

33 Field( 

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

35 examples=[ 

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

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

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

39 ], 

40 min_length=1, 

41 title='Weakness name', 

42 ), 

43 ] 

44 

45 

46class PartyCategory(Enum): 

47 """ 

48 Defines the category of the involved party. 

49 """ 

50 

51 coordinator = 'coordinator' 

52 discoverer = 'discoverer' 

53 other = 'other' 

54 user = 'user' 

55 vendor = 'vendor' 

56 

57 

58class PartyStatus(Enum): 

59 """ 

60 Defines contact status of the involved party. 

61 """ 

62 

63 completed = 'completed' 

64 contact_attempted = 'contact_attempted' 

65 disputed = 'disputed' 

66 in_progress = 'in_progress' 

67 not_contacted = 'not_contacted' 

68 open = 'open' 

69 

70 

71class Involvement(BaseModel): 

72 """ 

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

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

75 """ 

76 

77 date: Annotated[ 

78 Optional[datetime], 

79 Field( 

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

81 title='Date of involvement', 

82 ), 

83 ] = None 

84 party: Annotated[ 

85 PartyCategory, 

86 Field( 

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

88 title='Party category', 

89 ), 

90 ] 

91 status: Annotated[ 

92 PartyStatus, 

93 Field( 

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

95 title='Party status', 

96 ), 

97 ] 

98 summary: Annotated[ 

99 Optional[str], 

100 Field( 

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

102 min_length=1, 

103 title='Summary of the involvement', 

104 ), 

105 ] = None 

106 

107 

108class RemediationCategory(Enum): 

109 """ 

110 Specifies the category which this remediation belongs to. 

111 """ 

112 

113 mitigation = 'mitigation' 

114 no_fix_planned = 'no_fix_planned' 

115 none_available = 'none_available' 

116 vendor_fix = 'vendor_fix' 

117 workaround = 'workaround' 

118 

119 

120class Entitlement( 

121 RootModel[ 

122 Annotated[ 

123 str, 

124 Field( 

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

126 'that fully resolves the vulnerability.', 

127 min_length=1, 

128 title='Entitlement of the remediation', 

129 ), 

130 ] 

131 ] 

132): 

133 pass 

134 

135 

136class RestartRequiredCategory(Enum): 

137 """ 

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

139 """ 

140 

141 connected = 'connected' 

142 dependencies = 'dependencies' 

143 machine = 'machine' 

144 none = 'none' 

145 parent = 'parent' 

146 service = 'service' 

147 system = 'system' 

148 vulnerable_component = 'vulnerable_component' 

149 zone = 'zone' 

150 

151 

152class RestartRequired(BaseModel): 

153 """ 

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

155 """ 

156 

157 category: Annotated[ 

158 RestartRequiredCategory, 

159 Field( 

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

161 title='Category of restart', 

162 ), 

163 ] 

164 details: Annotated[ 

165 Optional[str], 

166 Field( 

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

168 'scope or impact.', 

169 min_length=1, 

170 title='Additional restart information', 

171 ), 

172 ] = None 

173 

174 

175class ThreatCategory(Enum): 

176 """ 

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

178 """ 

179 

180 exploit_status = 'exploit_status' 

181 impact = 'impact' 

182 target_set = 'target_set' 

183 

184 

185class AccessVectorType(Enum): 

186 network = 'NETWORK' 

187 adjacent_network = 'ADJACENT_NETWORK' 

188 local = 'LOCAL' 

189 

190 

191class AccessComplexityType(Enum): 

192 high = 'HIGH' 

193 medium = 'MEDIUM' 

194 low = 'LOW' 

195 

196 

197class AuthenticationType(Enum): 

198 multiple = 'MULTIPLE' 

199 single = 'SINGLE' 

200 none = 'NONE' 

201 

202 

203class CiaType(Enum): 

204 none = 'NONE' 

205 low = 'LOW' 

206 high = 'HIGH' 

207 

208 

209class ExploitabilityType(Enum): 

210 unproven = 'UNPROVEN' 

211 proof_of_concept = 'PROOF_OF_CONCEPT' 

212 functional = 'FUNCTIONAL' 

213 high = 'HIGH' 

214 not_defined = 'NOT_DEFINED' 

215 

216 

217class RemediationLevelType(Enum): 

218 official_fix = 'OFFICIAL_FIX' 

219 temporary_fix = 'TEMPORARY_FIX' 

220 workaround = 'WORKAROUND' 

221 unavailable = 'UNAVAILABLE' 

222 not_defined = 'NOT_DEFINED' 

223 

224 

225class ReportConfidenceType(Enum): 

226 unconfirmed = 'UNCONFIRMED' 

227 uncorroborated = 'UNCORROBORATED' 

228 confirmed = 'CONFIRMED' 

229 not_defined = 'NOT_DEFINED' 

230 

231 

232class CollateralDamagePotentialType(Enum): 

233 none = 'NONE' 

234 low = 'LOW' 

235 low_medium = 'LOW_MEDIUM' 

236 medium_high = 'MEDIUM_HIGH' 

237 high = 'HIGH' 

238 not_defined = 'NOT_DEFINED' 

239 

240 

241class TargetDistributionType(Enum): 

242 none = 'NONE' 

243 low = 'LOW' 

244 medium = 'MEDIUM' 

245 high = 'HIGH' 

246 not_defined = 'NOT_DEFINED' 

247 

248 

249class CiaRequirementType(Enum): 

250 low = 'LOW' 

251 medium = 'MEDIUM' 

252 high = 'HIGH' 

253 not_defined = 'NOT_DEFINED' 

254 

255 

256class ScoreType(BaseModel): 

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

258 

259 

260class AttackVectorType(Enum): 

261 network = 'NETWORK' 

262 adjacent_network = 'ADJACENT_NETWORK' 

263 local = 'LOCAL' 

264 physical = 'PHYSICAL' 

265 

266 

267class ModifiedAttackVectorType(Enum): 

268 network = 'NETWORK' 

269 adjacent_network = 'ADJACENT_NETWORK' 

270 local = 'LOCAL' 

271 physical = 'PHYSICAL' 

272 not_defined = 'NOT_DEFINED' 

273 

274 

275class AttackComplexityType(Enum): 

276 high = 'HIGH' 

277 low = 'LOW' 

278 

279 

280class ModifiedAttackComplexityType(Enum): 

281 high = 'HIGH' 

282 low = 'LOW' 

283 not_defined = 'NOT_DEFINED' 

284 

285 

286class PrivilegesRequiredType(Enum): 

287 high = 'HIGH' 

288 low = 'LOW' 

289 none = 'NONE' 

290 

291 

292class ModifiedPrivilegesRequiredType(Enum): 

293 high = 'HIGH' 

294 low = 'LOW' 

295 none = 'NONE' 

296 not_defined = 'NOT_DEFINED' 

297 

298 

299class UserInteractionType(Enum): 

300 none = 'NONE' 

301 required = 'REQUIRED' 

302 

303 

304class ModifiedUserInteractionType(Enum): 

305 none = 'NONE' 

306 required = 'REQUIRED' 

307 not_defined = 'NOT_DEFINED' 

308 

309 

310class ScopeType(Enum): 

311 unchanged = 'UNCHANGED' 

312 changed = 'CHANGED' 

313 

314 

315class ModifiedScopeType(Enum): 

316 unchanged = 'UNCHANGED' 

317 changed = 'CHANGED' 

318 not_defined = 'NOT_DEFINED' 

319 

320 

321class CiaTypeModel(Enum): 

322 none = 'NONE' 

323 low = 'LOW' 

324 high = 'HIGH' 

325 

326 

327class ModifiedCiaType(Enum): 

328 none = 'NONE' 

329 low = 'LOW' 

330 high = 'HIGH' 

331 not_defined = 'NOT_DEFINED' 

332 

333 

334class ConfidenceType(Enum): 

335 unknown = 'UNKNOWN' 

336 reasonable = 'REASONABLE' 

337 confirmed = 'CONFIRMED' 

338 not_defined = 'NOT_DEFINED' 

339 

340 

341class ScoreTypeModel(ScoreType): 

342 pass 

343 

344 

345class SeverityType(Enum): 

346 none = 'NONE' 

347 low = 'LOW' 

348 medium = 'MEDIUM' 

349 high = 'HIGH' 

350 critical = 'CRITICAL' 

351 

352 

353class ScoreTypeModel1(ScoreType): 

354 pass 

355 

356 

357class Remediation(BaseModel): 

358 """ 

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

360 """ 

361 

362 category: Annotated[ 

363 RemediationCategory, 

364 Field( 

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

366 title='Category of the remediation', 

367 ), 

368 ] 

369 date: Annotated[ 

370 Optional[datetime], 

371 Field( 

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

373 title='Date of the remediation', 

374 ), 

375 ] = None 

376 details: Annotated[ 

377 str, 

378 Field( 

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

380 min_length=1, 

381 title='Details of the remediation', 

382 ), 

383 ] 

384 entitlements: Annotated[ 

385 Optional[List[Entitlement]], 

386 Field( 

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

388 min_length=1, 

389 title='List of entitlements', 

390 ), 

391 ] = None 

392 group_ids: Optional[ProductGroupIds] = None 

393 product_ids: Optional[Products] = None 

394 restart_required: Annotated[ 

395 Optional[RestartRequired], 

396 Field( 

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

398 ' become effective.', 

399 title='Restart required by remediation', 

400 ), 

401 ] = None 

402 url: Annotated[ 

403 Optional[AnyUrl], 

404 Field( 

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

406 title='URL to the remediation', 

407 ), 

408 ] = None 

409 

410 @classmethod 

411 @no_type_check 

412 @field_validator('entitlements') 

413 def check_len(cls, v): 

414 if not v: 

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

416 return v 

417 

418 

419class Threat(BaseModel): 

420 """ 

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

422 information becomes available. 

423 """ 

424 

425 category: Annotated[ 

426 ThreatCategory, 

427 Field( 

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

429 title='Category of the threat', 

430 ), 

431 ] 

432 date: Annotated[ 

433 Optional[datetime], 

434 Field( 

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

436 title='Date of the threat', 

437 ), 

438 ] = None 

439 details: Annotated[ 

440 str, 

441 Field( 

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

443 min_length=1, 

444 title='Details of the threat', 

445 ), 

446 ] 

447 group_ids: Optional[ProductGroupIds] = None 

448 product_ids: Optional[Products] = None 

449 

450 

451class Score(BaseModel): 

452 """ 

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

454 which products the given value applies. 

455 """ 

456 

457 cvss_v2: Optional[CVSS2] = None 

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

459 products: Products 

460 

461 @no_type_check 

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

463 kwargs.setdefault('by_alias', True) 

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

465 

466 

467class Vulnerability(BaseModel): 

468 """ 

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

470 """ 

471 

472 acknowledgments: Annotated[ 

473 Optional[Acknowledgments], 

474 Field( 

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

476 title='Vulnerability acknowledgments', 

477 ), 

478 ] = None 

479 cve: Annotated[ 

480 Optional[str], 

481 Field( 

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

483 ' the vulnerability.', 

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

485 title='CVE', 

486 ), 

487 ] = None 

488 cwe: Annotated[ 

489 Optional[Cwe], 

490 Field( 

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

492 title='CWE', 

493 ), 

494 ] = None 

495 discovery_date: Annotated[ 

496 Optional[datetime], 

497 Field( 

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

499 title='Discovery date', 

500 ), 

501 ] = None 

502 flags: Annotated[ 

503 Optional[Flags], 

504 Field( 

505 description=( 

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

507 ' a single machine readable flag.' 

508 ), 

509 title='List of flags', 

510 ), 

511 ] = None 

512 ids: Annotated[ 

513 Optional[Ids], 

514 Field( 

515 description=( 

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

517 ' (if such information exists).' 

518 ), 

519 title='List of IDs', 

520 ), 

521 ] = None 

522 involvements: Annotated[ 

523 Optional[List[Involvement]], 

524 Field( 

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

526 min_length=1, 

527 title='List of involvements', 

528 ), 

529 ] = None 

530 notes: Annotated[ 

531 Optional[Notes], 

532 Field( 

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

534 title='Vulnerability notes', 

535 ), 

536 ] = None 

537 product_status: Annotated[ 

538 Optional[ProductStatus], 

539 Field( 

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

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

542 title='Product status', 

543 ), 

544 ] = None 

545 references: Annotated[ 

546 Optional[References], 

547 Field( 

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

549 title='Vulnerability references', 

550 ), 

551 ] = None 

552 release_date: Annotated[ 

553 Optional[datetime], 

554 Field( 

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

556 title='Release date', 

557 ), 

558 ] = None 

559 remediations: Annotated[ 

560 Optional[List[Remediation]], 

561 Field( 

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

563 min_length=1, 

564 title='List of remediations', 

565 ), 

566 ] = None 

567 scores: Annotated[ 

568 Optional[List[Score]], 

569 Field( 

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

571 min_length=1, 

572 title='List of scores', 

573 ), 

574 ] = None 

575 threats: Annotated[ 

576 Optional[List[Threat]], 

577 Field( 

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

579 min_length=1, 

580 title='List of threats', 

581 ), 

582 ] = None 

583 title: Annotated[ 

584 Optional[str], 

585 Field( 

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

587 ' the vulnerability.', 

588 min_length=1, 

589 title='Title', 

590 ), 

591 ] = None 

592 

593 @no_type_check 

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

595 kwargs.setdefault('by_alias', True) 

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

597 

598 @classmethod 

599 @no_type_check 

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

601 def check_len(cls, v): 

602 if not v: 

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

604 return v