qontract-reconcile 0.10.1rc505__py3-none-any.whl → 0.10.1rc507__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: qontract-reconcile
3
- Version: 0.10.1rc505
3
+ Version: 0.10.1rc507
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Home-page: https://github.com/app-sre/qontract-reconcile
6
6
  Author: Red Hat App-SRE Team
@@ -1,5 +1,6 @@
1
1
  reconcile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- reconcile/acs_rbac.py,sha256=Jr9qIR1VjH16xpADZqjkigqsDW7iDnMD9nn7GxU4z0Y,23828
2
+ reconcile/acs_policies.py,sha256=Mop44Z5T0DxXghme13ZVvy1hr5KphZKzc_ZevxAJBQ0,8784
3
+ reconcile/acs_rbac.py,sha256=YoKu5wTRTtb3EGT0PV3r279LDgvw2ECb-0_0j4suScg,23032
3
4
  reconcile/aws_ami_share.py,sha256=eeu0TI3M5yyUaozyAq_aW3tir-9be4YFguOXvIvKHSo,3757
4
5
  reconcile/aws_ecr_image_pull_secrets.py,sha256=TGEc_0nv8oxV2HqA8VdcM4HHP-B1YqmNOOU6FPwVFTY,2328
5
6
  reconcile/aws_garbage_collector.py,sha256=ddwU8IKTueAJc0TzymcREr7hcoVui9kOGvdH1B2EcuM,450
@@ -8,7 +9,7 @@ reconcile/aws_iam_password_reset.py,sha256=NwErtrqgBiXr7eGCAHdtGGOx0S7-4JnSc29Ie
8
9
  reconcile/aws_support_cases_sos.py,sha256=Jk6_XjDeJSYxgRGqcEAOcynt9qJF2r5HPIPcSKmoBv8,2974
9
10
  reconcile/blackbox_exporter_endpoint_monitoring.py,sha256=W_VJagnsJR1v5oqjlI3RJJE0_nhtJ0m81RS8zWA5u5c,3538
10
11
  reconcile/checkpoint.py,sha256=R2WFXUXLTB4sWMi4GeA4eegsuf_1-Q4vH8M0Toh3Ij4,5036
11
- reconcile/cli.py,sha256=bI6iACTMbjI3hngvhIl1L6PqY-k2XfkVunLV95yGPSk,82112
12
+ reconcile/cli.py,sha256=3IRugyi6JemMe5RhL5gGiAycQHdFb-9hB989BeT2Tw0,82390
12
13
  reconcile/closedbox_endpoint_monitoring_base.py,sha256=SMhkcQqprWvThrIJa3U_3uh5w1h-alleW1QnCJFY4Qw,4909
13
14
  reconcile/cluster_deployment_mapper.py,sha256=2Ah-nu-Mdig0pjuiZl_XLrmVAjYzFjORR3dMlCgkmw0,2352
14
15
  reconcile/dashdotdb_base.py,sha256=a5aPLVxyqPSbjdB0Ty-uliOtxwvEbbEljHJKxdK3-Zk,4813
@@ -165,6 +166,7 @@ reconcile/glitchtip_project_dsn/integration.py,sha256=PlOsRaaftzODZE_uf1I-XSM732
165
166
  reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
166
167
  reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
167
168
  reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
169
+ reconcile/gql_definitions/acs/acs_policies.py,sha256=Z6Z7duvS9W4cbciBED4oK40Vg9QyYti3zXvoEXM-fak,4422
168
170
  reconcile/gql_definitions/acs/acs_rbac.py,sha256=cZsIlCWliPQdQHgmBsIMx54fJNOtkdRXLzmOKZmJNHk,3009
169
171
  reconcile/gql_definitions/advanced_upgrade_service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
170
172
  reconcile/gql_definitions/advanced_upgrade_service/aus_clusters.py,sha256=2-OnknXDUI2pnZknEmjzMPBXUpWStoE32lpQQStobao,4221
