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

228 statements  

« prev     ^ index     » next       coverage.py v7.4.1, created at 2024-02-05 19:27:17 +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, Id, Notes, ProductGroupIds, Products, References 

13from turvallisuusneuvonta.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 AccessVectorType(Enum): 

185 network = 'NETWORK' 

186 adjacent_network = 'ADJACENT_NETWORK' 

187 local = 'LOCAL' 

188 

189 

190class AccessComplexityType(Enum): 

191 high = 'HIGH' 

192 medium = 'MEDIUM' 

193 low = 'LOW' 

194 

195 

196class AuthenticationType(Enum): 

197 multiple = 'MULTIPLE' 

198 single = 'SINGLE' 

199 none = 'NONE' 

200 

201 

202class CiaType(Enum): 

203 none = 'NONE' 

204 low = 'LOW' 

205 high = 'HIGH' 

206 

207 

208class ExploitabilityType(Enum): 

209 unproven = 'UNPROVEN' 

210 proof_of_concept = 'PROOF_OF_CONCEPT' 

211 functional = 'FUNCTIONAL' 

212 high = 'HIGH' 

213 not_defined = 'NOT_DEFINED' 

214 

215 

216class RemediationLevelType(Enum): 

217 official_fix = 'OFFICIAL_FIX' 

218 temporary_fix = 'TEMPORARY_FIX' 

219 workaround = 'WORKAROUND' 

220 unavailable = 'UNAVAILABLE' 

221 not_defined = 'NOT_DEFINED' 

222 

223 

224class ReportConfidenceType(Enum): 

225 unconfirmed = 'UNCONFIRMED' 

226 uncorroborated = 'UNCORROBORATED' 

227 confirmed = 'CONFIRMED' 

228 not_defined = 'NOT_DEFINED' 

229 

230 

231class CollateralDamagePotentialType(Enum): 

232 none = 'NONE' 

233 low = 'LOW' 

234 low_medium = 'LOW_MEDIUM' 

235 medium_high = 'MEDIUM_HIGH' 

236 high = 'HIGH' 

237 not_defined = 'NOT_DEFINED' 

238 

239 

240class TargetDistributionType(Enum): 

241 none = 'NONE' 

242 low = 'LOW' 

243 medium = 'MEDIUM' 

244 high = 'HIGH' 

245 not_defined = 'NOT_DEFINED' 

246 

247 

248class CiaRequirementType(Enum): 

249 low = 'LOW' 

250 medium = 'MEDIUM' 

251 high = 'HIGH' 

252 not_defined = 'NOT_DEFINED' 

253 

254 

255class ScoreType(BaseModel): 

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

257 

258 

259class AttackVectorType(Enum): 

260 network = 'NETWORK' 

261 adjacent_network = 'ADJACENT_NETWORK' 

262 local = 'LOCAL' 

263 physical = 'PHYSICAL' 

264 

265 

266class ModifiedAttackVectorType(Enum): 

267 network = 'NETWORK' 

268 adjacent_network = 'ADJACENT_NETWORK' 

269 local = 'LOCAL' 

270 physical = 'PHYSICAL' 

271 not_defined = 'NOT_DEFINED' 

272 

273 

274class AttackComplexityType(Enum): 

275 high = 'HIGH' 

276 low = 'LOW' 

277 

278 

279class ModifiedAttackComplexityType(Enum): 

280 high = 'HIGH' 

281 low = 'LOW' 

282 not_defined = 'NOT_DEFINED' 

283 

284 

285class PrivilegesRequiredType(Enum): 

286 high = 'HIGH' 

287 low = 'LOW' 

288 none = 'NONE' 

289 

290 

291class ModifiedPrivilegesRequiredType(Enum): 

292 high = 'HIGH' 

293 low = 'LOW' 

294 none = 'NONE' 

295 not_defined = 'NOT_DEFINED' 

296 

297 

298class UserInteractionType(Enum): 

299 none = 'NONE' 

300 required = 'REQUIRED' 

301 

302 

303class ModifiedUserInteractionType(Enum): 

304 none = 'NONE' 

305 required = 'REQUIRED' 

306 not_defined = 'NOT_DEFINED' 

307 

308 

309class ScopeType(Enum): 

310 unchanged = 'UNCHANGED' 

311 changed = 'CHANGED' 

312 

313 

314class ModifiedScopeType(Enum): 

315 unchanged = 'UNCHANGED' 

316 changed = 'CHANGED' 

317 not_defined = 'NOT_DEFINED' 

318 

319 

320class CiaTypeModel(Enum): 

321 none = 'NONE' 

322 low = 'LOW' 

323 high = 'HIGH' 

324 

325 

326class ModifiedCiaType(Enum): 

327 none = 'NONE' 

328 low = 'LOW' 

329 high = 'HIGH' 

330 not_defined = 'NOT_DEFINED' 

331 

332 

333class ConfidenceType(Enum): 

334 unknown = 'UNKNOWN' 

335 reasonable = 'REASONABLE' 

336 confirmed = 'CONFIRMED' 

337 not_defined = 'NOT_DEFINED' 

338 

339 

340class ScoreTypeModel(ScoreType): 

341 pass 

342 

343 

344class SeverityType(Enum): 

345 none = 'NONE' 

346 low = 'LOW' 

347 medium = 'MEDIUM' 

348 high = 'HIGH' 

349 critical = 'CRITICAL' 

