qontract-reconcile 0.10.2.dev204__py3-none-any.whl → 0.10.2.dev205__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.4
2
2
  Name: qontract-reconcile
3
- Version: 0.10.2.dev204
3
+ Version: 0.10.2.dev205
4
4
  Summary: Collection of tools to reconcile services with their desired state as defined in the app-interface DB.
5
5
  Project-URL: homepage, https://github.com/app-sre/qontract-reconcile
6
6
  Project-URL: repository, https://github.com/app-sre/qontract-reconcile
@@ -72,7 +72,7 @@ reconcile/openshift_prometheus_rules.py,sha256=onowXab248zmHH8SbYDTc1W1bl7JiqRFU
72
72
  reconcile/openshift_resourcequotas.py,sha256=yUi56PiOn3inMMfq_x_FEHmaW-reGipzoorjdar372g,2415
73
73
  reconcile/openshift_resources.py,sha256=I2nO_C37mG3rfyGrd4cGwN3mVseVGuTAHAyhFzLyqF4,1518
74
74
  reconcile/openshift_resources_base.py,sha256=3HudPdM7EE0HNWUn1eu0O20Ij25fqGisaDBMVvTk1fk,41768
75
- reconcile/openshift_rhcs_certs.py,sha256=snLx33sX2cj2TPGRgrEGDV6Ofm17IougnxT0T-5FNoA,9448
75
+ reconcile/openshift_rhcs_certs.py,sha256=lP0GwKMRl8YBzxrwdbBOxrPqIPYNmu2KkZPGzWKyRVU,9859
76
76
  reconcile/openshift_rolebindings.py,sha256=9mlJ2FjWUoH-rsjtasreA_hV-K5Z_YR00qR_RR60OZM,6555
77
77
  reconcile/openshift_routes.py,sha256=fXvuPSjcjVw1X3j2EQvUAdbOepmIFdKk-M3qP8QzPiw,1075
78
78
  reconcile/openshift_saas_deploy.py,sha256=T1dvb9zajisaJNjbnR6-AZHU-itscHtr4oCqLj8KCK0,13037
@@ -227,7 +227,7 @@ reconcile/glitchtip_project_alerts/integration.py,sha256=BgMx-NyV9mTuv7Sotb2OioC
227
227
  reconcile/glitchtip_project_dsn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
228
228
  reconcile/glitchtip_project_dsn/integration.py,sha256=2iugub-kHYkHNK33n0v9_TeWonuxCPah_VkoTPvaajE,8077
229
229
  reconcile/gql_definitions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
230
- reconcile/gql_definitions/introspection.json,sha256=Go8Ep6qN2COkqnWsqQLfiGDS1TbQFyK_fp5TiM0ghpo,2334795
230
+ reconcile/gql_definitions/introspection.json,sha256=oYlYgYdUjLtUwu9mJA-bh1y6BTTlWgVhL07N-9_9Du0,2335501
231
231
  reconcile/gql_definitions/acs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
232
232
  reconcile/gql_definitions/acs/acs_instances.py,sha256=L91WW9LbhJbBSrECqShQpFtjoBOsmNIYLRpMbx1io5o,2181
233
233
  reconcile/gql_definitions/acs/acs_policies.py,sha256=Ygpfl2-VkYLSlJvHgp_dJBfb66K_Rwfdfpsa18w1v1s,4338
@@ -296,7 +296,7 @@ reconcile/gql_definitions/common/pipeline_providers.py,sha256=9rpsqPuvj82B4ki56x
296
296
  reconcile/gql_definitions/common/quay_instances.py,sha256=toBkdYYVTmEafezAHZKgaW-mQ29xEW6jeronzsAlNyI,1786
297
297
  reconcile/gql_definitions/common/quay_orgs.py,sha256=NhA8kqvVUDbrsryEvEL5mlIv5R3T4XNhSRXtfL_yptY,1788
298
298
  reconcile/gql_definitions/common/reserved_networks.py,sha256=yP9qSQCaSQcva-ZgTnZp09qH27ur5_qK080ToIs04MY,2560
299
- reconcile/gql_definitions/common/rhcs_provider_settings.py,sha256=x3OmYjMCr9EzfkmLmbdx9zEKzqE9IczE8lcyzf_v9JU,1969
299
+ reconcile/gql_definitions/common/rhcs_provider_settings.py,sha256=88NwWT1kmbysze_w4YT6kiOYU2StDHHP6QRQXnPlpmQ,2057
300
300
  reconcile/gql_definitions/common/saas_files.py,sha256=d1L_S5LgCMa4QuAqZGQYTWb5L_nPCOxSEjU4O__OeBU,17728
301
301
  reconcile/gql_definitions/common/saas_target_namespaces.py,sha256=4VYP2VbwY8WVwtSFk2-jsUNhSmRD3X4FWKxetOKvmd0,2835
