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
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-18 20:29:38 +00:00
1"""CSAF Vulnerability model."""
3from __future__ import annotations
5from datetime import datetime
6from enum import Enum
7from typing import Annotated, List, Optional, Union, no_type_check
9from pydantic import AnyUrl, BaseModel, Field, RootModel, field_validator
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
17class Cwe(BaseModel):
18 """
19 Holds the MITRE standard Common Weakness Enumeration (CWE) for the weakness associated.
20 """
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 ]
46class PartyCategory(Enum):
47 """
48 Defines the category of the involved party.
49 """
51 coordinator = 'coordinator'
52 discoverer = 'discoverer'
53 other = 'other'
54 user = 'user'
55 vendor = 'vendor'
58class PartyStatus(Enum):
59 """
60 Defines contact status of the involved party.
61 """
63 completed = 'completed'
64 contact_attempted = 'contact_attempted'
65 disputed = 'disputed'
66 in_progress = 'in_progress'
67 not_contacted = 'not_contacted'
68 open = 'open'
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 """
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
108class RemediationCategory(Enum):
109 """
110 Specifies the category which this remediation belongs to.
111 """
113 mitigation = 'mitigation'
114 no_fix_planned = 'no_fix_planned'
115 none_available = 'none_available'
116 vendor_fix = 'vendor_fix'
117 workaround = 'workaround'
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
136class RestartRequiredCategory(Enum):
137 """
138 Specifies what category of restart is required by this remediation to become effective.
139 """
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'
152class RestartRequired(BaseModel):
153 """
154 Provides information on category of restart is required by this remediation to become effective.
155 """
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
175class ThreatCategory(Enum):
176 """
177 Categorizes the threat according to the rules of the specification.
178 """
180 exploit_status = 'exploit_status'
181 impact = 'impact'
182 target_set = 'target_set'
185class AccessVectorType(Enum):
186 network = 'NETWORK'
187 adjacent_network = 'ADJACENT_NETWORK'
188 local = 'LOCAL'
191class AccessComplexityType(Enum):
192 high = 'HIGH'
193 medium = 'MEDIUM'
194 low = 'LOW'
197class AuthenticationType(Enum):
198 multiple = 'MULTIPLE'
199 single = 'SINGLE'
200 none = 'NONE'
203class CiaType(Enum):
204 none = 'NONE'
205 low = 'LOW'
206 high = 'HIGH'
209class ExploitabilityType(Enum):
210 unproven = 'UNPROVEN'
211 proof_of_concept = 'PROOF_OF_CONCEPT'
212 functional = 'FUNCTIONAL'
213 high = 'HIGH'
214 not_defined = 'NOT_DEFINED'
217class RemediationLevelType(Enum):
218 official_fix = 'OFFICIAL_FIX'
219 temporary_fix = 'TEMPORARY_FIX'
220 workaround = 'WORKAROUND'
221 unavailable = 'UNAVAILABLE'
222 not_defined = 'NOT_DEFINED'
225class ReportConfidenceType(Enum):
226 unconfirmed = 'UNCONFIRMED'
227 uncorroborated = 'UNCORROBORATED'
228 confirmed = 'CONFIRMED'
229 not_defined = 'NOT_DEFINED'
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'
241class TargetDistributionType(Enum):
242 none = 'NONE'
243 low = 'LOW'
244 medium = 'MEDIUM'
245 high = 'HIGH'
246 not_defined = 'NOT_DEFINED'
249class CiaRequirementType(Enum):
250 low = 'LOW'
251 medium = 'MEDIUM'
252 high = 'HIGH'
253 not_defined = 'NOT_DEFINED'
256class ScoreType(BaseModel):
257 value: Annotated[float, Field(ge=0.0, le=10.0)]
260class AttackVectorType(Enum):
261 network = 'NETWORK'
262 adjacent_network = 'ADJACENT_NETWORK'
263 local = 'LOCAL'
264 physical = 'PHYSICAL'
267class ModifiedAttackVectorType(Enum):
268 network = 'NETWORK'
269 adjacent_network = 'ADJACENT_NETWORK'
270 local = 'LOCAL'
271 physical = 'PHYSICAL'
272 not_defined = 'NOT_DEFINED'
275class AttackComplexityType(Enum):
276 high = 'HIGH'
277 low = 'LOW'
280class ModifiedAttackComplexityType(Enum):
281 high = 'HIGH'
282 low = 'LOW'
283 not_defined = 'NOT_DEFINED'
286class PrivilegesRequiredType(Enum):
287 high = 'HIGH'
288 low = 'LOW'
289 none = 'NONE'
292class ModifiedPrivilegesRequiredType(Enum):
293 high = 'HIGH'
294 low = 'LOW'
295 none = 'NONE'
296 not_defined = 'NOT_DEFINED'
299class UserInteractionType(Enum):
300 none = 'NONE'
301 required = 'REQUIRED'
304class ModifiedUserInteractionType(Enum):
305 none = 'NONE'
306 required = 'REQUIRED'
307 not_defined = 'NOT_DEFINED'
310class ScopeType(Enum):
311 unchanged = 'UNCHANGED'
312 changed = 'CHANGED'
315class ModifiedScopeType(Enum):
316 unchanged = 'UNCHANGED'
317 changed = 'CHANGED'
318 not_defined = 'NOT_DEFINED'
321class CiaTypeModel(Enum):
322 none = 'NONE'
323 low = 'LOW'
324 high = 'HIGH'
327class ModifiedCiaType(Enum):
328 none = 'NONE'
329 low = 'LOW'
330 high = 'HIGH'
331 not_defined = 'NOT_DEFINED'
334class ConfidenceType(Enum):
335 unknown = 'UNKNOWN'
336 reasonable = 'REASONABLE'
337 confirmed = 'CONFIRMED'
338 not_defined = 'NOT_DEFINED'
341class ScoreTypeModel(ScoreType):
342 pass
345class SeverityType(Enum):
346 none = 'NONE'
347 low = 'LOW'
348 medium = 'MEDIUM'
349 high = 'HIGH'
350 critical = 'CRITICAL'
353class ScoreTypeModel1(ScoreType):
354 pass
357class Remediation(BaseModel):
358 """
359 Specifies details on how to handle (and presumably, fix) a vulnerability.
360 """
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
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
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 """
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
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 """
457 cvss_v2: Optional[CVSS2] = None
458 cvss_v3: Optional[Union[CVSS30, CVSS31]] = None
459 products: Products
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)
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 """
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
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)
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