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
« prev ^ index » next coverage.py v7.4.1, created at 2024-02-05 19:27:17 +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, Id, Notes, ProductGroupIds, Products, References
13from turvallisuusneuvonta.csaf.product import ProductStatus
16class Cwe(BaseModel):
17 """
18 Holds the MITRE standard Common Weakness Enumeration (CWE) for the weakness associated.
19 """
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 ]
45class PartyCategory(Enum):
46 """
47 Defines the category of the involved party.
48 """
50 coordinator = 'coordinator'
51 discoverer = 'discoverer'
52 other = 'other'
53 user = 'user'
54 vendor = 'vendor'
57class PartyStatus(Enum):
58 """
59 Defines contact status of the involved party.
60 """
62 completed = 'completed'
63 contact_attempted = 'contact_attempted'
64 disputed = 'disputed'
65 in_progress = 'in_progress'
66 not_contacted = 'not_contacted'
67 open = 'open'
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 """
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
107class RemediationCategory(Enum):
108 """
109 Specifies the category which this remediation belongs to.
110 """
112 mitigation = 'mitigation'
113 no_fix_planned = 'no_fix_planned'
114 none_available = 'none_available'
115 vendor_fix = 'vendor_fix'
116 workaround = 'workaround'
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
135class RestartRequiredCategory(Enum):
136 """
137 Specifies what category of restart is required by this remediation to become effective.
138 """
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'
151class RestartRequired(BaseModel):
152 """
153 Provides information on category of restart is required by this remediation to become effective.
154 """
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
174class ThreatCategory(Enum):
175 """
176 Categorizes the threat according to the rules of the specification.
177 """
179 exploit_status = 'exploit_status'
180 impact = 'impact'
181 target_set = 'target_set'
184class AccessVectorType(Enum):
185 network = 'NETWORK'
186 adjacent_network = 'ADJACENT_NETWORK'
187 local = 'LOCAL'
190class AccessComplexityType(Enum):
191 high = 'HIGH'
192 medium = 'MEDIUM'
193 low = 'LOW'
196class AuthenticationType(Enum):
197 multiple = 'MULTIPLE'
198 single = 'SINGLE'
199 none = 'NONE'
202class CiaType(Enum):
203 none = 'NONE'
204 low = 'LOW'
205 high = 'HIGH'
208class ExploitabilityType(Enum):
209 unproven = 'UNPROVEN'
210 proof_of_concept = 'PROOF_OF_CONCEPT'
211 functional = 'FUNCTIONAL'
212 high = 'HIGH'
213 not_defined = 'NOT_DEFINED'
216class RemediationLevelType(Enum):
217 official_fix = 'OFFICIAL_FIX'
218 temporary_fix = 'TEMPORARY_FIX'
219 workaround = 'WORKAROUND'
220 unavailable = 'UNAVAILABLE'
221 not_defined = 'NOT_DEFINED'
224class ReportConfidenceType(Enum):
225 unconfirmed = 'UNCONFIRMED'
226 uncorroborated = 'UNCORROBORATED'
227 confirmed = 'CONFIRMED'
228 not_defined = 'NOT_DEFINED'
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'
240class TargetDistributionType(Enum):
241 none = 'NONE'
242 low = 'LOW'
243 medium = 'MEDIUM'
244 high = 'HIGH'
245 not_defined = 'NOT_DEFINED'
248class CiaRequirementType(Enum):
249 low = 'LOW'
250 medium = 'MEDIUM'
251 high = 'HIGH'
252 not_defined = 'NOT_DEFINED'
255class ScoreType(BaseModel):
256 value: Annotated[float, Field(ge=0.0, le=10.0)]
259class AttackVectorType(Enum):
260 network = 'NETWORK'
261 adjacent_network = 'ADJACENT_NETWORK'
262 local = 'LOCAL'
263 physical = 'PHYSICAL'
266class ModifiedAttackVectorType(Enum):
267 network = 'NETWORK'
268 adjacent_network = 'ADJACENT_NETWORK'
269 local = 'LOCAL'
270 physical = 'PHYSICAL'
271 not_defined = 'NOT_DEFINED'
274class AttackComplexityType(Enum):
275 high = 'HIGH'
276 low = 'LOW'
279class ModifiedAttackComplexityType(Enum):
280 high = 'HIGH'
281 low = 'LOW'
282 not_defined = 'NOT_DEFINED'
285class PrivilegesRequiredType(Enum):
286 high = 'HIGH'
287 low = 'LOW'
288 none = 'NONE'
291class ModifiedPrivilegesRequiredType(Enum):
292 high = 'HIGH'
293 low = 'LOW'
294 none = 'NONE'
295 not_defined = 'NOT_DEFINED'
298class UserInteractionType(Enum):
299 none = 'NONE'
300 required = 'REQUIRED'
303class ModifiedUserInteractionType(Enum):
304 none = 'NONE'
305 required = 'REQUIRED'
306 not_defined = 'NOT_DEFINED'
309class ScopeType(Enum):
310 unchanged = 'UNCHANGED'
311 changed = 'CHANGED'
314class ModifiedScopeType(Enum):
315 unchanged = 'UNCHANGED'
316 changed = 'CHANGED'
317 not_defined = 'NOT_DEFINED'
320class CiaTypeModel(Enum):
321 none = 'NONE'
322 low = 'LOW'
323 high = 'HIGH'
326class ModifiedCiaType(Enum):
327 none = 'NONE'
328 low = 'LOW'
329 high = 'HIGH'
330 not_defined = 'NOT_DEFINED'
333class ConfidenceType(Enum):
334 unknown = 'UNKNOWN'
335 reasonable = 'REASONABLE'
336 confirmed = 'CONFIRMED'
337 not_defined = 'NOT_DEFINED'
340class ScoreTypeModel(ScoreType):
341 pass
344class SeverityType(Enum):
345 none = 'NONE'
346 low = 'LOW'
347 medium = 'MEDIUM'
348 high = 'HIGH'
349 critical = 'CRITICAL'
352class ScoreTypeModel1(ScoreType):
353 pass
356class Remediation(BaseModel):
357 """
358 Specifies details on how to handle (and presumably, fix) a vulnerability.
359 """
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
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
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 """
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
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 """
456 cvss_v2: Optional[CVSS2] = None
457 cvss_v3: Optional[Union[CVSS30, CVSS31]] = None
458 products: Products
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)
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 """
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
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)
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