blob: a8c69fe65ca6d2b60ac60327ea3ca009626ef9b9 [file] [log] [blame]
David Garcia009a5d62020-08-27 16:53:44 +02001#!/usr/bin/env python3
David Garcia49379ce2021-02-24 13:48:22 +01002# Copyright 2021 Canonical Ltd.
David Garcia009a5d62020-08-27 16:53:44 +02003#
David Garcia49379ce2021-02-24 13:48:22 +01004# Licensed under the Apache License, Version 2.0 (the "License"); you may
5# not use this file except in compliance with the License. You may obtain
6# a copy of the License at
David Garcia009a5d62020-08-27 16:53:44 +02007#
David Garcia49379ce2021-02-24 13:48:22 +01008# http://www.apache.org/licenses/LICENSE-2.0
David Garcia009a5d62020-08-27 16:53:44 +02009#
David Garcia49379ce2021-02-24 13:48:22 +010010# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13# License for the specific language governing permissions and limitations
14# under the License.
15#
16# For those usages not covered by the Apache License, Version 2.0 please
17# contact: legal@canonical.com
18#
19# To get in touch with the maintainers, please contact:
20# osm-charmers@lists.launchpad.net
21##
22
23# pylint: disable=E0213
24
David Garcia009a5d62020-08-27 16:53:44 +020025
David Garciac753dc52021-03-17 15:28:47 +010026from datetime import datetime
27from ipaddress import ip_network
sousaedu738bf6f2020-10-10 00:25:26 +010028import json
David Garcia009a5d62020-08-27 16:53:44 +020029import logging
David Garciac753dc52021-03-17 15:28:47 +010030from typing import List, NoReturn, Optional, Tuple
David Garcia009a5d62020-08-27 16:53:44 +020031from urllib.parse import urlparse
32
David Garciac753dc52021-03-17 15:28:47 +010033from cryptography.fernet import Fernet
David Garcia009a5d62020-08-27 16:53:44 +020034from ops.main import main
David Garcia49379ce2021-02-24 13:48:22 +010035from opslib.osm.charm import CharmedOsmBase, RelationsMissing
David Garciac753dc52021-03-17 15:28:47 +010036from opslib.osm.interfaces.keystone import KeystoneServer
37from opslib.osm.interfaces.mysql import MysqlClient
David Garcia49379ce2021-02-24 13:48:22 +010038from opslib.osm.pod import (
39 ContainerV3Builder,
David Garcia49379ce2021-02-24 13:48:22 +010040 FilesV3Builder,
41 IngressResourceV3Builder,
David Garciac753dc52021-03-17 15:28:47 +010042 PodSpecV3Builder,
David Garcia009a5d62020-08-27 16:53:44 +020043)
David Garciac753dc52021-03-17 15:28:47 +010044from opslib.osm.validator import ModelValidator, validator
David Garcia49379ce2021-02-24 13:48:22 +010045
46
47logger = logging.getLogger(__name__)
48
David Garcia009a5d62020-08-27 16:53:44 +020049
David Garciaab11f842020-12-16 17:25:15 +010050REQUIRED_SETTINGS = ["token_expiration"]
David Garcia009a5d62020-08-27 16:53:44 +020051
sousaedu738bf6f2020-10-10 00:25:26 +010052# This is hardcoded in the keystone container script
53DATABASE_NAME = "keystone"
54
David Garcia009a5d62020-08-27 16:53:44 +020055# We expect the keystone container to use the default port
David Garcia49379ce2021-02-24 13:48:22 +010056PORT = 5000
David Garcia009a5d62020-08-27 16:53:44 +020057
sousaedu738bf6f2020-10-10 00:25:26 +010058# Number of keys need might need to be adjusted in the future
59NUMBER_FERNET_KEYS = 2
60NUMBER_CREDENTIAL_KEYS = 2
61
62# Path for keys
63CREDENTIAL_KEYS_PATH = "/etc/keystone/credential-keys"
64FERNET_KEYS_PATH = "/etc/keystone/fernet-keys"
65
David Garcia009a5d62020-08-27 16:53:44 +020066
David Garcia49379ce2021-02-24 13:48:22 +010067class ConfigModel(ModelValidator):
68 region_id: str
69 keystone_db_password: str
70 admin_username: str
71 admin_password: str
72 admin_project: str
73 service_username: str
74 service_password: str
75 service_project: str
76 user_domain_name: str
77 project_domain_name: str
78 token_expiration: int
79 max_file_size: int
80 site_url: Optional[str]
81 ingress_whitelist_source_range: Optional[str]
82 tls_secret_name: Optional[str]
David Garciaab11f842020-12-16 17:25:15 +010083
David Garcia49379ce2021-02-24 13:48:22 +010084 @validator("max_file_size")
85 def validate_max_file_size(cls, v):
86 if v < 0:
87 raise ValueError("value must be equal or greater than 0")
88 return v
89
90 @validator("site_url")
91 def validate_site_url(cls, v):
92 if v:
93 parsed = urlparse(v)
94 if not parsed.scheme.startswith("http"):
95 raise ValueError("value must start with http")
96 return v
97
98 @validator("ingress_whitelist_source_range")
99 def validate_ingress_whitelist_source_range(cls, v):
100 if v:
101 ip_network(v)
102 return v
David Garciaab11f842020-12-16 17:25:15 +0100103
104
David Garcia49379ce2021-02-24 13:48:22 +0100105class ConfigLdapModel(ModelValidator):
106 ldap_enabled: bool
107 ldap_authentication_domain_name: Optional[str]
108 ldap_url: Optional[str]
109 ldap_bind_user: Optional[str]
110 ldap_bind_password: Optional[str]
111 ldap_chase_referrals: Optional[str]
112 ldap_page_size: Optional[int]
113 ldap_user_tree_dn: Optional[str]
114 ldap_user_objectclass: Optional[str]
115 ldap_user_id_attribute: Optional[str]
116 ldap_user_name_attribute: Optional[str]
117 ldap_user_pass_attribute: Optional[str]
118 ldap_user_filter: Optional[str]
119 ldap_user_enabled_attribute: Optional[str]
120 ldap_user_enabled_mask: Optional[int]
121 ldap_user_enabled_default: Optional[bool]
122 ldap_user_enabled_invert: Optional[bool]
123 ldap_group_objectclass: Optional[str]
124 ldap_group_tree_dn: Optional[str]
125 ldap_use_starttls: Optional[bool]
126 ldap_tls_cacert_base64: Optional[str]
127 ldap_tls_req_cert: Optional[str]
David Garciaab11f842020-12-16 17:25:15 +0100128
David Garcia95ba7e12021-02-03 11:10:28 +0100129
David Garcia49379ce2021-02-24 13:48:22 +0100130class KeystoneCharm(CharmedOsmBase):
sousaedu738bf6f2020-10-10 00:25:26 +0100131 def __init__(self, *args) -> NoReturn:
David Garcia49379ce2021-02-24 13:48:22 +0100132 super().__init__(*args, oci_image="image")
sousaedu738bf6f2020-10-10 00:25:26 +0100133 self.state.set_default(fernet_keys=None)
134 self.state.set_default(credential_keys=None)
135 self.state.set_default(keys_timestamp=0)
David Garcia009a5d62020-08-27 16:53:44 +0200136
David Garcia49379ce2021-02-24 13:48:22 +0100137 self.keystone_server = KeystoneServer(self, "keystone")
138 self.mysql_client = MysqlClient(self, "db")
139 self.framework.observe(self.on["db"].relation_changed, self.configure_pod)
140 self.framework.observe(self.on["db"].relation_broken, self.configure_pod)
David Garcia009a5d62020-08-27 16:53:44 +0200141
David Garcia009a5d62020-08-27 16:53:44 +0200142 self.framework.observe(
David Garcia49379ce2021-02-24 13:48:22 +0100143 self.on["keystone"].relation_joined, self._publish_keystone_info
David Garcia009a5d62020-08-27 16:53:44 +0200144 )
145
David Garcia49379ce2021-02-24 13:48:22 +0100146 def _publish_keystone_info(self, event):
147 if self.unit.is_leader():
148 config = ConfigModel(**dict(self.config))
149 self.keystone_server.publish_info(
150 host=f"http://{self.app.name}:{PORT}/v3",
151 port=PORT,
152 user_domain_name=config.user_domain_name,
153 project_domain_name=config.project_domain_name,
154 username=config.service_username,
155 password=config.service_password,
156 service=config.service_project,
157 keystone_db_password=config.keystone_db_password,
158 region_id=config.region_id,
159 admin_username=config.admin_username,
160 admin_password=config.admin_password,
161 admin_project_name=config.admin_project,
David Garciaab11f842020-12-16 17:25:15 +0100162 )
David Garciaab11f842020-12-16 17:25:15 +0100163
David Garcia49379ce2021-02-24 13:48:22 +0100164 def _check_missing_dependencies(self, config: ConfigModel):
165 missing_relations = []
166 if self.mysql_client.is_missing_data_in_unit():
167 missing_relations.append("mysql")
168 if missing_relations:
169 raise RelationsMissing(missing_relations)
David Garcia009a5d62020-08-27 16:53:44 +0200170
sousaedu738bf6f2020-10-10 00:25:26 +0100171 def _generate_keys(self) -> Tuple[List[str], List[str]]:
172 """Generating new fernet tokens.
David Garcia009a5d62020-08-27 16:53:44 +0200173
sousaedu738bf6f2020-10-10 00:25:26 +0100174 Returns:
175 Tuple[List[str], List[str]]: contains two lists of strings. First
176 list contains strings that represent
177 the keys for fernet and the second
178 list contains strins that represent
179 the keys for credentials.
180 """
181 fernet_keys = [
182 Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS)
183 ]
184 credential_keys = [
185 Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS)
186 ]
187
188 return (fernet_keys, credential_keys)
189
David Garcia49379ce2021-02-24 13:48:22 +0100190 def _get_keys(self):
191 keys_timestamp = self.state.keys_timestamp
sousaedu738bf6f2020-10-10 00:25:26 +0100192 if fernet_keys := self.state.fernet_keys:
193 fernet_keys = json.loads(fernet_keys)
194
195 if credential_keys := self.state.credential_keys:
196 credential_keys = json.loads(credential_keys)
197
198 now = datetime.now().timestamp()
David Garcia49379ce2021-02-24 13:48:22 +0100199 token_expiration = self.config["token_expiration"]
sousaedu738bf6f2020-10-10 00:25:26 +0100200
201 valid_keys = (now - keys_timestamp) < token_expiration
202 if not credential_keys or not fernet_keys or not valid_keys:
203 fernet_keys, credential_keys = self._generate_keys()
204 self.state.fernet_keys = json.dumps(fernet_keys)
205 self.state.credential_keys = json.dumps(credential_keys)
206 self.state.keys_timestamp = now
David Garcia49379ce2021-02-24 13:48:22 +0100207 return credential_keys, fernet_keys
sousaedu738bf6f2020-10-10 00:25:26 +0100208
David Garcia49379ce2021-02-24 13:48:22 +0100209 def _build_files(self, config: ConfigModel):
210 credentials_files_builder = FilesV3Builder()
211 fernet_files_builder = FilesV3Builder()
David Garcia009a5d62020-08-27 16:53:44 +0200212
David Garcia49379ce2021-02-24 13:48:22 +0100213 credential_keys, fernet_keys = self._get_keys()
David Garcia009a5d62020-08-27 16:53:44 +0200214
David Garcia49379ce2021-02-24 13:48:22 +0100215 for (key_id, value) in enumerate(credential_keys):
216 credentials_files_builder.add_file(str(key_id), value)
217 for (key_id, value) in enumerate(fernet_keys):
218 fernet_files_builder.add_file(str(key_id), value)
219 return credentials_files_builder.build(), fernet_files_builder.build()
220
221 def build_pod_spec(self, image_info):
222 # Validate config
223 config = ConfigModel(**dict(self.config))
224 config_ldap = ConfigLdapModel(**dict(self.config))
225 # Check relations
226 self._check_missing_dependencies(config)
227 # Create Builder for the PodSpec
228 pod_spec_builder = PodSpecV3Builder()
229 # Build Container
230 container_builder = ContainerV3Builder(self.app.name, image_info)
231 container_builder.add_port(name=self.app.name, port=PORT)
232 # Build files
233 credential_files, fernet_files = self._build_files(config)
234 container_builder.add_volume_config(
235 "credential-keys", CREDENTIAL_KEYS_PATH, credential_files
236 )
237 container_builder.add_volume_config(
238 "fernet-keys", FERNET_KEYS_PATH, fernet_files
239 )
240 container_builder.add_envs(
241 {
242 "DB_HOST": self.mysql_client.host,
243 "DB_PORT": self.mysql_client.port,
244 "ROOT_DB_USER": "root",
245 "ROOT_DB_PASSWORD": self.mysql_client.root_password,
246 "KEYSTONE_DB_PASSWORD": config.keystone_db_password,
247 "REGION_ID": config.region_id,
248 "KEYSTONE_HOST": self.app.name,
249 "ADMIN_USERNAME": config.admin_username,
250 "ADMIN_PASSWORD": config.admin_password,
251 "ADMIN_PROJECT": config.admin_project,
252 "SERVICE_USERNAME": config.service_username,
253 "SERVICE_PASSWORD": config.service_password,
254 "SERVICE_PROJECT": config.service_project,
255 }
256 )
257
258 if config_ldap.ldap_enabled:
259
260 container_builder.add_envs(
David Garcia009a5d62020-08-27 16:53:44 +0200261 {
David Garcia49379ce2021-02-24 13:48:22 +0100262 "LDAP_AUTHENTICATION_DOMAIN_NAME": config_ldap.ldap_authentication_domain_name,
263 "LDAP_URL": config_ldap.ldap_url,
264 "LDAP_PAGE_SIZE": config_ldap.ldap_page_size,
265 "LDAP_USER_OBJECTCLASS": config_ldap.ldap_user_objectclass,
266 "LDAP_USER_ID_ATTRIBUTE": config_ldap.ldap_user_id_attribute,
267 "LDAP_USER_NAME_ATTRIBUTE": config_ldap.ldap_user_name_attribute,
268 "LDAP_USER_PASS_ATTRIBUTE": config_ldap.ldap_user_pass_attribute,
269 "LDAP_USER_ENABLED_MASK": config_ldap.ldap_user_enabled_mask,
270 "LDAP_USER_ENABLED_DEFAULT": config_ldap.ldap_user_enabled_default,
271 "LDAP_USER_ENABLED_INVERT": config_ldap.ldap_user_enabled_invert,
272 "LDAP_GROUP_OBJECTCLASS": config_ldap.ldap_group_objectclass,
David Garcia009a5d62020-08-27 16:53:44 +0200273 }
David Garcia49379ce2021-02-24 13:48:22 +0100274 )
275 if config_ldap.ldap_bind_user:
276 container_builder.add_envs(
277 {"LDAP_BIND_USER": config_ldap.ldap_bind_user}
278 )
sousaedu738bf6f2020-10-10 00:25:26 +0100279
David Garcia49379ce2021-02-24 13:48:22 +0100280 if config_ldap.ldap_bind_password:
281 container_builder.add_envs(
282 {"LDAP_BIND_PASSWORD": config_ldap.ldap_bind_password}
283 )
sousaedu738bf6f2020-10-10 00:25:26 +0100284
David Garcia49379ce2021-02-24 13:48:22 +0100285 if config_ldap.ldap_user_tree_dn:
286 container_builder.add_envs(
287 {"LDAP_USER_TREE_DN": config_ldap.ldap_user_tree_dn}
288 )
289
290 if config_ldap.ldap_user_filter:
291 container_builder.add_envs(
292 {"LDAP_USER_FILTER": config_ldap.ldap_user_filter}
293 )
294
295 if config_ldap.ldap_user_enabled_attribute:
296 container_builder.add_envs(
297 {
298 "LDAP_USER_ENABLED_ATTRIBUTE": config_ldap.ldap_user_enabled_attribute
299 }
300 )
301
302 if config_ldap.ldap_chase_referrals:
303 container_builder.add_envs(
304 {"LDAP_CHASE_REFERRALS": config_ldap.ldap_chase_referrals}
305 )
306
307 if config_ldap.ldap_group_tree_dn:
308 container_builder.add_envs(
309 {"LDAP_GROUP_TREE_DN": config_ldap.ldap_group_tree_dn}
310 )
311
312 if config_ldap.ldap_use_starttls:
313 container_builder.add_envs(
314 {
315 "LDAP_USE_STARTTLS": config_ldap.ldap_use_starttls,
316 "LDAP_TLS_CACERT_BASE64": config_ldap.ldap_tls_cacert_base64,
317 "LDAP_TLS_REQ_CERT": config_ldap.ldap_tls_req_cert,
318 }
319 )
320 container = container_builder.build()
321 # Add container to pod spec
322 pod_spec_builder.add_container(container)
323 # Add ingress resources to pod spec if site url exists
324 if config.site_url:
325 parsed = urlparse(config.site_url)
326 annotations = {
327 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
328 str(config.max_file_size) + "m"
329 if config.max_file_size > 0
330 else config.max_file_size
331 ),
332 }
333 ingress_resource_builder = IngressResourceV3Builder(
334 f"{self.app.name}-ingress", annotations
335 )
336
337 if config.ingress_whitelist_source_range:
338 annotations[
339 "nginx.ingress.kubernetes.io/whitelist-source-range"
340 ] = config.ingress_whitelist_source_range
341
342 if parsed.scheme == "https":
343 ingress_resource_builder.add_tls(
344 [parsed.hostname], config.tls_secret_name
345 )
346 else:
347 annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
348
349 ingress_resource_builder.add_rule(parsed.hostname, self.app.name, PORT)
350 ingress_resource = ingress_resource_builder.build()
351 pod_spec_builder.add_ingress_resource(ingress_resource)
352 return pod_spec_builder.build()
David Garcia009a5d62020-08-27 16:53:44 +0200353
354
355if __name__ == "__main__":
356 main(KeystoneCharm)
David Garcia49379ce2021-02-24 13:48:22 +0100357
358# LOGGER = logging.getLogger(__name__)
359
360
361# class ConfigurePodEvent(EventBase):
362# """Configure Pod event"""
363
364# pass
365
366
367# class KeystoneEvents(CharmEvents):
368# """Keystone Events"""
369
370# configure_pod = EventSource(ConfigurePodEvent)
371
372# class KeystoneCharm(CharmBase):
373# """Keystone K8s Charm"""
374
375# state = StoredState()
376# on = KeystoneEvents()
377
378# def __init__(self, *args) -> NoReturn:
379# """Constructor of the Charm object.
380# Initializes internal state and register events it can handle.
381# """
382# super().__init__(*args)
383# self.state.set_default(db_host=None)
384# self.state.set_default(db_port=None)
385# self.state.set_default(db_user=None)
386# self.state.set_default(db_password=None)
387# self.state.set_default(pod_spec=None)
388# self.state.set_default(fernet_keys=None)
389# self.state.set_default(credential_keys=None)
390# self.state.set_default(keys_timestamp=0)
391
392# # Register all of the events we want to observe
393# self.framework.observe(self.on.config_changed, self.configure_pod)
394# self.framework.observe(self.on.start, self.configure_pod)
395# self.framework.observe(self.on.upgrade_charm, self.configure_pod)
396# self.framework.observe(self.on.leader_elected, self.configure_pod)
397# self.framework.observe(self.on.update_status, self.configure_pod)
398
399# # Registering custom internal events
400# self.framework.observe(self.on.configure_pod, self.configure_pod)
401
402# # Register relation events
403# self.framework.observe(
404# self.on.db_relation_changed, self._on_db_relation_changed
405# )
406# self.framework.observe(
407# self.on.db_relation_broken, self._on_db_relation_broken
408# )
409# self.framework.observe(
410# self.on.keystone_relation_joined, self._publish_keystone_info
411# )
412
413# def _publish_keystone_info(self, event: EventBase) -> NoReturn:
414# """Publishes keystone information for NBI usage through the keystone
415# relation.
416
417# Args:
418# event (EventBase): Keystone relation event to update NBI.
419# """
420# config = self.model.config
421# rel_data = {
422# "host": f"http://{self.app.name}:{KEYSTONE_PORT}/v3",
423# "port": str(KEYSTONE_PORT),
424# "keystone_db_password": config["keystone_db_password"],
425# "region_id": config["region_id"],
426# "user_domain_name": config["user_domain_name"],
427# "project_domain_name": config["project_domain_name"],
428# "admin_username": config["admin_username"],
429# "admin_password": config["admin_password"],
430# "admin_project_name": config["admin_project"],
431# "username": config["service_username"],
432# "password": config["service_password"],
433# "service": config["service_project"],
434# }
435# for k, v in rel_data.items():
436# event.relation.data[self.model.unit][k] = v
437
438# def _on_db_relation_changed(self, event: EventBase) -> NoReturn:
439# """Reads information about the DB relation, in order for keystone to
440# access it.
441
442# Args:
443# event (EventBase): DB relation event to access database
444# information.
445# """
446# if not event.unit in event.relation.data:
447# return
448# relation_data = event.relation.data[event.unit]
449# db_host = relation_data.get("host")
450# db_port = int(relation_data.get("port", 3306))
451# db_user = "root"
452# db_password = relation_data.get("root_password")
453
454# if (
455# db_host
456# and db_port
457# and db_user
458# and db_password
459# and (
460# self.state.db_host != db_host
461# or self.state.db_port != db_port
462# or self.state.db_user != db_user
463# or self.state.db_password != db_password
464# )
465# ):
466# self.state.db_host = db_host
467# self.state.db_port = db_port
468# self.state.db_user = db_user
469# self.state.db_password = db_password
470# self.on.configure_pod.emit()
471
472
473# def _on_db_relation_broken(self, event: EventBase) -> NoReturn:
474# """Clears data from db relation.
475
476# Args:
477# event (EventBase): DB relation event.
478
479# """
480# self.state.db_host = None
481# self.state.db_port = None
482# self.state.db_user = None
483# self.state.db_password = None
484# self.on.configure_pod.emit()
485
486# def _check_settings(self) -> str:
487# """Check if there any settings missing from Keystone configuration.
488
489# Returns:
490# str: Information about the problems found (if any).
491# """
492# problems = []
493# config = self.model.config
494
495# for setting in REQUIRED_SETTINGS:
496# if not config.get(setting):
497# problem = f"missing config {setting}"
498# problems.append(problem)
499
500# return ";".join(problems)
501
502# def _make_pod_image_details(self) -> Dict[str, str]:
503# """Generate the pod image details.
504
505# Returns:
506# Dict[str, str]: pod image details.
507# """
508# config = self.model.config
509# image_details = {
510# "imagePath": config["image"],
511# }
512# if config["image_username"]:
513# image_details.update(
514# {
515# "username": config["image_username"],
516# "password": config["image_password"],
517# }
518# )
519# return image_details
520
521# def _make_pod_ports(self) -> List[Dict[str, Any]]:
522# """Generate the pod ports details.
523
524# Returns:
525# List[Dict[str, Any]]: pod ports details.
526# """
527# return [
528# {"name": "keystone", "containerPort": KEYSTONE_PORT, "protocol": "TCP"},
529# ]
530
531# def _make_pod_envconfig(self) -> Dict[str, Any]:
532# """Generate pod environment configuraiton.
533
534# Returns:
535# Dict[str, Any]: pod environment configuration.
536# """
537# config = self.model.config
538
539# envconfig = {
540# "DB_HOST": self.state.db_host,
541# "DB_PORT": self.state.db_port,
542# "ROOT_DB_USER": self.state.db_user,
543# "ROOT_DB_PASSWORD": self.state.db_password,
544# "KEYSTONE_DB_PASSWORD": config["keystone_db_password"],
545# "REGION_ID": config["region_id"],
546# "KEYSTONE_HOST": self.app.name,
547# "ADMIN_USERNAME": config["admin_username"],
548# "ADMIN_PASSWORD": config["admin_password"],
549# "ADMIN_PROJECT": config["admin_project"],
550# "SERVICE_USERNAME": config["service_username"],
551# "SERVICE_PASSWORD": config["service_password"],
552# "SERVICE_PROJECT": config["service_project"],
553# }
554
555# if config.get("ldap_enabled"):
556# envconfig["LDAP_AUTHENTICATION_DOMAIN_NAME"] = config[
557# "ldap_authentication_domain_name"
558# ]
559# envconfig["LDAP_URL"] = config["ldap_url"]
560# envconfig["LDAP_PAGE_SIZE"] = config["ldap_page_size"]
561# envconfig["LDAP_USER_OBJECTCLASS"] = config["ldap_user_objectclass"]
562# envconfig["LDAP_USER_ID_ATTRIBUTE"] = config["ldap_user_id_attribute"]
563# envconfig["LDAP_USER_NAME_ATTRIBUTE"] = config["ldap_user_name_attribute"]
564# envconfig["LDAP_USER_PASS_ATTRIBUTE"] = config["ldap_user_pass_attribute"]
565# envconfig["LDAP_USER_ENABLED_MASK"] = config["ldap_user_enabled_mask"]
566# envconfig["LDAP_USER_ENABLED_DEFAULT"] = config["ldap_user_enabled_default"]
567# envconfig["LDAP_USER_ENABLED_INVERT"] = config["ldap_user_enabled_invert"]
568# envconfig["LDAP_GROUP_OBJECTCLASS"] = config["ldap_group_objectclass"]
569
570# if config["ldap_bind_user"]:
571# envconfig["LDAP_BIND_USER"] = config["ldap_bind_user"]
572
573# if config["ldap_bind_password"]:
574# envconfig["LDAP_BIND_PASSWORD"] = config["ldap_bind_password"]
575
576# if config["ldap_user_tree_dn"]:
577# envconfig["LDAP_USER_TREE_DN"] = config["ldap_user_tree_dn"]
578
579# if config["ldap_user_filter"]:
580# envconfig["LDAP_USER_FILTER"] = config["ldap_user_filter"]
581
582# if config["ldap_user_enabled_attribute"]:
583# envconfig["LDAP_USER_ENABLED_ATTRIBUTE"] = config[
584# "ldap_user_enabled_attribute"
585# ]
586
587# if config["ldap_chase_referrals"]:
588# envconfig["LDAP_CHASE_REFERRALS"] = config["ldap_chase_referrals"]
589
590# if config["ldap_group_tree_dn"]:
591# envconfig["LDAP_GROUP_TREE_DN"] = config["ldap_group_tree_dn"]
592
593# if config["ldap_use_starttls"]:
594# envconfig["LDAP_USE_STARTTLS"] = config["ldap_use_starttls"]
595# envconfig["LDAP_TLS_CACERT_BASE64"] = config["ldap_tls_cacert_base64"]
596# envconfig["LDAP_TLS_REQ_CERT"] = config["ldap_tls_req_cert"]
597
598# return envconfig
599
600# def _make_pod_ingress_resources(self) -> List[Dict[str, Any]]:
601# """Generate pod ingress resources.
602
603# Returns:
604# List[Dict[str, Any]]: pod ingress resources.
605# """
606# site_url = self.model.config["site_url"]
607
608# if not site_url:
609# return
610
611# parsed = urlparse(site_url)
612
613# if not parsed.scheme.startswith("http"):
614# return
615
616# max_file_size = self.model.config["max_file_size"]
617# ingress_whitelist_source_range = self.model.config[
618# "ingress_whitelist_source_range"
619# ]
620
621# annotations = {
622# "nginx.ingress.kubernetes.io/proxy-body-size": "{}m".format(max_file_size)
623# }
624
625# if ingress_whitelist_source_range:
626# annotations[
627# "nginx.ingress.kubernetes.io/whitelist-source-range"
628# ] = ingress_whitelist_source_range
629
630# ingress_spec_tls = None
631
632# if parsed.scheme == "https":
633# ingress_spec_tls = [{"hosts": [parsed.hostname]}]
634# tls_secret_name = self.model.config["tls_secret_name"]
635# if tls_secret_name:
636# ingress_spec_tls[0]["secretName"] = tls_secret_name
637# else:
638# annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
639
640# ingress = {
641# "name": "{}-ingress".format(self.app.name),
642# "annotations": annotations,
643# "spec": {
644# "rules": [
645# {
646# "host": parsed.hostname,
647# "http": {
648# "paths": [
649# {
650# "path": "/",
651# "backend": {
652# "serviceName": self.app.name,
653# "servicePort": KEYSTONE_PORT,
654# },
655# }
656# ]
657# },
658# }
659# ],
660# },
661# }
662# if ingress_spec_tls:
663# ingress["spec"]["tls"] = ingress_spec_tls
664
665# return [ingress]
666
667# def _generate_keys(self) -> Tuple[List[str], List[str]]:
668# """Generating new fernet tokens.
669
670# Returns:
671# Tuple[List[str], List[str]]: contains two lists of strings. First
672# list contains strings that represent
673# the keys for fernet and the second
674# list contains strins that represent
675# the keys for credentials.
676# """
677# fernet_keys = [
678# Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS)
679# ]
680# credential_keys = [
681# Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS)
682# ]
683
684# return (fernet_keys, credential_keys)
685
686# def configure_pod(self, event: EventBase) -> NoReturn:
687# """Assemble the pod spec and apply it, if possible.
688
689# Args:
690# event (EventBase): Hook or Relation event that started the
691# function.
692# """
693# if not self.state.db_host:
694# self.unit.status = WaitingStatus("Waiting for database relation")
695# event.defer()
696# return
697
698# if not self.unit.is_leader():
699# self.unit.status = ActiveStatus("ready")
700# return
701
702# if fernet_keys := self.state.fernet_keys:
703# fernet_keys = json.loads(fernet_keys)
704
705# if credential_keys := self.state.credential_keys:
706# credential_keys = json.loads(credential_keys)
707
708# now = datetime.now().timestamp()
709# keys_timestamp = self.state.keys_timestamp
710# token_expiration = self.model.config["token_expiration"]
711
712# valid_keys = (now - keys_timestamp) < token_expiration
713# if not credential_keys or not fernet_keys or not valid_keys:
714# fernet_keys, credential_keys = self._generate_keys()
715# self.state.fernet_keys = json.dumps(fernet_keys)
716# self.state.credential_keys = json.dumps(credential_keys)
717# self.state.keys_timestamp = now
718
719# # Check problems in the settings
720# problems = self._check_settings()
721# if problems:
722# self.unit.status = BlockedStatus(problems)
723# return
724
725# self.unit.status = BlockedStatus("Assembling pod spec")
726# image_details = self._make_pod_image_details()
727# ports = self._make_pod_ports()
728# env_config = self._make_pod_envconfig()
729# ingress_resources = self._make_pod_ingress_resources()
730# files = self._make_pod_files(fernet_keys, credential_keys)
731
732# pod_spec = {
733# "version": 3,
734# "containers": [
735# {
736# "name": self.framework.model.app.name,
737# "imageDetails": image_details,
738# "ports": ports,
739# "envConfig": env_config,
740# "volumeConfig": files,
741# }
742# ],
743# "kubernetesResources": {"ingressResources": ingress_resources or []},
744# }
745
746# if self.state.pod_spec != (
747# pod_spec_json := json.dumps(pod_spec, sort_keys=True)
748# ):
749# self.state.pod_spec = pod_spec_json
750# self.model.pod.set_spec(pod_spec)
751
752# self.unit.status = ActiveStatus("ready")
753
754
755# if __name__ == "__main__":
756# main(KeystoneCharm)