qontract-reconcile 0.10.1rc508__py3-none-any.whl → 0.10.1rc510__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.1rc508.dist-info → qontract_reconcile-0.10.1rc510.dist-info}/METADATA +1 -1
- {qontract_reconcile-0.10.1rc508.dist-info → qontract_reconcile-0.10.1rc510.dist-info}/RECORD +10 -6
- reconcile/openshift_clusterrolebindings.py +1 -0
- reconcile/utils/acs/__init__.py +0 -0
- reconcile/utils/acs/base.py +72 -0
- reconcile/utils/acs/policies.py +151 -0
- reconcile/utils/acs/rbac.py +277 -0
- {qontract_reconcile-0.10.1rc508.dist-info → qontract_reconcile-0.10.1rc510.dist-info}/WHEEL +0 -0
- {qontract_reconcile-0.10.1rc508.dist-info → qontract_reconcile-0.10.1rc510.dist-info}/entry_points.txt +0 -0
- {qontract_reconcile-0.10.1rc508.dist-info → qontract_reconcile-0.10.1rc510.dist-info}/top_level.txt +0 -0
{qontract_reconcile-0.10.1rc508.dist-info → qontract_reconcile-0.10.1rc510.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.1rc510
|
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.1rc508.dist-info → qontract_reconcile-0.10.1rc510.dist-info}/RECORD
RENAMED
@@ -62,7 +62,7 @@ reconcile/ocm_update_recommended_version.py,sha256=IYkfLXIprOW1jguZeELcGP1iBPuj-
|
|
62
62
|
reconcile/ocm_upgrade_scheduler_org_updater.py,sha256=ta8hMJ-su5mRcPpYrvB1COsojXV-SU3PzLPbQhy2Q0I,4190
|
63
63
|
reconcile/openshift_base.py,sha256=7aifvl-ay5wpY6encbUX9pGbKdjiwJmevZ3XWGRzpCM,49696
|
64
64
|
reconcile/openshift_cluster_bots.py,sha256=cdvIIXBh7QZl3g7ZheCR2NeI_Pq4eKn-hJPoixl9NS8,10168
|
65
|
-
reconcile/openshift_clusterrolebindings.py,sha256=
|
65
|
+
reconcile/openshift_clusterrolebindings.py,sha256=QfSy1Ik8eEY5XObc1Q4xyhqyErZenJmbPv_u9wcDNNo,5864
|
66
66
|
reconcile/openshift_groups.py,sha256=d-qGI1aUEpZZLZq7PuSnjVDgsy5EB063CQr2tNvYPCE,9419
|
67
67
|
reconcile/openshift_limitranges.py,sha256=UvCGo_OQ4XoDK55TJmn55qEhhlkhLzhU12tX8nT5kPQ,3442
|
68
68
|
reconcile/openshift_namespace_labels.py,sha256=dLkQgtgsD51WtDHiQOc-lF2yaaFzkiUAZ7ueKUkg-ZM,15669
|
@@ -585,6 +585,10 @@ reconcile/utils/unleash.py,sha256=1D56CsZfE3ShDtN3IErE1T2eeIwNmxhK-yYbCotJ99E,36
|
|
585
585
|
reconcile/utils/vault.py,sha256=S0eHqvZ9N3fya1E8YDaUffEvLk_fdtpzL4rvWn6f828,14991
|
586
586
|
reconcile/utils/vaultsecretref.py,sha256=3Ed2uBy36TzSvL0B-l4FoWQqB2SbBKDKEuUPIO608Bo,931
|
587
587
|
reconcile/utils/vcs.py,sha256=o1r0n_IrU2El75CED_6sjR2GZGM-exuWsj5F7jONaMU,6779
|
588
|
+
reconcile/utils/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
589
|
+
reconcile/utils/acs/base.py,sha256=Qih-xZ3RBJZEE291iHHlv7lUY6ShcAvSj1PA3_aTTnM,2276
|
590
|
+
reconcile/utils/acs/policies.py,sha256=ucmvWhcu9I5uumOyFIr5POb64TqHNNJ8LutZWo2Jw7k,5099
|
591
|
+
reconcile/utils/acs/rbac.py,sha256=ugsLM9Pb7FbUbdq85E3VzXGMaB9ZovXob7tdWCxwqZ8,8808
|
588
592
|
reconcile/utils/cloud_resource_best_practice/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
589
593
|
reconcile/utils/cloud_resource_best_practice/aws_rds.py,sha256=EvE6XKLsrZ531MJptKqPht2lOETrOjySTHXk6CzMgo0,2279
|
590
594
|
reconcile/utils/glitchtip/__init__.py,sha256=FT6iBhGqoe7KExFdbgL8AYUb64iW_4snF5__Dcl7yt0,258
|
@@ -661,8 +665,8 @@ tools/test/test_app_interface_metrics_exporter.py,sha256=SX7qL3D1SIRKFo95FoQztvf
|
|
661
665
|
tools/test/test_qontract_cli.py,sha256=d18KrdhtUGqoC7_kWZU128U0-VJEj-0rjFkLVufcI6I,2755
|
662
666
|
tools/test/test_sd_app_sre_alert_report.py,sha256=v363r9zM7__0kR5K6mvJoGFcM9BvE33fWAayrqkpojA,2116
|
663
667
|
tools/test/test_sre_checkpoints.py,sha256=SKqPPTl9ua0RFdSSofnoQX-JZE6dFLO3LRhfQzqtfh8,2607
|
664
|
-
qontract_reconcile-0.10.
|
665
|
-
qontract_reconcile-0.10.
|
666
|
-
qontract_reconcile-0.10.
|
667
|
-
qontract_reconcile-0.10.
|
668
|
-
qontract_reconcile-0.10.
|
668
|
+
qontract_reconcile-0.10.1rc510.dist-info/METADATA,sha256=GZqpYG08olXHPFA1b6JcnDqPweC-vf7spJk2BXC6vZU,2349
|
669
|
+
qontract_reconcile-0.10.1rc510.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
670
|
+
qontract_reconcile-0.10.1rc510.dist-info/entry_points.txt,sha256=rTjAv28I_CHLM8ID3OPqMI_suoQ9s7tFbim4aYjn9kk,376
|
671
|
+
qontract_reconcile-0.10.1rc510.dist-info/top_level.txt,sha256=l5ISPoXzt0SdR4jVdkfa7RPSKNc8zAHYWAnR-Dw8Ey8,24
|
672
|
+
qontract_reconcile-0.10.1rc510.dist-info/RECORD,,
|
@@ -159,6 +159,7 @@ def run(dry_run, thread_pool_size=10, internal=None, use_jump_host=True, defer=N
|
|
159
159
|
cluster_info
|
160
160
|
for cluster_info in queries.get_clusters()
|
161
161
|
if cluster_info.get("managedClusterRoles")
|
162
|
+
and cluster_info.get("automationToken")
|
162
163
|
]
|
163
164
|
ri, oc_map = ob.fetch_current_state(
|
164
165
|
clusters=clusters,
|
File without changes
|
@@ -0,0 +1,72 @@
|
|
1
|
+
from collections.abc import Callable
|
2
|
+
from typing import (
|
3
|
+
Any,
|
4
|
+
Optional,
|
5
|
+
Self,
|
6
|
+
)
|
7
|
+
|
8
|
+
import requests
|
9
|
+
|
10
|
+
from reconcile.gql_definitions.acs.acs_instances import AcsInstanceV1
|
11
|
+
from reconcile.gql_definitions.acs.acs_instances import query as acs_instances_query
|
12
|
+
from reconcile.utils.exceptions import AppInterfaceSettingsError
|
13
|
+
|
14
|
+
|
15
|
+
class AcsBaseApi:
|
16
|
+
def __init__(
|
17
|
+
self,
|
18
|
+
instance: Any,
|
19
|
+
timeout: int = 30,
|
20
|
+
) -> None:
|
21
|
+
self.base_url = instance["url"]
|
22
|
+
self.token = instance["token"]
|
23
|
+
self.timeout = timeout
|
24
|
+
self.session = requests.Session()
|
25
|
+
|
26
|
+
def __enter__(self) -> Self:
|
27
|
+
return self
|
28
|
+
|
29
|
+
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
|
30
|
+
self.session.close()
|
31
|
+
|
32
|
+
@staticmethod
|
33
|
+
def get_acs_instance(query_func: Callable) -> AcsInstanceV1:
|
34
|
+
"""
|
35
|
+
Get an ACS instance
|
36
|
+
|
37
|
+
:param query_func: function which queries GQL Server
|
38
|
+
"""
|
39
|
+
if instances := acs_instances_query(query_func=query_func).instances:
|
40
|
+
# mirroring logic for gitlab instances
|
41
|
+
# current assumption is for appsre to only utilize one instance
|
42
|
+
if len(instances) != 1:
|
43
|
+
raise AppInterfaceSettingsError("More than one ACS instance found!")
|
44
|
+
return instances[0]
|
45
|
+
raise AppInterfaceSettingsError("No ACS instance found!")
|
46
|
+
|
47
|
+
@staticmethod
|
48
|
+
def check_len_attributes(attrs: list[Any], api_data: Any) -> None:
|
49
|
+
# generic attribute check function for expected types with valid len()
|
50
|
+
for attr in attrs:
|
51
|
+
value = api_data.get(attr)
|
52
|
+
if value is None or len(value) == 0:
|
53
|
+
raise ValueError(
|
54
|
+
f"Attribute '{attr}' must exist and not be empty\n\t{api_data}"
|
55
|
+
)
|
56
|
+
|
57
|
+
def generic_request(
|
58
|
+
self, path: str, verb: str, json: Optional[Any] = None
|
59
|
+
) -> requests.Response:
|
60
|
+
url = f"{self.base_url}{path}"
|
61
|
+
headers = {
|
62
|
+
"Authorization": f"Bearer {self.token}",
|
63
|
+
}
|
64
|
+
response = self.session.request(
|
65
|
+
verb,
|
66
|
+
url,
|
67
|
+
headers=headers,
|
68
|
+
json=json,
|
69
|
+
timeout=self.timeout,
|
70
|
+
)
|
71
|
+
response.raise_for_status()
|
72
|
+
return response
|
@@ -0,0 +1,151 @@
|
|
1
|
+
from typing import Any, Optional
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
from reconcile.utils.acs.base import AcsBaseApi
|
6
|
+
|
7
|
+
|
8
|
+
class Scope(BaseModel):
|
9
|
+
"""
|
10
|
+
Scope represents a single cluster or namespace that an acs security policy is applied to.
|
11
|
+
"""
|
12
|
+
|
13
|
+
cluster: str
|
14
|
+
namespace: Optional[str]
|
15
|
+
|
16
|
+
|
17
|
+
class PolicyCondition(BaseModel):
|
18
|
+
"""
|
19
|
+
PolicyCondition represents a single condition criteria enforced within an ACS security policy.
|
20
|
+
Current attributes support subset of "build" lifecycle condition criteria.
|
21
|
+
See section 6.4.3 for table of all condition criteria:
|
22
|
+
https://access.redhat.com/documentation/en-us/red_hat_advanced_cluster_security_for_kubernetes/4.3/html/operating/manage-security-policies
|
23
|
+
"""
|
24
|
+
|
25
|
+
field_name: str
|
26
|
+
negate: Optional[bool]
|
27
|
+
values: list[str]
|
28
|
+
|
29
|
+
|
30
|
+
class Policy(BaseModel):
|
31
|
+
"""
|
32
|
+
Policy is minimum attributes required to represent an ACS security policy
|
33
|
+
https://access.redhat.com/documentation/en-us/red_hat_advanced_cluster_security_for_kubernetes/4.3/html/operating/manage-security-policies
|
34
|
+
"""
|
35
|
+
|
36
|
+
name: str
|
37
|
+
description: str
|
38
|
+
notifiers: list[str]
|
39
|
+
categories: list[str]
|
40
|
+
severity: str
|
41
|
+
scope: list[Scope]
|
42
|
+
conditions: list[PolicyCondition]
|
43
|
+
|
44
|
+
|
45
|
+
class AcsPolicyApi(AcsBaseApi):
|
46
|
+
"""
|
47
|
+
Implements methods to support reconcile operations against the ACS PolicyService api
|
48
|
+
"""
|
49
|
+
|
50
|
+
def _build_policy(
|
51
|
+
self, api_policy: Any, conditions: list[PolicyCondition]
|
52
|
+
) -> Policy:
|
53
|
+
return Policy(
|
54
|
+
name=api_policy["name"],
|
55
|
+
description=api_policy["description"],
|
56
|
+
notifiers=sorted(api_policy["notifiers"]),
|
57
|
+
categories=sorted(api_policy["categories"]),
|
58
|
+
severity=api_policy["severity"],
|
59
|
+
scope=sorted(
|
60
|
+
[
|
61
|
+
Scope(
|
62
|
+
cluster=s["cluster"],
|
63
|
+
namespace=s["namespace"],
|
64
|
+
)
|
65
|
+
for s in api_policy["scope"]
|
66
|
+
],
|
67
|
+
key=lambda s: s.cluster,
|
68
|
+
),
|
69
|
+
conditions=conditions,
|
70
|
+
)
|
71
|
+
|
72
|
+
def _build_policy_condition(self, api_policy_group: Any) -> PolicyCondition:
|
73
|
+
return PolicyCondition(
|
74
|
+
field_name=api_policy_group["fieldName"],
|
75
|
+
values=[v["value"] for v in api_policy_group["values"]],
|
76
|
+
negate=api_policy_group["negate"],
|
77
|
+
)
|
78
|
+
|
79
|
+
def list_custom_policies(self) -> list[Any]:
|
80
|
+
# retrieve summary data for each custom policy
|
81
|
+
return [
|
82
|
+
p
|
83
|
+
for p in self.generic_request("/v1/policies", "GET").json()["policies"]
|
84
|
+
if not p["isDefault"]
|
85
|
+
]
|
86
|
+
|
87
|
+
def get_custom_policies(self) -> list[Policy]:
|
88
|
+
custom_policy_ids = [p["id"] for p in self.list_custom_policies()]
|
89
|
+
# make individual policy requests to obtain further details
|
90
|
+
custom_policies_api_result = [
|
91
|
+
self.generic_request(f"/v1/policies/{pid}", "GET").json()
|
92
|
+
for pid in custom_policy_ids
|
93
|
+
]
|
94
|
+
return [
|
95
|
+
self._build_policy(
|
96
|
+
api_policy=cp,
|
97
|
+
conditions=[
|
98
|
+
self._build_policy_condition(group)
|
99
|
+
for section in cp["policySections"]
|
100
|
+
for group in section.get("policyGroups", [])
|
101
|
+
],
|
102
|
+
)
|
103
|
+
for cp in custom_policies_api_result
|
104
|
+
]
|
105
|
+
|
106
|
+
def create_or_update_policy(self, desired: Policy, id: str = "") -> None:
|
107
|
+
body = {
|
108
|
+
"name": desired.name,
|
109
|
+
"description": desired.description,
|
110
|
+
"categories": desired.categories,
|
111
|
+
"severity": desired.severity,
|
112
|
+
"notifiers": desired.notifiers,
|
113
|
+
"isDefault": False,
|
114
|
+
"disabled": False,
|
115
|
+
"scope": [
|
116
|
+
{"cluster": s.cluster, "namespace": s.namespace} for s in desired.scope
|
117
|
+
],
|
118
|
+
"lifecycleStages": [
|
119
|
+
"BUILD"
|
120
|
+
], # all currently supported policy criteria are classified as 'build' stage
|
121
|
+
"policySections": [
|
122
|
+
{
|
123
|
+
"sectionName": "primary",
|
124
|
+
"policyGroups": [
|
125
|
+
{
|
126
|
+
"fieldName": c.field_name,
|
127
|
+
"negate": c.negate,
|
128
|
+
"values": [{"value": v} for v in c.values],
|
129
|
+
}
|
130
|
+
for c in desired.conditions
|
131
|
+
],
|
132
|
+
}
|
133
|
+
],
|
134
|
+
}
|
135
|
+
if id:
|
136
|
+
self.generic_request(f"/v1/policies/{id}", "PUT", body)
|
137
|
+
else:
|
138
|
+
self.generic_request("/v1/policies", "POST", body)
|
139
|
+
|
140
|
+
def delete_policy(self, id: str) -> None:
|
141
|
+
self.generic_request(f"/v1/policies/{id}", "DELETE")
|
142
|
+
|
143
|
+
class NotifierIdentifiers(BaseModel):
|
144
|
+
id: str
|
145
|
+
name: str
|
146
|
+
|
147
|
+
def list_notifiers(self) -> list[NotifierIdentifiers]:
|
148
|
+
return [
|
149
|
+
self.NotifierIdentifiers(id=c["id"], name=c["name"])
|
150
|
+
for c in self.generic_request("/v1/notifiers", "GET").json()["notifiers"]
|
151
|
+
]
|
@@ -0,0 +1,277 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from pydantic import BaseModel
|
4
|
+
|
5
|
+
from reconcile.utils.acs.base import AcsBaseApi
|
6
|
+
|
7
|
+
|
8
|
+
class Role(BaseModel):
|
9
|
+
name: str
|
10
|
+
permission_set_id: str
|
11
|
+
access_scope_id: str
|
12
|
+
description: str
|
13
|
+
system_default: bool
|
14
|
+
|
15
|
+
def __init__(self, api_data: Any) -> None:
|
16
|
+
# attributes defined within stackrox(ACS) API for GET /v1/roles
|
17
|
+
AcsBaseApi.check_len_attributes(
|
18
|
+
["name", "permissionSetId", "accessScopeId"],
|
19
|
+
api_data,
|
20
|
+
)
|
21
|
+
|
22
|
+
# traits is populated for system default resources and contains `origin: DEFAULT`
|
23
|
+
# this attribute is used to ignore such resources from reconciliation
|
24
|
+
traits = api_data.get("traits")
|
25
|
+
is_default = traits is not None and traits.get("origin") == "DEFAULT"
|
26
|
+
|
27
|
+
super().__init__(
|
28
|
+
name=api_data["name"],
|
29
|
+
permission_set_id=api_data["permissionSetId"],
|
30
|
+
access_scope_id=api_data["accessScopeId"],
|
31
|
+
description=api_data.get("description", ""),
|
32
|
+
system_default=is_default,
|
33
|
+
)
|
34
|
+
|
35
|
+
|
36
|
+
class Group(BaseModel):
|
37
|
+
id: str
|
38
|
+
role_name: str
|
39
|
+
auth_provider_id: str
|
40
|
+
key: str
|
41
|
+
value: str
|
42
|
+
|
43
|
+
def __init__(self, api_data: Any) -> None:
|
44
|
+
# attributes defined within stackrox(ACS) API for GET /v1/groups
|
45
|
+
AcsBaseApi.check_len_attributes(["roleName", "props"], api_data)
|
46
|
+
if api_data["roleName"] != "None":
|
47
|
+
AcsBaseApi.check_len_attributes(
|
48
|
+
["id", "authProviderId", "key", "value"], api_data["props"]
|
49
|
+
)
|
50
|
+
else:
|
51
|
+
# it is valid for the default None group to contain empty key/value
|
52
|
+
AcsBaseApi.check_len_attributes(["id", "authProviderId"], api_data["props"])
|
53
|
+
|
54
|
+
super().__init__(
|
55
|
+
role_name=api_data["roleName"],
|
56
|
+
id=api_data["props"]["id"],
|
57
|
+
auth_provider_id=api_data["props"]["authProviderId"],
|
58
|
+
key=api_data["props"]["key"],
|
59
|
+
value=api_data["props"]["value"],
|
60
|
+
)
|
61
|
+
|
62
|
+
|
63
|
+
class AccessScope(BaseModel):
|
64
|
+
id: str
|
65
|
+
name: str
|
66
|
+
description: str
|
67
|
+
clusters: list[str]
|
68
|
+
namespaces: list[dict[str, str]]
|
69
|
+
|
70
|
+
def __init__(self, api_data: Any) -> None:
|
71
|
+
# attributes defined within stackrox(ACS) API for GET /v1/simpleaccessscopes/{id}
|
72
|
+
unrestricted = False
|
73
|
+
AcsBaseApi.check_len_attributes(["id", "name"], api_data)
|
74
|
+
|
75
|
+
# it is valid for the default Unrestricted access scope to have null 'rules'
|
76
|
+
unrestricted = api_data["name"] == "Unrestricted"
|
77
|
+
if not unrestricted:
|
78
|
+
AcsBaseApi.check_len_attributes(["rules"], api_data)
|
79
|
+
|
80
|
+
super().__init__(
|
81
|
+
id=api_data["id"],
|
82
|
+
name=api_data["name"],
|
83
|
+
clusters=[]
|
84
|
+
if unrestricted
|
85
|
+
else api_data["rules"].get("includedClusters", []),
|
86
|
+
namespaces=[]
|
87
|
+
if unrestricted
|
88
|
+
else api_data["rules"].get("includedNamespaces", []),
|
89
|
+
description=api_data.get("description", ""),
|
90
|
+
)
|
91
|
+
|
92
|
+
|
93
|
+
class PermissionSet(BaseModel):
|
94
|
+
id: str
|
95
|
+
name: str
|
96
|
+
|
97
|
+
def __init__(self, api_data: Any) -> None:
|
98
|
+
# attributes defined within stackrox(ACS) API for GET /v1/permissionsets/{id}
|
99
|
+
AcsBaseApi.check_len_attributes(["id", "name"], api_data)
|
100
|
+
|
101
|
+
super().__init__(id=api_data["id"], name=api_data["name"])
|
102
|
+
|
103
|
+
|
104
|
+
class RbacResources(BaseModel):
|
105
|
+
roles: list[Role]
|
106
|
+
access_scopes: list[AccessScope]
|
107
|
+
groups: list[Group]
|
108
|
+
permission_sets: list[PermissionSet]
|
109
|
+
|
110
|
+
|
111
|
+
class AcsRbacApi(AcsBaseApi):
|
112
|
+
def get_roles(self) -> list[Role]:
|
113
|
+
response = self.generic_request("/v1/roles", "GET")
|
114
|
+
return [Role(r) for r in response.json()["roles"]]
|
115
|
+
|
116
|
+
def create_role(
|
117
|
+
self, name: str, desc: str, permission_set_id: str, access_scope_id: str
|
118
|
+
) -> None:
|
119
|
+
json = {
|
120
|
+
"name": name,
|
121
|
+
"description": desc,
|
122
|
+
"permissionSetId": permission_set_id,
|
123
|
+
"accessScopeId": access_scope_id,
|
124
|
+
}
|
125
|
+
|
126
|
+
self.generic_request(f"/v1/roles/{name}", "POST", json)
|
127
|
+
|
128
|
+
def update_role(
|
129
|
+
self, name: str, desc: str, permission_set_id: str, access_scope_id: str
|
130
|
+
) -> None:
|
131
|
+
json = {
|
132
|
+
"name": name,
|
133
|
+
"description": desc,
|
134
|
+
"permissionSetId": permission_set_id,
|
135
|
+
"accessScopeId": access_scope_id,
|
136
|
+
}
|
137
|
+
|
138
|
+
self.generic_request(f"/v1/roles/{name}", "PUT", json)
|
139
|
+
|
140
|
+
def delete_role(self, name: str) -> None:
|
141
|
+
self.generic_request(f"/v1/roles/{name}", "DELETE")
|
142
|
+
|
143
|
+
def get_groups(self) -> list[Group]:
|
144
|
+
response = self.generic_request("/v1/groups", "GET")
|
145
|
+
return [Group(g) for g in response.json()["groups"]]
|
146
|
+
|
147
|
+
class GroupAdd(BaseModel):
|
148
|
+
role_name: str
|
149
|
+
key: str
|
150
|
+
value: str
|
151
|
+
auth_provider_id: str
|
152
|
+
|
153
|
+
def create_group_batch(self, additions: list[GroupAdd]) -> None:
|
154
|
+
json = {
|
155
|
+
"previousGroups": [],
|
156
|
+
"requiredGroups": [
|
157
|
+
{
|
158
|
+
"roleName": group.role_name,
|
159
|
+
"props": {
|
160
|
+
"id": "",
|
161
|
+
"authProviderId": group.auth_provider_id,
|
162
|
+
"key": group.key,
|
163
|
+
"value": group.value,
|
164
|
+
},
|
165
|
+
}
|
166
|
+
for group in additions
|
167
|
+
],
|
168
|
+
}
|
169
|
+
|
170
|
+
self.generic_request("/v1/groupsbatch", "POST", json)
|
171
|
+
|
172
|
+
def delete_group_batch(self, removals: list[Group]) -> None:
|
173
|
+
json = {
|
174
|
+
"previousGroups": [
|
175
|
+
{
|
176
|
+
"roleName": group.role_name,
|
177
|
+
"props": {
|
178
|
+
"id": group.id,
|
179
|
+
"authProviderId": group.auth_provider_id,
|
180
|
+
"key": group.key,
|
181
|
+
"value": group.value,
|
182
|
+
},
|
183
|
+
}
|
184
|
+
for group in removals
|
185
|
+
],
|
186
|
+
"requiredGroups": [],
|
187
|
+
}
|
188
|
+
|
189
|
+
self.generic_request("/v1/groupsbatch", "POST", json)
|
190
|
+
|
191
|
+
def update_group_batch(self, old: list[Group], new: list[GroupAdd]) -> None:
|
192
|
+
json = {
|
193
|
+
"previousGroups": [
|
194
|
+
{
|
195
|
+
"roleName": o.role_name,
|
196
|
+
"props": {
|
197
|
+
"id": o.id,
|
198
|
+
"authProviderId": o.auth_provider_id,
|
199
|
+
"key": o.key,
|
200
|
+
"value": o.value,
|
201
|
+
},
|
202
|
+
}
|
203
|
+
for o in old
|
204
|
+
],
|
205
|
+
"requiredGroups": [
|
206
|
+
{
|
207
|
+
"roleName": n.role_name,
|
208
|
+
"props": {
|
209
|
+
"id": "",
|
210
|
+
"authProviderId": n.auth_provider_id,
|
211
|
+
"key": n.key,
|
212
|
+
"value": n.value,
|
213
|
+
},
|
214
|
+
}
|
215
|
+
for n in new
|
216
|
+
],
|
217
|
+
}
|
218
|
+
self.generic_request("/v1/groupsbatch", "POST", json)
|
219
|
+
|
220
|
+
def get_access_scopes(self) -> list[AccessScope]:
|
221
|
+
response = self.generic_request("/v1/simpleaccessscopes", "GET")
|
222
|
+
return [AccessScope(a) for a in response.json()["accessScopes"]]
|
223
|
+
|
224
|
+
def create_access_scope(
|
225
|
+
self,
|
226
|
+
name: str,
|
227
|
+
desc: str,
|
228
|
+
clusters: list[str],
|
229
|
+
namespaces: list[dict[str, str]],
|
230
|
+
) -> str:
|
231
|
+
# response is the created access_scope id
|
232
|
+
json = {
|
233
|
+
"name": name,
|
234
|
+
"description": desc,
|
235
|
+
"rules": {
|
236
|
+
"includedClusters": clusters,
|
237
|
+
"includedNamespaces": namespaces,
|
238
|
+
},
|
239
|
+
}
|
240
|
+
|
241
|
+
response = self.generic_request("/v1/simpleaccessscopes", "POST", json)
|
242
|
+
|
243
|
+
return response.json()["id"]
|
244
|
+
|
245
|
+
def delete_access_scope(self, id: str) -> None:
|
246
|
+
self.generic_request(f"/v1/simpleaccessscopes/{id}", "DELETE")
|
247
|
+
|
248
|
+
def update_access_scope(
|
249
|
+
self,
|
250
|
+
id: str,
|
251
|
+
name: str,
|
252
|
+
desc: str,
|
253
|
+
clusters: list[str],
|
254
|
+
namespaces: list[dict[str, str]],
|
255
|
+
) -> None:
|
256
|
+
json = {
|
257
|
+
"name": name,
|
258
|
+
"description": desc,
|
259
|
+
"rules": {
|
260
|
+
"includedClusters": clusters,
|
261
|
+
"includedNamespaces": namespaces,
|
262
|
+
},
|
263
|
+
}
|
264
|
+
|
265
|
+
self.generic_request(f"/v1/simpleaccessscopes/{id}", "PUT", json)
|
266
|
+
|
267
|
+
def get_permission_sets(self) -> list[PermissionSet]:
|
268
|
+
response = self.generic_request("/v1/permissionsets", "GET")
|
269
|
+
return [PermissionSet(p) for p in response.json()["permissionSets"]]
|
270
|
+
|
271
|
+
def get_rbac_resources(self) -> RbacResources:
|
272
|
+
return RbacResources(
|
273
|
+
roles=self.get_roles(),
|
274
|
+
access_scopes=self.get_access_scopes(),
|
275
|
+
groups=self.get_groups(),
|
276
|
+
permission_sets=self.get_permission_sets(),
|
277
|
+
)
|
File without changes
|
File without changes
|
{qontract_reconcile-0.10.1rc508.dist-info → qontract_reconcile-0.10.1rc510.dist-info}/top_level.txt
RENAMED
File without changes
|