gitlabcis 1.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (218) hide show
  1. gitlabcis/__init__.py +12 -0
  2. gitlabcis/__main__.py +7 -0
  3. gitlabcis/benchmarks/__init__.py +8 -0
  4. gitlabcis/benchmarks/artifacts_4/__init__.py +4 -0
  5. gitlabcis/benchmarks/artifacts_4/access_to_artifacts_4_2.py +139 -0
  6. gitlabcis/benchmarks/artifacts_4/origin_traceability_4_4.py +11 -0
  7. gitlabcis/benchmarks/artifacts_4/package_registries_4_3.py +105 -0
  8. gitlabcis/benchmarks/artifacts_4/verification_4_1.py +83 -0
  9. gitlabcis/benchmarks/build_pipelines_2/__init__.py +4 -0
  10. gitlabcis/benchmarks/build_pipelines_2/build_environment_2_1.py +268 -0
  11. gitlabcis/benchmarks/build_pipelines_2/build_worker_2_2.py +129 -0
  12. gitlabcis/benchmarks/build_pipelines_2/pipeline_instructions_2_3.py +444 -0
  13. gitlabcis/benchmarks/build_pipelines_2/pipeline_integrity_2_4.py +146 -0
  14. gitlabcis/benchmarks/dependencies_3/__init__.py +2 -0
  15. gitlabcis/benchmarks/dependencies_3/third_party_packages_3_1.py +171 -0
  16. gitlabcis/benchmarks/dependencies_3/validate_packages_3_2.py +182 -0
  17. gitlabcis/benchmarks/deployment_5/__init__.py +2 -0
  18. gitlabcis/benchmarks/deployment_5/deployment_configuration_5_1.py +165 -0
  19. gitlabcis/benchmarks/deployment_5/deployment_environment_5_2.py +66 -0
  20. gitlabcis/benchmarks/source_code_1/__init__.py +6 -0
  21. gitlabcis/benchmarks/source_code_1/code_changes_1_1.py +665 -0
  22. gitlabcis/benchmarks/source_code_1/code_risks_1_5.py +506 -0
  23. gitlabcis/benchmarks/source_code_1/contribution_access_1_3.py +334 -0
  24. gitlabcis/benchmarks/source_code_1/repository_management_1_2.py +168 -0
  25. gitlabcis/benchmarks/source_code_1/third_party_1_4.py +139 -0
  26. gitlabcis/cli/__init__.py +0 -0
  27. gitlabcis/cli/log.py +30 -0
  28. gitlabcis/cli/main.py +541 -0
  29. gitlabcis/cli/output.py +151 -0
  30. gitlabcis/recommendations/artifacts_4/access_to_artifacts_4_2/external_auth_server.yml +51 -0
  31. gitlabcis/recommendations/artifacts_4/access_to_artifacts_4_2/limit_artifact_uploaders.yml +57 -0
  32. gitlabcis/recommendations/artifacts_4/access_to_artifacts_4_2/limit_certifying_artifacts.yml +53 -0
  33. gitlabcis/recommendations/artifacts_4/access_to_artifacts_4_2/minimum_package_registry_admins.yml +54 -0
  34. gitlabcis/recommendations/artifacts_4/access_to_artifacts_4_2/readme.md +14 -0
  35. gitlabcis/recommendations/artifacts_4/access_to_artifacts_4_2/require_mfa_to_package_registry.yml +52 -0
  36. gitlabcis/recommendations/artifacts_4/access_to_artifacts_4_2/restrict_anonymous_access.yml +67 -0
  37. gitlabcis/recommendations/artifacts_4/origin_traceability_4_4/artifact_origin_info.yml +56 -0
  38. gitlabcis/recommendations/artifacts_4/origin_traceability_4_4/readme.md +7 -0
  39. gitlabcis/recommendations/artifacts_4/package_registries_4_3/all_artifact_versions_signed.yml +70 -0
  40. gitlabcis/recommendations/artifacts_4/package_registries_4_3/audit_package_registry_config.yml +46 -0
  41. gitlabcis/recommendations/artifacts_4/package_registries_4_3/readme.md +12 -0
  42. gitlabcis/recommendations/artifacts_4/package_registries_4_3/secure_repo_webhooks.yml +50 -0
  43. gitlabcis/recommendations/artifacts_4/package_registries_4_3/validate_signed_artifacts_on_upload.yml +72 -0
  44. gitlabcis/recommendations/artifacts_4/readme.md +12 -0
  45. gitlabcis/recommendations/artifacts_4/verification_4_1/encrypt_artifacts_before_distribution.yml +47 -0
  46. gitlabcis/recommendations/artifacts_4/verification_4_1/only_authorized_platforms_can_decrypt_artifacts.yml +59 -0
  47. gitlabcis/recommendations/artifacts_4/verification_4_1/readme.md +11 -0
  48. gitlabcis/recommendations/artifacts_4/verification_4_1/sign_artifacts_in_build_pipeline.yml +40 -0
  49. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/authenticate_build_access.yml +55 -0
  50. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/build_automation.yml +54 -0
  51. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/build_env_admins.yml +55 -0
  52. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/build_logging.yml +49 -0
  53. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/disable_build_tools_default_passwords.yml +54 -0
  54. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/immutable_pipeline_infrastructure.yml +60 -0
  55. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/limit_build_access.yml +64 -0
  56. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/limit_build_secrets_scope.yml +56 -0
  57. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/readme.md +19 -0
  58. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/secure_build_env_webhooks.yml +43 -0
  59. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/single_responsibility_pipeline.yml +58 -0
  60. gitlabcis/recommendations/build_pipelines_2/build_environment_2_1/vuln_scanning.yml +64 -0
  61. gitlabcis/recommendations/build_pipelines_2/build_worker_2_2/build_worker_vuln_scanning.yml +58 -0
  62. gitlabcis/recommendations/build_pipelines_2/build_worker_2_2/monitor_worker_resource_consumption.yml +59 -0
  63. gitlabcis/recommendations/build_pipelines_2/build_worker_2_2/pass_worker_envs_and_commands.yml +48 -0
  64. gitlabcis/recommendations/build_pipelines_2/build_worker_2_2/readme.md +16 -0
  65. gitlabcis/recommendations/build_pipelines_2/build_worker_2_2/restrict_worker_connectivity.yml +61 -0
  66. gitlabcis/recommendations/build_pipelines_2/build_worker_2_2/segregate_worker_duties.yml +78 -0
  67. gitlabcis/recommendations/build_pipelines_2/build_worker_2_2/single_use_workers.yml +47 -0
  68. gitlabcis/recommendations/build_pipelines_2/build_worker_2_2/store_worker_config.yml +62 -0
  69. gitlabcis/recommendations/build_pipelines_2/build_worker_2_2/worker_runtime_security.yml +37 -0
  70. gitlabcis/recommendations/build_pipelines_2/pipeline_instructions_2_3/build_stage_io.yml +49 -0
  71. gitlabcis/recommendations/build_pipelines_2/pipeline_instructions_2_3/build_steps_as_code.yml +42 -0
  72. gitlabcis/recommendations/build_pipelines_2/pipeline_instructions_2_3/limit_pipeline_triggers.yml +76 -0
  73. gitlabcis/recommendations/build_pipelines_2/pipeline_instructions_2_3/pipeline_misconfiguration_scanning.yml +48 -0
  74. gitlabcis/recommendations/build_pipelines_2/pipeline_instructions_2_3/pipeline_secret_scanning.yml +56 -0
  75. gitlabcis/recommendations/build_pipelines_2/pipeline_instructions_2_3/pipeline_vuln_scanning.yml +44 -0
  76. gitlabcis/recommendations/build_pipelines_2/pipeline_instructions_2_3/readme.md +16 -0
  77. gitlabcis/recommendations/build_pipelines_2/pipeline_instructions_2_3/secure_pipeline_output.yml +52 -0
  78. gitlabcis/recommendations/build_pipelines_2/pipeline_instructions_2_3/track_pipeline_files.yml +48 -0
  79. gitlabcis/recommendations/build_pipelines_2/pipeline_integrity_2_4/create_reproducible_artifacts.yml +52 -0
  80. gitlabcis/recommendations/build_pipelines_2/pipeline_integrity_2_4/lock_dependencies.yml +59 -0
  81. gitlabcis/recommendations/build_pipelines_2/pipeline_integrity_2_4/pipeline_produces_sbom.yml +81 -0
  82. gitlabcis/recommendations/build_pipelines_2/pipeline_integrity_2_4/pipeline_signs_sbom.yml +38 -0
  83. gitlabcis/recommendations/build_pipelines_2/pipeline_integrity_2_4/readme.md +14 -0
  84. gitlabcis/recommendations/build_pipelines_2/pipeline_integrity_2_4/sign_artifacts.yml +35 -0
  85. gitlabcis/recommendations/build_pipelines_2/pipeline_integrity_2_4/validate_dependencies.yml +63 -0
  86. gitlabcis/recommendations/build_pipelines_2/readme.md +12 -0
  87. gitlabcis/recommendations/dependencies_3/readme.md +10 -0
  88. gitlabcis/recommendations/dependencies_3/third_party_packages_3_1/define_package_managers.yml +84 -0
  89. gitlabcis/recommendations/dependencies_3/third_party_packages_3_1/dependency_sbom.yml +84 -0
  90. gitlabcis/recommendations/dependencies_3/third_party_packages_3_1/monitor_dependencies.yml +61 -0
  91. gitlabcis/recommendations/dependencies_3/third_party_packages_3_1/packages_over_60_days_old.yml +95 -0
  92. gitlabcis/recommendations/dependencies_3/third_party_packages_3_1/pin_dependency_version.yml +48 -0
  93. gitlabcis/recommendations/dependencies_3/third_party_packages_3_1/readme.md +14 -0
  94. gitlabcis/recommendations/dependencies_3/third_party_packages_3_1/third_party_sbom_required.yml +70 -0
  95. gitlabcis/recommendations/dependencies_3/third_party_packages_3_1/verify_artifacts.yml +45 -0
  96. gitlabcis/recommendations/dependencies_3/third_party_packages_3_1/verify_signed_metadata.yml +41 -0
  97. gitlabcis/recommendations/dependencies_3/validate_packages_3_2/org_wide_dependency_policy.yml +47 -0
  98. gitlabcis/recommendations/dependencies_3/validate_packages_3_2/package_license_scanning.yml +47 -0
  99. gitlabcis/recommendations/dependencies_3/validate_packages_3_2/package_ownership_change.yml +42 -0
  100. gitlabcis/recommendations/dependencies_3/validate_packages_3_2/package_vuln_scanning.yml +62 -0
  101. gitlabcis/recommendations/dependencies_3/validate_packages_3_2/readme.md +10 -0
  102. gitlabcis/recommendations/deployment_5/deployment_configuration_5_1/audit_deployment_config.yml +46 -0
  103. gitlabcis/recommendations/deployment_5/deployment_configuration_5_1/limit_deployment_config_access.yml +51 -0
  104. gitlabcis/recommendations/deployment_5/deployment_configuration_5_1/pin_deployment_config_manifests.yml +59 -0
  105. gitlabcis/recommendations/deployment_5/deployment_configuration_5_1/readme.md +13 -0
  106. gitlabcis/recommendations/deployment_5/deployment_configuration_5_1/scan_iac.yml +72 -0
  107. gitlabcis/recommendations/deployment_5/deployment_configuration_5_1/secret_scan_deployment_config.yml +45 -0
  108. gitlabcis/recommendations/deployment_5/deployment_configuration_5_1/separate_deployment_config.yml +50 -0
  109. gitlabcis/recommendations/deployment_5/deployment_configuration_5_1/verify_deployment_config.yml +49 -0
  110. gitlabcis/recommendations/deployment_5/deployment_environment_5_2/automate_deployment.yml +47 -0
  111. gitlabcis/recommendations/deployment_5/deployment_environment_5_2/disable_default_passwords.yml +63 -0
  112. gitlabcis/recommendations/deployment_5/deployment_environment_5_2/limit_prod_access.yml +45 -0
  113. gitlabcis/recommendations/deployment_5/deployment_environment_5_2/readme.md +12 -0
  114. gitlabcis/recommendations/deployment_5/deployment_environment_5_2/reproducible_deployment.yml +50 -0
  115. gitlabcis/recommendations/deployment_5/readme.md +10 -0
  116. gitlabcis/recommendations/readme.md +24 -0
  117. gitlabcis/recommendations/source_code_1/code_changes_1_1/audit_branch_protections.yml +56 -0
  118. gitlabcis/recommendations/source_code_1/code_changes_1_1/auto_risk_scan_merges.yml +62 -0
  119. gitlabcis/recommendations/source_code_1/code_changes_1_1/branch_protections_for_admins.yml +60 -0
  120. gitlabcis/recommendations/source_code_1/code_changes_1_1/branches_updated_before_merging.yml +56 -0
  121. gitlabcis/recommendations/source_code_1/code_changes_1_1/checks_pass_before_merging.yml +57 -0
  122. gitlabcis/recommendations/source_code_1/code_changes_1_1/code_approval_dismissals.yml +62 -0
  123. gitlabcis/recommendations/source_code_1/code_changes_1_1/code_approvals.yml +65 -0
  124. gitlabcis/recommendations/source_code_1/code_changes_1_1/code_changes_require_code_owners.yml +68 -0
  125. gitlabcis/recommendations/source_code_1/code_changes_1_1/code_dismissal_restrictions.yml +69 -0
  126. gitlabcis/recommendations/source_code_1/code_changes_1_1/code_owners.yml +61 -0
  127. gitlabcis/recommendations/source_code_1/code_changes_1_1/code_tracing.yml +52 -0
  128. gitlabcis/recommendations/source_code_1/code_changes_1_1/comments_resolved_before_merging.yml +59 -0
  129. gitlabcis/recommendations/source_code_1/code_changes_1_1/commits_must_be_signed_before_merging.yml +63 -0
  130. gitlabcis/recommendations/source_code_1/code_changes_1_1/default_branch_protected.yml +85 -0
  131. gitlabcis/recommendations/source_code_1/code_changes_1_1/deny_branch_deletions.yml +76 -0
  132. gitlabcis/recommendations/source_code_1/code_changes_1_1/ensure_force_push_is_denied.yml +59 -0
  133. gitlabcis/recommendations/source_code_1/code_changes_1_1/linear_history_required.yml +56 -0
  134. gitlabcis/recommendations/source_code_1/code_changes_1_1/merging_restrictions.yml +65 -0
  135. gitlabcis/recommendations/source_code_1/code_changes_1_1/readme.md +26 -0
  136. gitlabcis/recommendations/source_code_1/code_changes_1_1/stale_branch_reviews.yml +72 -0
  137. gitlabcis/recommendations/source_code_1/code_changes_1_1/version_control.yml +45 -0
  138. gitlabcis/recommendations/source_code_1/code_risks_1_5/dast_api_scanning.yml +50 -0
  139. gitlabcis/recommendations/source_code_1/code_risks_1_5/dast_web_scanning.yml +51 -0
  140. gitlabcis/recommendations/source_code_1/code_risks_1_5/dependency_scanning.yml +84 -0
  141. gitlabcis/recommendations/source_code_1/code_risks_1_5/enable_secret_detection.yml +45 -0
  142. gitlabcis/recommendations/source_code_1/code_risks_1_5/license_scanning.yml +47 -0
  143. gitlabcis/recommendations/source_code_1/code_risks_1_5/readme.md +14 -0
  144. gitlabcis/recommendations/source_code_1/code_risks_1_5/secure_iac_instructions.yml +81 -0
  145. gitlabcis/recommendations/source_code_1/code_risks_1_5/secure_pipeline_instructions.yml +62 -0
  146. gitlabcis/recommendations/source_code_1/code_risks_1_5/vulnerability_scanning.yml +48 -0
  147. gitlabcis/recommendations/source_code_1/contribution_access_1_3/domain_verification.yml +65 -0
  148. gitlabcis/recommendations/source_code_1/contribution_access_1_3/ensure_2_admins_per_repo.yml +56 -0
  149. gitlabcis/recommendations/source_code_1/contribution_access_1_3/limit_top_level_group_creation.yml +61 -0
  150. gitlabcis/recommendations/source_code_1/contribution_access_1_3/limit_user_registration_domain.yml +58 -0
  151. gitlabcis/recommendations/source_code_1/contribution_access_1_3/minimum_number_of_admins.yml +56 -0
  152. gitlabcis/recommendations/source_code_1/contribution_access_1_3/org_provided_ssh_certs.yml +70 -0
  153. gitlabcis/recommendations/source_code_1/contribution_access_1_3/readme.md +21 -0
  154. gitlabcis/recommendations/source_code_1/contribution_access_1_3/require_mfa_at_org_level.yml +89 -0
  155. gitlabcis/recommendations/source_code_1/contribution_access_1_3/require_mfa_for_contributors.yml +76 -0
  156. gitlabcis/recommendations/source_code_1/contribution_access_1_3/restrict_ip_addresses.yml +84 -0
  157. gitlabcis/recommendations/source_code_1/contribution_access_1_3/review_and_remove_inactive_users.yml +62 -0
  158. gitlabcis/recommendations/source_code_1/contribution_access_1_3/scm_notification_restriction.yml +46 -0
  159. gitlabcis/recommendations/source_code_1/contribution_access_1_3/strict_permissions_for_repo.yml +62 -0
  160. gitlabcis/recommendations/source_code_1/contribution_access_1_3/track_code_anomalies.yml +43 -0
  161. gitlabcis/recommendations/source_code_1/readme.md +13 -0
  162. gitlabcis/recommendations/source_code_1/repository_management_1_2/limit_issue_deletions.yml +57 -0
  163. gitlabcis/recommendations/source_code_1/repository_management_1_2/limit_repo_creations.yml +64 -0
  164. gitlabcis/recommendations/source_code_1/repository_management_1_2/limit_repo_deletions.yml +57 -0
  165. gitlabcis/recommendations/source_code_1/repository_management_1_2/public_repos_have_security_file.yml +59 -0
  166. gitlabcis/recommendations/source_code_1/repository_management_1_2/readme.md +15 -0
  167. gitlabcis/recommendations/source_code_1/repository_management_1_2/review_and_archive_stale_repos.yml +65 -0
  168. gitlabcis/recommendations/source_code_1/repository_management_1_2/track_forks.yml +74 -0
  169. gitlabcis/recommendations/source_code_1/repository_management_1_2/track_project_visibility_status.yml +74 -0
  170. gitlabcis/recommendations/source_code_1/third_party_1_4/README.md +12 -0
  171. gitlabcis/recommendations/source_code_1/third_party_1_4/admin_approval_for_app_installs.yml +83 -0
  172. gitlabcis/recommendations/source_code_1/third_party_1_4/least_privilge_app_permissions.yml +103 -0
  173. gitlabcis/recommendations/source_code_1/third_party_1_4/secure_webhooks.yml +73 -0
  174. gitlabcis/recommendations/source_code_1/third_party_1_4/stale_app_reviews.yml +66 -0
  175. gitlabcis/recommendations/template.yml +30 -0
  176. gitlabcis/tests/__init__.py +0 -0
  177. gitlabcis/tests/input/__init__.py +0 -0
  178. gitlabcis/tests/input/conftest.py +29 -0
  179. gitlabcis/tests/input/no_input_test.py +82 -0
  180. gitlabcis/tests/input/switch_test.py +19 -0
  181. gitlabcis/tests/input/version_test.py +7 -0
  182. gitlabcis/tests/unit/__init__.py +0 -0
  183. gitlabcis/tests/unit/benchmarks/artifacts_4/access_to_artifacts_4_2_test.py +131 -0
  184. gitlabcis/tests/unit/benchmarks/artifacts_4/origin_traceability_4_4_test.py +15 -0
  185. gitlabcis/tests/unit/benchmarks/artifacts_4/package_registries_4_3_test.py +102 -0
  186. gitlabcis/tests/unit/benchmarks/artifacts_4/verification_4_1_test.py +78 -0
  187. gitlabcis/tests/unit/benchmarks/build_pipelines_2/build_environment_2_1_test.py +239 -0
  188. gitlabcis/tests/unit/benchmarks/build_pipelines_2/build_worker_2_2_test.py +105 -0
  189. gitlabcis/tests/unit/benchmarks/build_pipelines_2/pipeline_instructions_2_3_test.py +340 -0
  190. gitlabcis/tests/unit/benchmarks/build_pipelines_2/pipeline_integrity_2_4_test.py +115 -0
  191. gitlabcis/tests/unit/benchmarks/conftest.py +47 -0
  192. gitlabcis/tests/unit/benchmarks/dependencies_3/third_party_packages_3_1_test.py +135 -0
  193. gitlabcis/tests/unit/benchmarks/dependencies_3/validate_packages_3_2_test.py +171 -0
  194. gitlabcis/tests/unit/benchmarks/deployment_5/deployment_configuration_5_1_test.py +140 -0
  195. gitlabcis/tests/unit/benchmarks/deployment_5/deployment_environment_5_2_test.py +60 -0
  196. gitlabcis/tests/unit/benchmarks/function_test.py +24 -0
  197. gitlabcis/tests/unit/benchmarks/source_code_1/code_changes_1_1_test.py +565 -0
  198. gitlabcis/tests/unit/benchmarks/source_code_1/code_risks_1_5_test.py +419 -0
  199. gitlabcis/tests/unit/benchmarks/source_code_1/contribution_access_1_3_test.py +265 -0
  200. gitlabcis/tests/unit/benchmarks/source_code_1/repository_management_1_2_test.py +142 -0
  201. gitlabcis/tests/unit/benchmarks/source_code_1/third_party_1_4_test.py +119 -0
  202. gitlabcis/tests/unit/conftest.py +94 -0
  203. gitlabcis/tests/unit/log/log_test.py +23 -0
  204. gitlabcis/tests/unit/utils/argfilters_test.py +9 -0
  205. gitlabcis/tests/unit/utils/ci_test.py +156 -0
  206. gitlabcis/tests/unit/utils/output_test.py +95 -0
  207. gitlabcis/tests/unit/utils/utils_general_test.py +149 -0
  208. gitlabcis/tests/unit/utils/version_test.py +11 -0
  209. gitlabcis/tests/unit/yaml/bad_file_test.py +15 -0
  210. gitlabcis/tests/unit/yaml/recommendation_test.py +123 -0
  211. gitlabcis/utils/__init__.py +146 -0
  212. gitlabcis/utils/ci.py +132 -0
  213. gitlabcis-1.3.2.dist-info/LICENSE +21 -0
  214. gitlabcis-1.3.2.dist-info/METADATA +241 -0
  215. gitlabcis-1.3.2.dist-info/RECORD +218 -0
  216. gitlabcis-1.3.2.dist-info/WHEEL +5 -0
  217. gitlabcis-1.3.2.dist-info/entry_points.txt +2 -0
  218. gitlabcis-1.3.2.dist-info/top_level.txt +1 -0
