qontract-reconcile 0.10.1rc506__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.
- {qontract_reconcile-0.10.1rc506.dist-info → qontract_reconcile-0.10.1rc507.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc506.dist-info → qontract_reconcile-0.10.1rc507.dist-info}/RECORD +11 -9
- reconcile/acs_policies.py +232 -0
- reconcile/acs_rbac.py +12 -33
- reconcile/cli.py +11 -0
- reconcile/gql_definitions/acs/acs_policies.py +159 -0
- reconcile/test/test_acs_policies.py +444 -0
- reconcile/test/test_acs_rbac.py +51 -51
- reconcile/utils/acs_api.py +0 -326
- {qontract_reconcile-0.10.1rc506.dist-info → qontract_reconcile-0.10.1rc507.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc506.dist-info → qontract_reconcile-0.10.1rc507.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc506.dist-info → qontract_reconcile-0.10.1rc507.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc506.dist-info → qontract_reconcile-0.10.1rc507.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: qontract-reconcile
|
3
|
-
Version: 0.10.
|
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
|
{qontract_reconcile-0.10.1rc506.dist-info → qontract_reconcile-0.10.1rc507.dist-info}/RECORD
RENAMED
@@ -1,5 +1,6 @@
|
|
1
1
|
reconcile/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
reconcile/
|
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=
|
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/
|
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
|
@@ -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.
|
659
|
-
qontract_reconcile-0.10.
|
660
|
-
qontract_reconcile-0.10.
|
661
|
-
qontract_reconcile-0.10.
|
662
|
-
qontract_reconcile-0.10.
|
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.
|
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:
|
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
|
-
|
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:
|
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:
|
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:
|
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:
|
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
|
-
|
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:
|
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:
|
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 =
|
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
|
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)
|