@@ -368,7 +370,8 @@ reconcile/templates/jira-checkpoint-missinginfo.j2,sha256=c_Vvg-lEENsB3tgxm9B6Y9
368
370
  reconcile/test/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
369
371
  reconcile/test/conftest.py,sha256=rQousYrxUz-EwAIbsYO6bIwR1B4CrOz9y_zaUVo2lfI,4466
370
372
  reconcile/test/fixtures.py,sha256=9SDWAUlSd1rCx7z3GhULHcpr-I6FyCsXxaFAZIqYQsQ,591
371
- reconcile/test/test_acs_rbac.py,sha256=BADHTYfXBkoge5Tl4DOoh3pwU7EsN4yzLIy3gNQ8XHM,28409
373
+ reconcile/test/test_acs_policies.py,sha256=52pZXzsLZXsvWoFW7IgP1nGjQw9a2Pd3axjfRHWUUfc,15083
374
+ reconcile/test/test_acs_rbac.py,sha256=lvNd8GY0-GHzcOdOn13QWdrqbBXXKzNT7EEDHNH7cjM,28272
372
375
  reconcile/test/test_aggregated_list.py,sha256=iiWitQuNYC58aimWaiBoE4NROHjr1NCgQ91MnHEG_Ro,6412
373
376
  reconcile/test/test_amtool.py,sha256=vxRhGieeydMBOb9UI2ziMHjJa8puMeGNsUhGhy-yMnk,1032
374
377
  reconcile/test/test_aws_ami_cleanup.py,sha256=cHumkZD33vIIblNbrMKNCva-WeE3sMv8kiFA0A96wwk,8733
@@ -502,7 +505,6 @@ reconcile/typed_queries/app_interface_metrics_exporter/onboarding_status.py,sha2
502
505
  reconcile/typed_queries/terraform_tgw_attachments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
503
506
  reconcile/typed_queries/terraform_tgw_attachments/aws_accounts.py,sha256=T5HSeyBcGKP-LzDmIzs-WlBwOtSenYpm0Odw5--xdOg,410
504
507
  reconcile/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
505
- reconcile/utils/acs_api.py,sha256=AworbL61gIzw1asDaz0A1qbuZ2QDupF_EfrjQhXCCc8,10020
506
508
  reconcile/utils/aggregated_list.py,sha256=pkYoBj7WwmaNgEefETqEOFTnQMcUzHE3mdsVdzGYj60,3372
507
509
  reconcile/utils/amtool.py,sha256=JV5-to_e_FaIcvJWTKYA9d6L3LwzwijM0MjUWn83eD4,2204
508
510
  reconcile/utils/aws_api.py,sha256=I6a_-aW1ptL8287I-6GnAdtavjjYMMsMeNHMT3WwA_I,65033
@@ -633,7 +635,7 @@ reconcile/utils/terraform/config.py,sha256=5UVrd563TMcvi4ooa5JvWVDW1I3bIWg484u79
633
635
  reconcile/utils/terraform/config_client.py,sha256=py-Ree-QUYD6Hvng6bM40VgSuttteehIKNgwOSoJO1o,4706
634
636
  reconcile/utils/terrascript/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
635
637
  reconcile/utils/terrascript/cloudflare_client.py,sha256=EOmAnrgSUZz-UZB7x6TFd4WGk0huWZm2hh29Cueiyr8,10278
636
- reconcile/utils/terrascript/cloudflare_resources.py,sha256=ZvmFqE-Atki6ehMA4iirYqpml4bCPHdddq8lu58udGA,15952
638
+ reconcile/utils/terrascript/cloudflare_resources.py,sha256=FXeMlm3nVfaNBabyhC654D-MkHTTTr7rh4BHNCwoXZA,15996
637
639
  reconcile/utils/terrascript/models.py,sha256=x9HReI0k71MHBpRTvvmPlE0G6rri5GTzPXM9cqyTWm0,475
638
640
  reconcile/utils/terrascript/resources.py,sha256=bQzglnO41KZZEIeXYgi-qlup1p8R03Qyx_V944LRPsc,1391