302
302
  reconcile/gql_definitions/common/saasherder_settings.py,sha256=nqQLcMwYxLseqq0BEcVvmrpIj2eQq0h8XDSpLN6GGCw,1793
@@ -663,7 +663,7 @@ reconcile/utils/quay_mirror.py,sha256=dpWCNv5lITwIk6Q9RkmqaQKHNk_JPy27UQEribJ7E-
663
663
  reconcile/utils/raw_github_api.py,sha256=2WKtE8ABYYB9UGOAh9N_kLkksBWL3320Z2_scteZddI,2805
664
664
  reconcile/utils/repo_owners.py,sha256=P0QX6F0oB8wYA08yiyzhYUiBtU57iIK_PsxbzKENbKM,6571
665
665
  reconcile/utils/rest_api_base.py,sha256=MT7tp6CQO2S5aKfVOzw_hipWg7wAGoOqkm4qurI1hEU,4342
666
- reconcile/utils/rhcsv2_certs.py,sha256=nS0NJGI7pzEvVd0F84_KQ9hsxmslz-Db7Y2Pt-1E0x8,2248
666
+ reconcile/utils/rhcsv2_certs.py,sha256=ZnlUlEI2k6UrljGarkm1ey0znMlQtjeZB7VEfCH1A64,2545
667
667
  reconcile/utils/ruamel.py,sha256=FzL4_L0FnMOUZmgThrZSMJs5MTdXwiy-E9MZWfk8bh8,397
668
668
  reconcile/utils/secret_reader.py,sha256=MaP56KZaAE35EyYbgAitdm6fUSxdzWeGFSOym9qiZkw,10206
669
669
  reconcile/utils/semver_helper.py,sha256=-WfPOMSA2v1h7hT3PwVf-Htg7wOsoKlQC1JdmDX2Ars,1268
@@ -815,7 +815,7 @@ tools/saas_promotion_state/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJ
815
815
  tools/saas_promotion_state/saas_promotion_state.py,sha256=UfwwRLS5Ya4_Nh1w5n1dvoYtchQvYE9yj1VANt2IKqI,3925
816
816
  tools/sre_checkpoints/__init__.py,sha256=CDaDaywJnmRCLyl_NCcvxi-Zc0hTi_3OdwKiFOyS39I,145
817
817
  tools/sre_checkpoints/util.py,sha256=zEDbGr18ZeHNQwW8pUsr2JRjuXIPz--WAGJxZo9sv_Y,894