350 

351 

352class ScoreTypeModel1(ScoreType): 

353 pass 

354 

355 

356class Remediation(BaseModel): 

357 """ 

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

359 """ 

360 

361 category: Annotated[ 

362 RemediationCategory, 

363 Field( 

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

365 title='Category of the remediation', 

366 ), 

367 ] 

368 date: Annotated[ 

369 Optional[datetime], 

370 Field( 

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

372 title='Date of the remediation', 

373 ), 

374 ] = None 

375 details: Annotated[ 

376 str, 

377 Field( 

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

379 min_length=1, 

380 title='Details of the remediation', 

381 ), 

382 ] 

383 entitlements: Annotated[ 

384 Optional[List[Entitlement]], 

385 Field( 

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

387 min_length=1, 

388 title='List of entitlements', 

389 ), 

390 ] = None 

391 group_ids: Optional[ProductGroupIds] = None 

392 product_ids: Optional[Products] = None 

393 restart_required: Annotated[ 

394 Optional[RestartRequired], 

395 Field( 

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

397 ' become effective.', 

398 title='Restart required by remediation', 

399 ), 

400 ] = None 

401 url: Annotated[ 

402 Optional[AnyUrl], 

403 Field( 

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

405 title='URL to the remediation', 

406 ), 

407 ] = None 

408 

409 @classmethod 

410 @no_type_check 

411 @field_validator('entitlements') 

412 def check_len(cls, v): 

413 if not v: 

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

415 return v 

416 

417 

418class Threat(BaseModel): 

419 """ 

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

421 information becomes available. 

422 """ 

423 

424 category: Annotated[ 

425 ThreatCategory, 

426 Field( 

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

428 title='Category of the threat', 

429 ), 

430 ] 

431 date: Annotated[ 

432 Optional[datetime], 

433 Field( 

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

435 title='Date of the threat', 

436 ), 

437 ] = None 

438 details: Annotated[ 

439 str, 

440 Field( 

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

442 min_length=1, 

443 title='Details of the threat', 

444 ), 

445 ] 

446 group_ids: Optional[ProductGroupIds] = None 

447 product_ids: Optional[Products] = None 

448 

449 

450class Score(BaseModel): 

451 """ 

452 specifies information about (at least one) score of the vulnerability and for 

453 which products the given value applies. 

454 """ 

455 

456 cvss_v2: Optional[CVSS2] = None 

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

458 products: Products 

459 

460 @no_type_check 

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

462 kwargs.setdefault('by_alias', True) 

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

464 

465 

466class Vulnerability(BaseModel): 

467 """ 

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

469 """ 

470 

471 acknowledgments: Annotated[ 

472 Optional[Acknowledgments], 

473 Field( 

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

475 title='Vulnerability acknowledgments', 

476 ), 

477 ] = None 

478 cve: Annotated[ 

479 Optional[str], 

480 Field( 

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

482 ' the vulnerability.', 

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

484 title='CVE', 

485 ), 

486 ] = None 

487 cwe: Annotated[ 

488 Optional[Cwe], 

489 Field( 

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

491 title='CWE', 

492 ), 

493 ] = None 

494 discovery_date: Annotated[ 

495 Optional[datetime], 

496 Field( 

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

498 title='Discovery date', 

499 ), 

500 ] = None 

501 id: Annotated[ 

502 Optional[Id], 

503 Field( 

504 description='Gives the document producer a place to publish a unique label or tracking ID for the' 

505 ' vulnerability (if such information exists).', 

506 title='ID', 

507 ), 

508 ] = None 

509 involvements: Annotated[ 

510 Optional[List[Involvement]], 

511 Field( 

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

513 min_length=1, 

514 title='List of involvements', 

515 ), 

516 ] = None 

517 notes: Annotated[ 

518 Optional[Notes], 

519 Field( 

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

521 title='Vulnerability notes', 

522 ), 

523 ] = None 

524 product_status: Annotated[ 

525 Optional[ProductStatus], 

526 Field( 

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

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

529 title='Product status', 

530 ), 

531 ] = None 

532 references: Annotated[ 

533 Optional[References], 

534 Field( 

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

536 title='Vulnerability references', 

537 ), 

538 ] = None 

539 release_date: Annotated[ 

540 Optional[datetime], 

541 Field( 

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

543 title='Release date', 

544 ), 

545 ] = None 

546 remediations: Annotated[ 

547 Optional[List[Remediation]], 

548 Field( 

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

550 min_length=1, 

551 title='List of remediations', 

552 ), 

553 ] = None 

554 scores: Annotated[ 

555 Optional[List[Score]], 

556 Field( 

557 description='contains score objects for the current vulnerability.', 

558 min_length=1, 

559 title='List of scores', 

560 ), 

561 ] = None 

562 threats: Annotated[ 

563 Optional[List[Threat]], 

564 Field( 

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

566 min_length=1, 

567 title='List of threats', 

568 ), 

569 ] = None 

570 title: Annotated[ 

571 Optional[str], 

572 Field( 

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

574 ' the vulnerability.', 

575 min_length=1, 

576 title='Title', 

577 ), 

578 ] = None 

579 

580 @no_type_check 

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

582 kwargs.setdefault('by_alias', True) 

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

584 

585 @classmethod 

586 @no_type_check 

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

588 def check_len(cls, v): 

589 if not v: 

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

591 return v