639
641
  release/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -655,8 +657,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
655
657
  tools/test/test_qontract_cli.py,sha256=d18KrdhtUGqoC7_kWZU128U0-VJEj-0rjFkLVufcI6I,2755
656
658
  tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
657
659
  tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
658
- qontract_reconcile-0.10.1rc505.dist-info/METADATA,sha256=9vvQJsGEq4_oKtbivKJzIUMooQaoA4ewVRMXfA-yUNY,2349
659
- qontract_reconcile-0.10.1rc505.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
660
- qontract_reconcile-0.10.1rc505.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
661
- qontract_reconcile-0.10.1rc505.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
662
- qontract_reconcile-0.10.1rc505.dist-info/RECORD,,
660
+ qontract_reconcile-0.10.1rc507.dist-info/METADATA,sha256=MFhlaeZXsE2DxlrU5_8jeDSAc67WNz_TKbo_tydbkmE,2349
661
+ qontract_reconcile-0.10.1rc507.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
662
+ qontract_reconcile-0.10.1rc507.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
663
+ qontract_reconcile-0.10.1rc507.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
664
+ qontract_reconcile-0.10.1rc507.dist-info/RECORD,,
@@ -0,0 +1,232 @@
1
+ import logging
2
+ from collections.abc import Callable
3
+ from typing import cast
4
+
5
+ import reconcile.gql_definitions.acs.acs_policies as gql_acs_policies
6
+ from reconcile.gql_definitions.acs.acs_policies import (
7
+ AcsPolicyConditionsV1,
8
+ AcsPolicyV1,
9
+ )
10
+ from reconcile.typed_queries.app_interface_vault_settings import (
11
+ get_app_interface_vault_settings,
12
+ )
13
+ from reconcile.utils import gql
14
+ from reconcile.utils.acs.policies import AcsPolicyApi, Policy, PolicyCondition, Scope
15
+ from reconcile.utils.differ import diff_iterables
16
+ from reconcile.utils.runtime.integration import (
17
+ NoParams,
18
+ QontractReconcileIntegration,
19
+ )
20
+ from reconcile.utils.secret_reader import create_secret_reader
21
+ from reconcile.utils.semver_helper import make_semver
22
+
23
+ # proceeding constants map schema enum values to corresponding acs api defaults
24
+ POLICY_CATEGORIES = {
25
+ "anomalous-activity": "Anomalous Activity",
26
+ "devops-best-practices": "DevOps Best Practices",
27
+ "kubernetes": "Kubernetes",
28
+ "privileges": "Privileges",
29
+ "security-best-practices": "Security Best Practices",
30
+ "vulnerability-management": "Vulnerability Management",
31
+ }
32
+
33
+ POLICY_CONDITION_COMPARISONS = {
34
+ "gt": ">",
35
+ "gte": ">=",
36
+ "eq": "",
37
+ "lt": "<",
38
+ "lte": "<=",
39
+ }
40
+
41
+ POLICY_CONDITION_FIELD_NAMES = {
42
+ "cvss": "CVSS",
43
+ "severity": "Severity",
44
+ "imageTag": "Image Tag",
45
+ "imageAge": "Image Age",
46
+ "cve": "Fixable",
47
+ }
48
+
49
+
50
+ class AcsPoliciesIntegration(QontractReconcileIntegration[NoParams]):
51
+ def __init__(self) -> None:
52
+ super().__init__(NoParams())
53
+ self.qontract_integration = "acs_policies"
54
+ self.qontract_integration_version = make_semver(0, 1, 0)
55
+
56
+ @property
57
+ def name(self) -> str:
58
+ return self.qontract_integration.replace("_", "-")
59
+
60
+ def _build_policy(
61
+ self, gql_policy: AcsPolicyV1, notifier_name_to_id: dict[str, str]
62
+ ) -> Policy:
63
+ conditions = [
64
+ pc for c in gql_policy.conditions if (pc := self._build_policy_condition(c))
65
+ ]
66
+ return Policy(
67
+ name=gql_policy.name,
68
+ description=gql_policy.description,
69
+ notifiers=sorted([notifier_name_to_id[n] for n in gql_policy.notifiers])
70
+ if gql_policy.notifiers
71
+ else [],
72
+ severity=f"{gql_policy.severity.upper()}_SEVERITY", # align with acs api severity value format
73
+ scope=sorted(
74
+ [
75
+ Scope(cluster=cs.name, namespace="")
76
+ for cs in cast(
77
+ gql_acs_policies.AcsPolicyScopeClusterV1,
78
+ gql_policy.scope,
79
+ ).clusters
80
+ ],
81
+ key=lambda s: s.cluster,
82
+ )
83
+ if gql_policy.scope.level == "cluster"
84
+ else sorted(
85
+ [
86
+ Scope(cluster=ns.cluster.name, namespace=ns.name)
87
+ for ns in cast(
88
+ gql_acs_policies.AcsPolicyScopeNamespaceV1,
89
+ gql_policy.scope,
90
+ ).namespaces
91
+ ],
92
+ key=lambda s: s.cluster,
93
+ ),
94
+ categories=sorted([POLICY_CATEGORIES[pc] for pc in gql_policy.categories]),
95
+ conditions=conditions,
96
+ )
97
+
98
+ def _build_policy_condition(
99
+ self, condition: AcsPolicyConditionsV1
100
+ ) -> PolicyCondition | None:
101
+ field_name = POLICY_CONDITION_FIELD_NAMES[condition.policy_field]
102
+ match condition.policy_field:
103
+ case "cvss":
104
+ cvss_condition = cast(
105
+ gql_acs_policies.AcsPolicyConditionsCvssV1, condition
106
+ )
107
+ return PolicyCondition(
108
+ field_name=field_name,
109
+ negate=False,
110
+ values=[
111
+ f"{POLICY_CONDITION_COMPARISONS[cvss_condition.comparison]}{cvss_condition.score}"
112
+ ],
113
+ )
114
+ case "severity":
115
+ severity_condition = cast(
116
+ gql_acs_policies.AcsPolicyConditionsSeverityV1, condition
117
+ )
118
+ return PolicyCondition(
119
+ field_name=field_name,
120
+ negate=False,
121
+ values=[
122
+ f"{POLICY_CONDITION_COMPARISONS[severity_condition.comparison]}{severity_condition.level.upper()}"
123
+ ],
124
+ )
125
+ case "cve":
126
+ cve_condition = cast(
127
+ gql_acs_policies.AcsPolicyConditionsCveV1, condition
128
+ )
129
+ return PolicyCondition(
130
+ field_name=field_name,
131
+ negate=False,
132
+ values=[str(cve_condition.fixable).lower()],
133
+ )
134
+ case "image_tag":
135
+ image_tag_condition = cast(
136
+ gql_acs_policies.AcsPolicyConditionsImageTagV1, condition
137
+ )
138
+ return PolicyCondition(
139
+ field_name=field_name,
140
+ # negate utilized to enforce policy in which image tag should not be any
141
+ # defined in list of values
142
+ negate=image_tag_condition.negate or False,
143
+ values=image_tag_condition.tags,
144
+ )
145
+ case "image_age":
146
+ image_age_condition = cast(
147
+ gql_acs_policies.AcsPolicyConditionsImageAgeV1, condition
148
+ )
149
+ return PolicyCondition(
150
+ field_name=field_name,
151
+ negate=False,
152
+ values=[str(image_age_condition.days)],
153
+ )
154
+ case _:
155
+ logging.warning(
156
+ "unsupported policyField encountered: %s", condition.policy_field
157
+ )
158
+ return None
159
+
160
+ def get_desired_state(
161
+ self, query_func: Callable, notifiers: list[AcsPolicyApi.NotifierIdentifiers]
162
+ ) -> list[Policy]:
163
+ """
164
+ Get desired ACS security policies and convert to acs api policy object format
165
+
166
+ :param query_func: function which queries GQL server
167
+ :return: list of utils.acs.policies.Policy derived from acs-policy-1 definitions
168
+ """
169
+ notifier_name_to_id = {n.name: n.id for n in notifiers}
170
+ return [
171
+ self._build_policy(gql_policy, notifier_name_to_id)
172
+ for gql_policy in gql_acs_policies.query(query_func=query_func).acs_policies
173
+ or []
174
+ ]
175
+
176
+ def reconcile(
177
+ self,
178
+ desired: list[Policy],
179
+ current: list[Policy],
180
+ acs: AcsPolicyApi,
181
+ dry_run: bool,
182
+ ) -> None:
183
+ errors = []
184
+ diff = diff_iterables(current=current, desired=desired, key=lambda x: x.name)
185
+ for a in diff.add.values():
186
+ logging.info("Create policy: %s", a.name)
187
+ if not dry_run:
188
+ try:
189
+ acs.create_or_update_policy(desired=a)
190
+ except Exception as e:
191
+ errors.append(e)
192
+ if diff.delete or diff.change:
193
+ policy_id_by_name = {p["name"]: p["id"] for p in acs.list_custom_policies()}
194
+ for d in diff.delete.values():
195
+ logging.info("Delete policy: %s", d.name)
196
+ if not dry_run:
197
+ try:
198
+ acs.delete_policy(policy_id_by_name[d.name])
199
+ except Exception as e:
200
+ errors.append(e)
201
+ for c in diff.change.values():
202
+ logging.info("Update policy: %s", c.desired.name)
203
+ if not dry_run:
204
+ try:
205
+ acs.create_or_update_policy(
206
+ desired=c.desired, id=policy_id_by_name[c.current.name]
207
+ )
208
+ except Exception as e:
209
+ errors.append(e)
210
+ if errors:
211
+ raise ExceptionGroup("Reconcile errors occurred", errors)
212
+
213
+ def run(
214
+ self,
215
+ dry_run: bool,
216
+ ) -> None:
217
+ gqlapi = gql.get_api()
218
+ instance = AcsPolicyApi.get_acs_instance(gqlapi.query)
219
+
220
+ vault_settings = get_app_interface_vault_settings()
221
+ secret_reader = create_secret_reader(use_vault=vault_settings.vault)
222
+ token = secret_reader.read_all_secret(instance.credentials)
223
+
224
+ with AcsPolicyApi(
225
+ instance={"url": instance.url, "token": token[instance.credentials.field]}
226
+ ) as acs_api:
227
+ notifiers = acs_api.list_notifiers()
228
+ desired = self.get_desired_state(gqlapi.query, notifiers)
229
+ current = acs_api.get_custom_policies()
230
+ self.reconcile(
231
+ desired=desired, current=current, acs=acs_api, dry_run=dry_run
232
+ )
reconcile/acs_rbac.py CHANGED
@@ -8,24 +8,17 @@ from typing import (
8
8
 
9
9
  from pydantic import BaseModel
10
10
 
11
- from reconcile.gql_definitions.acs.acs_instances import AcsInstanceV1
12
- from reconcile.gql_definitions.acs.acs_instances import query as acs_instances_query
13
11
  from reconcile.gql_definitions.acs.acs_rbac import OidcPermissionAcsV1
14
12
  from reconcile.gql_definitions.acs.acs_rbac import query as acs_rbac_query
15
13
  from reconcile.typed_queries.app_interface_vault_settings import (
16
14
  get_app_interface_vault_settings,
17
15
  )
18
16
  from reconcile.utils import gql
19
- from reconcile.utils.acs_api import (
20
- AcsApi,
21
- Group,
22
- RbacResources,
23
- )
17
+ from reconcile.utils.acs.rbac import AcsRbacApi, Group, RbacResources
24
18
  from reconcile.utils.differ import (
25
19
  DiffPair,
26
20
  diff_iterables,
27
21
  )
28
- from reconcile.utils.exceptions import AppInterfaceSettingsError
29
22
  from reconcile.utils.runtime.integration import (
30
23
  NoParams,
31
24
  QontractReconcileIntegration,
@@ -143,20 +136,6 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
143
136
  def name(self) -> str:
144
137
  return self.qontract_integration.replace("_", "-")
145
138
 
146
- def get_acs_instance(self, query_func: Callable) -> AcsInstanceV1:
147
- """
148
- Get an ACS instance
149
-
150
- :param query_func: function which queries GQL Server
151
- """
152
- if instances := acs_instances_query(query_func=query_func).instances:
153
- # mirroring logic for gitlab instances
154
- # current assumption is for appsre to only utilize one instance
155
- if len(instances) != 1:
156
- raise AppInterfaceSettingsError("More than one ACS instance found!")
157
- return instances[0]
158
- raise AppInterfaceSettingsError("No ACS instance found!")
159
-
160
139
  def get_desired_state(self, query_func: Callable) -> list[AcsRole]:
161
140
  """
162
141
  Get desired ACS roles and associated users from App Interface
@@ -255,7 +234,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
255
234
  def add_rbac_for_role(
256
235
  self,
257
236
  role: AcsRole,
258
- acs: AcsApi,
237
+ acs: AcsRbacApi,
259
238
  admin_access_scope_id: str,
260
239
  permission_set_id: str,
261
240
  auth_id: str,
@@ -298,7 +277,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
298
277
 
299
278
  if not dry_run:
300
279
  additions = [
301
- AcsApi.GroupAdd(
280
+ AcsRbacApi.GroupAdd(
302
281
  role_name=role.name,
303
282
  key=a.key,
304
283
  value=a.value,
@@ -317,7 +296,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
317
296
  self,
318
297
  to_add: dict[str, AcsRole],
319
298
  rbac_api_resources: RbacResources,
320
- acs: AcsApi,
299
+ acs: AcsRbacApi,
321
300
  auth_id: str,
322
301
  dry_run: bool,
323
302
  ) -> list[Exception]:
@@ -351,7 +330,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
351
330
  def delete_rbac_for_role(
352
331
  self,
353
332
  role: AcsRole,
354
- acs: AcsApi,
333
+ acs: AcsRbacApi,
355
334
  access_scope_id: str,
356
335
  admin_access_scope_id: str,
357
336
  groups: list[Group],
@@ -394,7 +373,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
394
373
  self,
395
374
  to_delete: dict[str, AcsRole],
396
375
  rbac_api_resources: RbacResources,
397
- acs: AcsApi,
376
+ acs: AcsRbacApi,
398
377
  auth_id: str,
399
378
  dry_run: bool,
400
379
  ) -> list[Exception]:
@@ -430,7 +409,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
430
409
  def update_rbac_for_role(
431
410
  self,
432
411
  role_diff_pair: DiffPair[AcsRole, AcsRole],
433
- acs: AcsApi,
412
+ acs: AcsRbacApi,
434
413
  role_group_mappings: dict[str, dict[str, Group]],
435
414
  access_scope_id: str,
436
415
  permission_set_id: str,
@@ -465,7 +444,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
465
444
  for d in diff.delete.values()
466
445
  ]
467
446
  new = [
468
- AcsApi.GroupAdd(
447
+ AcsRbacApi.GroupAdd(
469
448
  role_name=role_diff_pair.desired.name,
470
449
  key=a.key,
471
450
  value=a.value,
@@ -513,7 +492,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
513
492
  self,
514
493
  to_update: dict[str, DiffPair[AcsRole, AcsRole]],
515
494
  rbac_api_resources: RbacResources,
516
- acs: AcsApi,
495
+ acs: AcsRbacApi,
517
496
  auth_id: str,
518
497
  dry_run: bool,
519
498
  ) -> list[Exception]:
@@ -558,7 +537,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
558
537
  desired: list[AcsRole],
559
538
  current: list[AcsRole],
560
539
  rbac_api_resources: RbacResources,
561
- acs: AcsApi,
540
+ acs: AcsRbacApi,
562
541
  auth_provider_id: str,
563
542
  dry_run: bool,
564
543
  ) -> None:
@@ -602,7 +581,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
602
581
  dry_run: bool,
603
582
  ) -> None:
604
583
  gqlapi = gql.get_api()
605
- instance = self.get_acs_instance(gqlapi.query)
584
+ instance = AcsRbacApi.get_acs_instance(gqlapi.query)
606
585
 
607
586
  vault_settings = get_app_interface_vault_settings()
608
587
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
@@ -610,7 +589,7 @@ class AcsRbacIntegration(QontractReconcileIntegration[NoParams]):
610
589
 
611
590
  desired = self.get_desired_state(gqlapi.query)
612
591
 
613
- with AcsApi(
592
+ with AcsRbacApi(
614
593
  instance={"url": instance.url, "token": token[instance.credentials.field]}
615
594
  ) as acs_api:
616
595
  rbac_api_resources = acs_api.get_rbac_resources()
reconcile/cli.py CHANGED
@@ -2952,6 +2952,17 @@ def acs_rbac(ctx):
2952
2952
  )
2953
2953
 
2954
2954
 
2955
+ @integration.command(short_help="Manages RHACS security policy configurations")
2956
+ @click.pass_context
2957
+ def acs_policies(ctx):
2958
+ from reconcile import acs_policies
2959
+
2960
+ run_class_integration(
2961
+ integration=acs_policies.AcsPoliciesIntegration(),
2962
+ ctx=ctx.obj,
2963
+ )
2964
+
2965
+
2955
2966
  def get_integration_cli_meta() -> dict[str, IntegrationMeta]:
2956
2967
  """
2957
2968
  returns all integrations known to cli.py via click introspection
@@ -0,0 +1,159 @@
1
+ """
2
+ Generated by qenerate plugin=pydantic_v1. DO NOT MODIFY MANUALLY!
3
+ """
4
+ from collections.abc import Callable # noqa: F401 # pylint: disable=W0611
5
+ from datetime import datetime # noqa: F401 # pylint: disable=W0611
6
+ from enum import Enum # noqa: F401 # pylint: disable=W0611
7
+ from typing import ( # noqa: F401 # pylint: disable=W0611
8
+ Any,
9
+ Optional,
10
+ Union,
11
+ )
12
+
13
+ from pydantic import ( # noqa: F401 # pylint: disable=W0611
14
+ BaseModel,
15
+ Extra,
16
+ Field,
17
+ Json,
18
+ )
19
+
20
+
21
+ DEFINITION = """
22
+ query AcsPolicy {
23
+ acs_policies: acs_policy_v1 {
24
+ name
25
+ description
26
+ severity
27
+ notifiers
28
+ categories
29
+ scope {
30
+ level
31
+ ... on AcsPolicyScopeCluster_v1 {
32
+ clusters {
33
+ name
34
+ }
35
+ }
36
+ ... on AcsPolicyScopeNamespace_v1 {
37
+ namespaces {
38
+ name
39
+ cluster {
40
+ name
41
+ }
42
+ }
43
+ }
44
+ }
45
+ conditions {
46
+ policyField
47
+ ... on AcsPolicyConditionsCvss_v1 {
48
+ comparison
49
+ score
50
+ }
51
+ ... on AcsPolicyConditionsSeverity_v1 {
52
+ comparison
53
+ level
54
+ }
55
+ ... on AcsPolicyConditionsCve_v1 {
56
+ fixable
57
+ }
58
+ ... on AcsPolicyConditionsImageTag_v1 {
59
+ tags
60
+ negate
61
+ }
62
+ ... on AcsPolicyConditionsImageAge_v1 {
63
+ days
64
+ }
65
+ }
66
+ }
67
+ }
68
+ """
69
+
70
+
71
+ class ConfiguredBaseModel(BaseModel):
72
+ class Config:
73
+ smart_union=True
74
+ extra=Extra.forbid
75
+
76
+
77
+ class AcsPolicyScopeV1(ConfiguredBaseModel):
78
+ level: str = Field(..., alias="level")
79
+
80
+
81
+ class ClusterV1(ConfiguredBaseModel):
82
+ name: str = Field(..., alias="name")
83
+
84
+
85
+ class AcsPolicyScopeClusterV1(AcsPolicyScopeV1):
86
+ clusters: list[ClusterV1] = Field(..., alias="clusters")
87
+
88
+
89
+ class NamespaceV1_ClusterV1(ConfiguredBaseModel):
90
+ name: str = Field(..., alias="name")
91
+
92
+
93
+ class NamespaceV1(ConfiguredBaseModel):
94
+ name: str = Field(..., alias="name")
95
+ cluster: NamespaceV1_ClusterV1 = Field(..., alias="cluster")
96
+
97
+
98
+ class AcsPolicyScopeNamespaceV1(AcsPolicyScopeV1):
99
+ namespaces: list[NamespaceV1] = Field(..., alias="namespaces")
100
+
101
+
102
+ class AcsPolicyConditionsV1(ConfiguredBaseModel):
103
+ policy_field: str = Field(..., alias="policyField")
104
+
105
+
106
+ class AcsPolicyConditionsCvssV1(AcsPolicyConditionsV1):
107
+ comparison: str = Field(..., alias="comparison")
108
+ score: int = Field(..., alias="score")
109
+
110
+
111
+ class AcsPolicyConditionsSeverityV1(AcsPolicyConditionsV1):
112
+ comparison: str = Field(..., alias="comparison")
113
+ level: str = Field(..., alias="level")
114
+
115
+
116
+ class AcsPolicyConditionsCveV1(AcsPolicyConditionsV1):
117
+ fixable: bool = Field(..., alias="fixable")
118
+
119
+
120
+ class AcsPolicyConditionsImageTagV1(AcsPolicyConditionsV1):
121
+ tags: list[str] = Field(..., alias="tags")
122
+ negate: Optional[bool] = Field(..., alias="negate")
123
+
124
+
125
+ class AcsPolicyConditionsImageAgeV1(AcsPolicyConditionsV1):
126
+ days: int = Field(..., alias="days")
127
+
128
+
129
+ class AcsPolicyV1(ConfiguredBaseModel):
130
+ name: str = Field(..., alias="name")
131
+ description: Optional[str] = Field(..., alias="description")
132
+ severity: str = Field(..., alias="severity")
133
+ notifiers: Optional[list[str]] = Field(..., alias="notifiers")
134
+ categories: list[str] = Field(..., alias="categories")
135
+ scope: Union[AcsPolicyScopeClusterV1, AcsPolicyScopeNamespaceV1, AcsPolicyScopeV1] = Field(..., alias="scope")
136
+ conditions: list[Union[AcsPolicyConditionsCvssV1, AcsPolicyConditionsSeverityV1, AcsPolicyConditionsImageTagV1, AcsPolicyConditionsCveV1, AcsPolicyConditionsImageAgeV1, AcsPolicyConditionsV1]] = Field(..., alias="conditions")
137
+
138
+
139
+ class AcsPolicyQueryData(ConfiguredBaseModel):
140
+ acs_policies: Optional[list[AcsPolicyV1]] = Field(..., alias="acs_policies")
141
+
142
+
143
+ def query(query_func: Callable, **kwargs: Any) -> AcsPolicyQueryData:
144
+ """
145
+ This is a convenience function which queries and parses the data into
146
+ concrete types. It should be compatible with most GQL clients.
147
+ You do not have to use it to consume the generated data classes.
148
+ Alternatively, you can also mime and alternate the behavior
149
+ of this function in the caller.
150
+
151
+ Parameters:
152
+ query_func (Callable): Function which queries your GQL Server
153
+ kwargs: optional arguments that will be passed to the query function
154
+
155
+ Returns:
156
+ AcsPolicyQueryData: queried data parsed into generated classes
157
+ """
158
+ raw_data: dict[Any, Any] = query_func(DEFINITION, **kwargs)
159
+ return AcsPolicyQueryData(**raw_data)