818
- qontract_reconcile-0.10.2.dev204.dist-info/METADATA,sha256=oPtlkzclWzhdxuM8i4TJB0ykd-vcRwVOk2orPS4I_Zw,24555
819
- qontract_reconcile-0.10.2.dev204.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
820
- qontract_reconcile-0.10.2.dev204.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
821
- qontract_reconcile-0.10.2.dev204.dist-info/RECORD,,
818
+ qontract_reconcile-0.10.2.dev205.dist-info/METADATA,sha256=Nk9aLZ_YnzuvFX0u_RJVWxJUztk3TT6ko7ANo6xbEgo,24555
819
+ qontract_reconcile-0.10.2.dev205.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
820
+ qontract_reconcile-0.10.2.dev205.dist-info/entry_points.txt,sha256=5i9l54La3vQrDLAdwDKQWC0iG4sV9RRfOb1BpvzOWLc,698
821
+ qontract_reconcile-0.10.2.dev205.dist-info/RECORD,,
@@ -22,8 +22,9 @@ DEFINITION = """
22
22
  query RhcsProviderSettings {
23
23
  settings: app_interface_settings_v1 {
24
24
  rhcsProvider {
25
- url
25
+ issuerUrl
26
26
  vaultBasePath
27
+ caCertUrl
27
28
  }
28
29
  }
29
30
  }
@@ -37,8 +38,9 @@ class ConfiguredBaseModel(BaseModel):
37
38
 
38
39
 
39
40
  class RhcsProviderSettingsV1(ConfiguredBaseModel):
40
- url: str = Field(..., alias="url")
41
+ issuer_url: str = Field(..., alias="issuerUrl")
41
42
  vault_base_path: str = Field(..., alias="vaultBasePath")
43
+ ca_cert_url: str = Field(..., alias="caCertUrl")
42
44
 
43
45
 
44
46
  class AppInterfaceSettingsV1(ConfiguredBaseModel):
@@ -26581,7 +26581,7 @@
26581
26581
  "description": null,
26582
26582
  "fields": [
26583
26583
  {
26584
- "name": "url",
26584
+ "name": "issuerUrl",
26585
26585
  "description": null,
26586
26586
  "args": [],
26587
26587
  "type": {
@@ -26611,6 +26611,22 @@
26611
26611
  },
26612
26612
  "isDeprecated": false,
26613
26613
  "deprecationReason": null
26614
+ },
26615
+ {
26616
+ "name": "caCertUrl",
26617
+ "description": null,
26618
+ "args": [],
26619
+ "type": {
26620
+ "kind": "NON_NULL",
26621
+ "name": null,
26622
+ "ofType": {
26623
+ "kind": "SCALAR",
26624
+ "name": "String",
26625
+ "ofType": null
26626
+ }
26627
+ },
26628
+ "isDeprecated": false,
26629
+ "deprecationReason": null
26614
26630
  }
26615
26631
  ],
26616
26632
  "inputFields": null,
@@ -6,6 +6,9 @@ from typing import Any
6
6
 
7
7
  import reconcile.openshift_base as ob
8
8
  import reconcile.openshift_resources_base as orb
9
+ from reconcile.gql_definitions.common.rhcs_provider_settings import (
10
+ RhcsProviderSettingsV1,
11
+ )
9
12
  from reconcile.gql_definitions.rhcs.certs import (
10
13
  NamespaceOpenshiftResourceRhcsCertV1,
11
14
  NamespaceV1,
@@ -13,6 +16,7 @@ from reconcile.gql_definitions.rhcs.certs import (
13
16
  from reconcile.gql_definitions.rhcs.certs import (
14
17
  query as rhcs_certs_query,
15
18
  )
19
+ from reconcile.status import ExitCodes
16
20
  from reconcile.typed_queries.app_interface_vault_settings import (
17
21
  get_app_interface_vault_settings,
18
22
  )
@@ -82,7 +86,7 @@ def construct_rhcs_cert_oc_secret(
82
86
  body: dict[str, Any] = {
83
87
  "apiVersion": "v1",
84
88
  "kind": "Secret",
85
- "type": "Opaque",
89
+ "type": "kubernetes.io/tls",
86
90
  "metadata": {"name": secret_name, "annotations": annotations},
87
91
  }
88
92
  for k, v in cert.items():
@@ -120,7 +124,7 @@ def get_vault_cert_secret(
120
124
  })
121
125
  except SecretNotFound:
122
126
  logging.info(
123
- f"No existing cert found for cluster='{ns.cluster.name}', namespace='{ns.name}', secret='{cert_resource.secret_name}', threshold='{cert_resource.auto_renew_threshold_days} days'"
127
+ f"No existing cert found for cluster='{ns.cluster.name}', namespace='{ns.name}', secret='{cert_resource.secret_name}''"
124
128
  )
125
129
  return vault_cert_secret
126
130
 
@@ -131,7 +135,8 @@ def generate_vault_cert_secret(
131
135
  cert_resource: NamespaceOpenshiftResourceRhcsCertV1,
132
136
  vault: _VaultClient,
133
137
  vault_base_path: str,
134
- cert_provider_url: str,
138
+ issuer_url: str,
139
+ ca_cert_url: str,
135
140
  ) -> dict:
136
141
  logging.info(
137
142
  f"Creating cert with service account credentials for '{cert_resource.service_account_name}'. cluster='{ns.cluster.name}', namespace='{ns.name}', secret='{cert_resource.secret_name}'"
@@ -141,14 +146,13 @@ def generate_vault_cert_secret(
141
146
  rhcs_cert = RhcsV2Cert(
142
147
  certificate="PLACEHOLDER_CERT",
143
148
  private_key="PLACEHOLDER_PRIVATE_KEY",
149
+ ca_cert="PLACEHOLDER_CA_CERT",
144
150
  expiration_timestamp=int(time.time()),
145
151
  )
146
152
  else:
147
153
  try:
148
154
  rhcs_cert = generate_cert(
149
- cert_provider_url,
150
- cert_resource.service_account_name,
151
- sa_password,
155
+ issuer_url, cert_resource.service_account_name, sa_password, ca_cert_url
152
156
  )
153
157
  except ValueError as e:
154
158
  raise Exception(
@@ -159,12 +163,12 @@ def generate_vault_cert_secret(
159
163
  )
160
164
  vault.write(
161
165
  secret={
162
- "data": rhcs_cert.dict(),
166
+ "data": rhcs_cert.dict(by_alias=True),
163
167
  "path": f"{vault_base_path}/{ns.cluster.name}/{ns.name}/{cert_resource.secret_name}",
164
168
  },
165
169
  decode_base64=False,
166
170
  )
167
- return rhcs_cert.dict()
171
+ return rhcs_cert.dict(by_alias=True)
168
172
 
169
173
 
170
174
  def fetch_openshift_resource_for_cert_resource(
@@ -172,15 +176,21 @@ def fetch_openshift_resource_for_cert_resource(
172
176
  ns: NamespaceV1,
173
177
  cert_resource: NamespaceOpenshiftResourceRhcsCertV1,
174
178
  vault: _VaultClient,
175
- vault_base_path: str,
176
- cert_provider_url: str,
179
+ rhcs_settings: RhcsProviderSettingsV1,
177
180
  ) -> OR:
181
+ vault_base_path = f"{rhcs_settings.vault_base_path}/{QONTRACT_INTEGRATION}"
178
182
  vault_cert_secret = get_vault_cert_secret(ns, cert_resource, vault, vault_base_path)
179
183
  if vault_cert_secret is None or cert_expires_within_threshold(
180
184
  ns, cert_resource, vault_cert_secret
181
185
  ):
182
186
  vault_cert_secret = generate_vault_cert_secret(
183
- dry_run, ns, cert_resource, vault, vault_base_path, cert_provider_url
187
+ dry_run,
188
+ ns,
189
+ cert_resource,
190
+ vault,
191
+ vault_base_path,
192
+ rhcs_settings.issuer_url,
193
+ rhcs_settings.ca_cert_url,
184
194
  )
185
195
 
186
196
  if not dry_run:
@@ -220,8 +230,7 @@ def fetch_desired_state(
220
230
  ns,
221
231
  cert_resource,
222
232
  vault,
223
- f"{cert_provider.vault_base_path}/{QONTRACT_INTEGRATION}",
224
- cert_provider.url,
233
+ cert_provider,
225
234
  ),
226
235
  )
227
236
 
@@ -237,6 +246,11 @@ def run(
237
246
  vault_settings = get_app_interface_vault_settings()
238
247
  secret_reader = create_secret_reader(use_vault=vault_settings.vault)
239
248
  namespaces = get_namespaces_with_rhcs_certs(gql_api.query, cluster_name)
249
+ if not namespaces:
250
+ logging.debug(
251
+ f"No rhcs-cert definitions found in app-interface for {cluster_name}"
252
+ )
253
+ sys.exit(ExitCodes.SUCCESS)
240
254
  oc_map = init_oc_map_from_namespaces(
241
255
  namespaces=namespaces,
242
256
  integration=QONTRACT_INTEGRATION,
@@ -6,16 +6,20 @@ from cryptography import x509
6
6
  from cryptography.hazmat.primitives import hashes, serialization
7
7
  from cryptography.hazmat.primitives.asymmetric import rsa
8
8
  from cryptography.x509.oid import NameOID
9
- from pydantic import BaseModel
9
+ from pydantic import BaseModel, Field
10
10
 
11
11
 
12
12
  class RhcsV2Cert(BaseModel):
13
- certificate: str
14
- private_key: str
13
+ certificate: str = Field(alias="tls.crt")
14
+ private_key: str = Field(alias="tls.key")
15
+ ca_cert: str = Field(alias="ca.crt")
15
16
  expiration_timestamp: int
16
17
 
18
+ class Config:
19
+ allow_population_by_field_name = True
17
20
 
18
- def generate_cert(url: str, uid: str, pwd: str) -> RhcsV2Cert:
21
+
22
+ def generate_cert(issuer_url: str, uid: str, pwd: str, ca_url: str) -> RhcsV2Cert:
19
23
  private_key = rsa.generate_private_key(65537, 4096)
20
24
  csr = (
21
25
  x509.CertificateSigningRequestBuilder()
@@ -35,8 +39,9 @@ def generate_cert(url: str, uid: str, pwd: str) -> RhcsV2Cert:
35
39
  "renewal": "false",
36
40
  "xmlOutput": "false",
37
41
  }
38
- response = requests.post(url, data=data, verify=False)
42
+ response = requests.post(issuer_url, data=data)
39
43
  response.raise_for_status()
44
+
40
45
  cert_pem = re.search(
41
46
  r'outputList\.outputVal="(-----BEGIN CERTIFICATE-----.*?-----END CERTIFICATE-----(?:\\n|\r?\n)?)";',
42
47
  response.text,
@@ -56,11 +61,16 @@ def generate_cert(url: str, uid: str, pwd: str) -> RhcsV2Cert:
56
61
  encryption_algorithm=serialization.NoEncryption(),
57
62
  ).decode()
58
63
 
64
+ response = requests.get(ca_url)
65
+ response.raise_for_status()
66
+ ca_pem = response.text
67
+
59
68
  return RhcsV2Cert(
60
69
  private_key=private_key_pem,
61
70
  certificate=cert_pem.group(1)
62
71
  .encode()
63
72
  .decode("unicode_escape")
64
73
  .replace("\\/", "/"),
74
+ ca_cert=ca_pem,
65
75
  expiration_timestamp=int(dt_expiry.timestamp()),
66
76
  )