gitlabcis/cli/main.py ADDED
@@ -0,0 +1,541 @@
1
+ # -----------------------------------------------------------------------------
2
+
3
+ import logging
4
+ from argparse import ArgumentParser, FileType
5
+ from collections import namedtuple
6
+ from concurrent.futures import ThreadPoolExecutor, as_completed
7
+ from datetime import datetime
8
+ from os import environ
9
+ from sys import exit
10
+ from urllib.parse import urlparse
11
+
12
+ import gitlab
13
+ from tqdm import tqdm
14
+
15
+ from gitlabcis import (__version__, benchmarks, countRecommendations,
16
+ mapRecommendations, readRecommendations)
17
+ from gitlabcis.cli import log, output
18
+
19
+ # -----------------------------------------------------------------------------
20
+ # Load project & group benchmark functions:
21
+ # -----------------------------------------------------------------------------
22
+
23
+ # load all of the compliance check functions:
24
+
25
+ benchmarkFunctions = [
26
+ getattr(getattr(getattr(benchmarks, catFile), subCatFile), func)
27
+ for catFile in dir(benchmarks)
28
+ if not catFile.startswith('__')
29
+ for subCatFile in dir(getattr(benchmarks, catFile))
30
+ if not subCatFile.startswith('__')
31
+ for func in dir(getattr(getattr(benchmarks, catFile), subCatFile))
32
+ if not func.startswith('__')
33
+ ]
34
+
35
+ # -----------------------------------------------------------------------------
36
+
37
+ PROFILES = [1, 2]
38
+ IMPLEMENTATION_GROUPS = ['IG1', 'IG2', 'IG3']
39
+ OUTPUT_FORMATS = ['terminal', 'yaml', 'json', 'csv', 'xml', 'txt']
40
+ MAX_WORKERS = 15
41
+
42
+ # -----------------------------------------------------------------------------
43
+ # Main:
44
+ # -----------------------------------------------------------------------------
45
+
46
+
47
+ def main():
48
+
49
+ # -------------------------------------------------------------------------
50
+ # Obtain Input:
51
+ # -------------------------------------------------------------------------
52
+
53
+ parser = ArgumentParser(
54
+ description=f'GitLab CIS Benchmark Scanner Version: {__version__}\n'
55
+ )
56
+
57
+ # Add arguments
58
+ parser.add_argument(
59
+ 'url',
60
+ metavar='URL',
61
+ nargs='*',
62
+ type=str,
63
+ help='The URL to the project to audit'
64
+ )
65
+
66
+ parser.add_argument(
67
+ '-t',
68
+ '--token',
69
+ dest='token',
70
+ metavar='TOKEN',
71
+ type=str,
72
+ help='GitLab Personal Access Token'
73
+ )
74
+
75
+ parser.add_argument(
76
+ '-ci',
77
+ '--cis-controls',
78
+ dest='cis_controls',
79
+ metavar='CIS_CONTROL_IDS',
80
+ nargs='*',
81
+ type=float,
82
+ help='The IDs of the CIS Controls to audit (e.g. 18.1)'
83
+ )
84
+
85
+ parser.add_argument(
86
+ '-ids',
87
+ '--recommendation-ids',
88
+ dest='recommendation_ids',
89
+ metavar='RECOMMENDATION_IDS',
90
+ nargs='*',
91
+ type=str,
92
+ help='The IDs of the recommedation controls to audit (e.g. 1.1.1)'
93
+ )
94
+
95
+ parser.add_argument(
96
+ '-s',
97
+ '--skip',
98
+ dest='skip_recommendation_ids',
99
+ metavar='RECOMMENDATION_IDS_TO_SKIP',
100
+ nargs='*',
101
+ type=str,
102
+ help='The IDs of the recommedation controls to SKIP (e.g. 1.1.1)'
103
+ )
104
+
105
+ parser.add_argument(
106
+ '-p',
107
+ '--profile',
108
+ dest='profile',
109
+ metavar='PROFILE',
110
+ type=int,
111
+ choices=PROFILES,
112
+ help='Which benchmark profile to use (default: both 1 & 2)'
113
+ )
114
+
115
+ parser.add_argument(
116
+ '-r',
117
+ '--remediations',
118
+ dest='remediations',
119
+ action='store_true',
120
+ help='Include remediations in the results output'
121
+ )
122
+
123
+ parser.add_argument(
124
+ '-o',
125
+ '--output',
126
+ dest='output_file',
127
+ metavar='OUTPUT_FILE',
128
+ type=FileType('w', encoding='utf-8'),
129
+ help='The name of the file to output results to'
130
+ )
131
+
132
+ parser.add_argument(
133
+ '-g',
134
+ '--implementation-groups',
135
+ dest='implementation_groups',
136
+ metavar='IMPLEMENTATION_GROUPS',
137
+ nargs='*',
138
+ type=str,
139
+ choices=IMPLEMENTATION_GROUPS,
140
+ help=f'Which CIS Implementation Group to use {IMPLEMENTATION_GROUPS} '
141
+ '(default: all)'
142
+ )
143
+
144
+ parser.add_argument(
145
+ '-os',
146
+ '--omit-skipped',
147
+ dest='omit_skipped',
148
+ action='store_true',
149
+ help='Excludes SKIP results from the output'
150
+ )
151
+
152
+ parser.add_argument(
153
+ '-f',
154
+ '--format',
155
+ dest='output_format',
156
+ default='terminal',
157
+ type=str,
158
+ choices=OUTPUT_FORMATS,
159
+ help='Output format (default: terminal)'
160
+ )
161
+
162
+ parser.add_argument(
163
+ '-mw',
164
+ '--max-workers',
165
+ dest='max_workers',
166
+ default=15,
167
+ type=int,
168
+ help='Maximum number of Worker threads (default: 15)'
169
+ )
170
+
171
+ parser.add_argument(
172
+ '-d',
173
+ '--debug',
174
+ dest='debug',
175
+ action='store_true',
176
+ help='Enable debugging mode'
177
+ )
178
+
179
+ parser.add_argument(
180
+ '-v',
181
+ '--version',
182
+ dest='version',
183
+ action='store_true',
184
+ help='Print the currently installed version of gitlabcis'
185
+ )
186
+
187
+ # Parse arguments
188
+ args = parser.parse_args()
189
+
190
+ if args.version:
191
+ print(f'GitLabCIS {__version__}')
192
+ exit(0)
193
+
194
+ if not args.url:
195
+ parser.print_usage()
196
+ exit(2)
197
+
198
+ # -------------------------------------------------------------------------
199
+ # bools to determine what entity to run checks against:
200
+ # -------------------------------------------------------------------------
201
+
202
+ isProject = False
203
+ isGroup = False
204
+ isDotCom = False
205
+
206
+ # -------------------------------------------------------------------------
207
+ # Token heirachy:
208
+ # -------------------------------------------------------------------------
209
+
210
+ # If a user provided a token, that should take highest priority, next
211
+ # is a GITLAB_TOKEN environment variable:
212
+
213
+ token = None
214
+ token_var = None
215
+
216
+ _availableTokens = {
217
+ '--token': args.token,
218
+ 'GITLAB_TOKEN': environ.get('GITLAB_TOKEN'),
219
+ }
220
+
221
+ for _type, token in _availableTokens.items():
222
+ if token is not None:
223
+ token_var = _type
224
+ break
225
+
226
+ if token is None:
227
+ print(
228
+ 'Error: No access token found, you must either have the '
229
+ 'environment variable: "GITLAB_TOKEN" or provide a token via the '
230
+ 'command line (--token).'
231
+ )
232
+ exit(1)
233
+
234
+ # -------------------------------------------------------------------------
235
+ # Input sanity:
236
+ # -------------------------------------------------------------------------
237
+
238
+ if args.output_format.lower() != 'terminal' and args.output_file is None:
239
+ print(
240
+ 'Error: Output format provided but no output file provided'
241
+ )
242
+ exit(1)
243
+
244
+ if len(args.url) > 1:
245
+ print('Error: Only one URL is currently supported')
246
+ exit(1)
247
+ else:
248
+ args.url = args.url[0]
249
+
250
+ # -------------------------------------------------------------------------
251
+ # Logging:
252
+ # -------------------------------------------------------------------------
253
+
254
+ if args.debug is False:
255
+ logLevel = 'INFO'
256
+ logging.getLogger('gql.transport.requests').setLevel(logging.ERROR)
257
+ else:
258
+ logLevel = 'DEBUG'
259
+
260
+ logging.basicConfig(
261
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
262
+ level=getattr(logging, logLevel.upper())
263
+ )
264
+
265
+ # Suppress tokens in logs & max pool size:
266
+ logFilter = log.CustomLogFilter(token)
267
+ logging.getLogger('urllib3.connectionpool').addFilter(logFilter)
268
+ logging.getLogger().addFilter(logFilter)
269
+
270
+ logging.debug(f'args: {args}')
271
+
272
+ # -------------------------------------------------------------------------
273
+ # Auth to GitLab:
274
+ # -------------------------------------------------------------------------
275
+
276
+ urlParsedInput = urlparse(args.url)
277
+ userInputPath = urlParsedInput.path.strip('/')
278
+ userInputHost = f'{urlParsedInput.scheme}://{urlParsedInput.netloc}' # noqa: E231, E501
279
+
280
+ if urlParsedInput.netloc == 'gitlab.com':
281
+ isDotCom = True
282
+
283
+ logging.debug(f'{isDotCom=}')
284
+
285
+ # instantiate the gl obj:
286
+ gl = gitlab.Gitlab(
287
+ userInputHost,
288
+ private_token=token
289
+ )
290
+
291
+ # attempt a dry-run auth to make sure the token works:
292
+ try:
293
+ gl.auth()
294
+
295
+ except gitlab.exceptions.GitlabAuthenticationError as e:
296
+ print(
297
+ f'Error: The token provided failed to authenticate to: {args.url}'
298
+ )
299
+ logging.debug(f'Auth Error: {e}')
300
+ exit(1)
301
+
302
+ except (
303
+ gitlab.exceptions.GitlabHttpError,
304
+ gitlab.exceptions.GitlabGetError
305
+ ) as e:
306
+
307
+ logging.debug(f'Exception: {e}')
308
+
309
+ if e.response_code == 403 and e.error_message == 'insufficient_scope':
310
+ print(f'Error: The "{token_var}" token has an insufficient scope.')
311
+ exit(1)
312
+
313
+ print(
314
+ f'Error: The host: {userInputHost} does not appear to be a '
315
+ 'GitLab instance. If this is erroneous, please raise a bug report.'
316
+ )
317
+ exit(1)
318
+
319
+ except Exception as e:
320
+ print(f'Error: Unable to connect to GitLab instance: {args.url}')
321
+ logging.debug(f'Connection Error: {e}')
322
+ exit(1)
323
+
324
+ # add a warning for gitlab.com admins:
325
+ try:
326
+ if isDotCom is True and gl.user.is_admin:
327
+
328
+ if input('\nWARNING: You are authenticated as a GitLab.com admin. '
329
+ 'Running a "full scan" may create significant load.\n\n'
330
+ ' Do you wish to continue? (y/n): ').lower() == 'y':
331
+ pass
332
+ else:
333
+ exit(0)
334
+
335
+ # if CTRL-C was pressed, exit cleanly:
336
+ except KeyboardInterrupt:
337
+ exit(0)
338
+
339
+ # if gl.user.is_admin does not return a bool:
340
+ except AttributeError:
341
+ pass
342
+
343
+ # -------------------------------------------------------------------------
344
+ # Check if we are dealing with a group or a project:
345
+ # -------------------------------------------------------------------------
346
+
347
+ try:
348
+ entity = gl.projects.get(userInputPath)
349
+ isProject = True
350
+
351
+ except gitlab.exceptions.GitlabGetError as e:
352
+ if '404 Project Not Found' in str(e):
353
+
354
+ try:
355
+ entity = gl.groups.get(userInputPath)
356
+ isGroup = True
357
+
358
+ except gitlab.exceptions.GitlabGetError as e:
359
+ if '404 Project Not Found' in str(e):
360
+ print(
361
+ 'Either you do not have access to the provided URL or '
362
+ 'the URL is invalid. '
363
+ 'Please provide a URL using the following '
364
+ 'format: https://gitlab.com/path-to-group-or-project '
365
+ 'e.g. https://gitlab.com/gitlab/gitlab-com'
366
+ )
367
+ exit(1)
368
+
369
+ if isGroup is False and isProject is False:
370
+ print(f'Error: Unable to find group/project: "{userInputPath}"')
371
+ exit()
372
+
373
+ # -------------------------------------------------------------------------
374
+
375
+ # Load the filtered ones from user input:
376
+ filteredRecommendations = readRecommendations(args)
377
+ if len(filteredRecommendations) == 0:
378
+ print('Error: No recommendations were found.')
379
+ exit(1)
380
+
381
+ # -------------------------------------------------------------------------
382
+
383
+ # format the plan:
384
+ _prof = args.profile if args.profile else ', '.join(
385
+ str(p) for p in PROFILES)
386
+
387
+ _ciCon = ', '.join(
388
+ str(ci)
389
+ for ci in args.cis_controls
390
+ if args.cis_controls) if args.cis_controls else 'All applicable'
391
+
392
+ _impl = ', '.join(
393
+ args.implementation_groups
394
+ if args.implementation_groups
395
+ else IMPLEMENTATION_GROUPS)
396
+
397
+ _start = datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")
398
+
399
+ # -------------------------------------------------------------------------
400
+
401
+ graphQLEndpoint = f'{userInputHost}/api/graphql'
402
+ graphQLHeaders = {
403
+ 'Authorization': f'Bearer {token}',
404
+ 'Content-Type': 'application/json'
405
+ }
406
+ workers = args.max_workers if args.max_workers else MAX_WORKERS
407
+
408
+ # -------------------------------------------------------------------------
409
+
410
+ # determine benchmarks to exec:
411
+ _filteredRecs = len(filteredRecommendations)
412
+ if _filteredRecs == countRecommendations():
413
+ _recs = len(benchmarkFunctions)
414
+ else:
415
+ _recs = _filteredRecs
416
+
417
+ # Print the plan to the user:
418
+ print(
419
+ f'\nRunning CIS benchmark scanner: \n\n'
420
+ f' - Scan Started: {_start}\n'
421
+ f' - Host: {userInputHost}\n'
422
+ f' - {"Group" if isGroup else "Project"}: {userInputPath}\n'
423
+ f' - Output Format: {args.output_format}\n'
424
+ f' - Output File: {args.output_file.name if args.output_file else "stdout"}\n' # noqa: E501
425
+ f' - Profile(s) applied: {_prof}\n'
426
+ f' - CIS Controls: {_ciCon}\n'
427
+ f' - Implementation Group(s): {_impl}\n'
428
+ f' - Benchmarks to check: {_recs}\n\n'
429
+ )
430
+
431
+ # -------------------------------------------------------------------------
432
+ # Map the benchmarks:
433
+ # -------------------------------------------------------------------------
434
+
435
+ logging.debug(
436
+ 'Running CIS benchmark checks '
437
+ f'against {"project" if isProject else "group"}: {userInputPath}'
438
+ )
439
+
440
+ results = []
441
+ stats = {
442
+ 'PASSED': 0,
443
+ 'FAILED': 0,
444
+ 'SKIPPED': 0,
445
+ 'TOTAL': _recs
446
+ }
447
+
448
+ mappedFuncs = mapRecommendations(
449
+ benchmarkFunctions, filteredRecommendations)
450
+
451
+ # -------------------------------------------------------------------------
452
+ # Setup kwargs that each benchmark function can have access to:
453
+ # -------------------------------------------------------------------------
454
+
455
+ kwargs = {'isDotCom': isDotCom, 'isProject': isProject,
456
+ 'isGroup': isGroup, 'graphQLEndpoint': graphQLEndpoint,
457
+ 'graphQLHeaders': graphQLHeaders}
458
+
459
+ # -------------------------------------------------------------------------
460
+ # Store benchmark results:
461
+ # -------------------------------------------------------------------------
462
+
463
+ Benchmark = namedtuple('Benchmark', ['projectCheck', 'result', 'func'])
464
+
465
+ def executeBenchmark(_projectFunction, _projectCheck, entity, gl,
466
+ **kwargs):
467
+
468
+ logging.debug(f'Executing benchmark: {_projectFunction.__name__}')
469
+ return Benchmark(
470
+ projectCheck=_projectCheck,
471
+ result=_projectFunction(entity, gl, **kwargs),
472
+ func=_projectFunction)
473
+
474
+ # -------------------------------------------------------------------------
475
+ # Projects:
476
+ # -------------------------------------------------------------------------
477
+
478
+ if isGroup:
479
+ raise NotImplementedError('We do not support groups just yet...')
480
+
481
+ try:
482
+
483
+ with ThreadPoolExecutor(max_workers=workers) as executor:
484
+ futures = [
485
+ executor.submit(
486
+ executeBenchmark, _projectFunction, _projectCheck, entity,
487
+ gl, **kwargs)
488
+ for _projectFunction, _projectCheck in mappedFuncs.items()
489
+ ]
490
+
491
+ for future in tqdm(
492
+ as_completed(futures),
493
+ total=len(mappedFuncs),
494
+ colour='green',
495
+ desc='Scanning',
496
+ bar_format=(
497
+ '{l_bar}{bar}| {n_fmt}/{total_fmt} completed '
498
+ '[elapsed: {elapsed} remaining: {remaining}]')):
499
+ _res = future.result()
500
+
501
+ try:
502
+ if next(iter(_res.result)) is True:
503
+ _resStr = 'PASS'
504
+ stats['PASSED'] += 1
505
+ elif next(iter(_res.result)) is False:
506
+ _resStr = 'FAIL'
507
+ stats['FAILED'] += 1
508
+ elif next(iter(_res.result)) is None:
509
+ _resStr = 'SKIP'
510
+ stats['SKIPPED'] += 1
511
+ except TypeError:
512
+ logging.error(f'Function: {_res.func.__name__} did '
513
+ 'not return a dict')
514
+ exit(1)
515
+
516
+ result = {
517
+ 'id': _res.projectCheck['id'],
518
+ 'title': _res.projectCheck['title'],
519
+ 'reason': list(_res.result.values())[0],
520
+ 'result': _resStr
521
+ }
522
+
523
+ if args.remediations is True:
524
+ result['remediation'] = _res.projectCheck['remediation']
525
+
526
+ if args.omit_skipped is True \
527
+ and result.get('result') == 'SKIP':
528
+ continue
529
+
530
+ results.append(result)
531
+
532
+ output.output(results, stats, args.output_format, args.output_file)
533
+
534
+ except KeyboardInterrupt:
535
+ exit(1)
536
+
537
+ # -----------------------------------------------------------------------------
538
+
539
+
540
+ if __name__ == "__main__":
541
+ main()
@@ -0,0 +1,151 @@
1
+ # -----------------------------------------------------------------------------
2
+
3
+ import csv
4
+ import json
5
+ from datetime import datetime
6
+ from xml.etree import ElementTree as ET # nosec: B405
7
+
8
+ import yaml
9
+ from defusedxml import ElementTree as DET
10
+ from tabulate import tabulate
11
+
12
+ # -----------------------------------------------------------------------------
13
+ # Output:
14
+ # -----------------------------------------------------------------------------
15
+
16
+
17
+ def output(results, stats, outputFormat='terminal', outputFile=None):
18
+ """
19
+ Desc: Output the results
20
+ """
21
+
22
+ # Terminal Icons:
23
+ CHECK = '\u2714'
24
+ CROSS = '\u2718'
25
+ LINE = '\u0021'
26
+
27
+ # Colours:
28
+ GREEN = '\033[32m'
29
+ RED = '\033[91m'
30
+ BLUE = '\033[36m'
31
+ YELLOW = '\033[93m'
32
+ RESET = '\033[0m'
33
+
34
+ if results:
35
+
36
+ _sortedResults = sorted(
37
+ results,
38
+ key=lambda col: [int(_id) for _id in col['id'].split('.')])
39
+
40
+ # ---------------------------------------------------------------------
41
+ # Terminal output:
42
+ # ---------------------------------------------------------------------
43
+
44
+ if outputFormat in ['terminal', 'txt']:
45
+
46
+ if _sortedResults:
47
+ _headers = _sortedResults[0].keys()
48
+ _rows = [res.values() for res in _sortedResults]
49
+ else:
50
+ _headers = []
51
+ _rows = []
52
+
53
+ for result in _sortedResults:
54
+ result['result'] = (
55
+ f'{GREEN}PASS {CHECK}{RESET}'
56
+ if result['result'] == 'PASS'
57
+ else f'{BLUE}SKIP {LINE}{RESET}'
58
+ if result['result'] == 'SKIP'
59
+ else f'{RED}FAIL {CROSS}{RESET}'
60
+ )
61
+
62
+ table = tabulate(
63
+ _rows,
64
+ headers=_headers,
65
+ tablefmt='rounded_grid',
66
+ maxcolwidths=[None, 75, 25, None])
67
+
68
+ if outputFormat == 'terminal':
69
+ print(f'\nResults:\n\n{table}') # noqa: E231
70
+
71
+ elif outputFormat == 'txt':
72
+ outputFile.write(table)
73
+
74
+ # ---------------------------------------------------------------------
75
+ # JSON output:
76
+ # ---------------------------------------------------------------------
77
+
78
+ if outputFormat == 'json':
79
+
80
+ json.dump(_sortedResults, outputFile, indent=4)
81
+
82
+ # ---------------------------------------------------------------------
83
+ # YAML output:
84
+ # ---------------------------------------------------------------------
85
+
86
+ if outputFormat == 'yaml':
87
+
88
+ yaml.dump(_sortedResults, outputFile, indent=4)
89
+
90
+ # ---------------------------------------------------------------------
91
+ # XML output:
92
+ # ---------------------------------------------------------------------
93
+
94
+ if outputFormat == 'xml':
95
+
96
+ root = ET.Element('root')
97
+
98
+ for item in _sortedResults:
99
+
100
+ sub_element = ET.SubElement(root, 'item')
101
+
102
+ for key, value in item.items():
103
+ ET.SubElement(sub_element, key).text = str(value)
104
+
105
+ outputFile.write(DET.tostring(root, encoding='utf-8').decode())
106
+
107
+ # ---------------------------------------------------------------------
108
+ # CSV output:
109
+ # ---------------------------------------------------------------------
110
+
111
+ if outputFormat == 'csv':
112
+
113
+ if _sortedResults:
114
+ _headers = _sortedResults[0].keys()
115
+ else:
116
+ _headers = []
117
+
118
+ writer = csv.DictWriter(outputFile, fieldnames=_headers)
119
+
120
+ writer.writeheader()
121
+
122
+ for row in _sortedResults:
123
+ writer.writerow(row)
124
+
125
+ # -------------------------------------------------------------------------
126
+ # Determine the score - output the stats:
127
+ # -------------------------------------------------------------------------
128
+
129
+ try:
130
+ score = round(
131
+ (stats["PASSED"] / (stats["TOTAL"] - stats["SKIPPED"])) * 100, 2)
132
+ except ZeroDivisionError:
133
+ # the total score matched the amount of skipped results
134
+ score = 0
135
+
136
+ if score >= 75:
137
+ scoreColor = GREEN
138
+ elif score >= 50 and score < 75:
139
+ scoreColor = YELLOW
140
+ else:
141
+ scoreColor = RED
142
+
143
+ print(
144
+ '\nScan finished: '
145
+ f'{datetime.strftime(datetime.now(), "%Y-%m-%d %H:%M:%S")}\n\n'
146
+ 'Stats:\n\n'
147
+ f' - {GREEN}PASSED: {stats["PASSED"]}/{stats["TOTAL"]}{RESET}\n'
148
+ f' - {RED}FAILED: {stats["FAILED"]}/{stats["TOTAL"]}{RESET}\n'
149
+ f' - {BLUE}SKIPPED: {stats["SKIPPED"]}/{stats["TOTAL"]}{RESET}\n'
150
+ f' - SCORE: {scoreColor}{score}%{RESET} (excludes SKIPPED)\n'
151
+ )