blob: 72d70906328c95d19240b5436cb29eeca070b527 [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
sousaedu738bf6f2020-10-10 00:25:26 +010026import json
David Garcia009a5d62020-08-27 16:53:44 +020027import logging
David Garcia49379ce2021-02-24 13:48:22 +010028from cryptography.fernet import Fernet
sousaedu738bf6f2020-10-10 00:25:26 +010029from datetime import datetime
David Garcia49379ce2021-02-24 13:48:22 +010030from typing import Optional, NoReturn, List, Tuple
31from ipaddress import ip_network
David Garcia009a5d62020-08-27 16:53:44 +020032from urllib.parse import urlparse
33
David Garcia009a5d62020-08-27 16:53:44 +020034from ops.main import main
David Garcia49379ce2021-02-24 13:48:22 +010035
36from opslib.osm.charm import CharmedOsmBase, RelationsMissing
37
38from opslib.osm.pod import (
39 ContainerV3Builder,
40 PodSpecV3Builder,
41 FilesV3Builder,
42 IngressResourceV3Builder,
David Garcia009a5d62020-08-27 16:53:44 +020043)
David Garcia009a5d62020-08-27 16:53:44 +020044
David Garcia49379ce2021-02-24 13:48:22 +010045
46from opslib.osm.validator import (
47 ModelValidator,
48 validator,
49)
50
51from opslib.osm.interfaces.mysql import MysqlClient
52from opslib.osm.interfaces.keystone import KeystoneServer
53
54
55logger = logging.getLogger(__name__)
56
David Garcia009a5d62020-08-27 16:53:44 +020057
David Garciaab11f842020-12-16 17:25:15 +010058REQUIRED_SETTINGS = ["token_expiration"]
David Garcia009a5d62020-08-27 16:53:44 +020059
sousaedu738bf6f2020-10-10 00:25:26 +010060# This is hardcoded in the keystone container script
61DATABASE_NAME = "keystone"
62
David Garcia009a5d62020-08-27 16:53:44 +020063# We expect the keystone container to use the default port
David Garcia49379ce2021-02-24 13:48:22 +010064PORT = 5000
David Garcia009a5d62020-08-27 16:53:44 +020065
sousaedu738bf6f2020-10-10 00:25:26 +010066# Number of keys need might need to be adjusted in the future
67NUMBER_FERNET_KEYS = 2
68NUMBER_CREDENTIAL_KEYS = 2
69
70# Path for keys
71CREDENTIAL_KEYS_PATH = "/etc/keystone/credential-keys"
72FERNET_KEYS_PATH = "/etc/keystone/fernet-keys"
73
David Garcia009a5d62020-08-27 16:53:44 +020074
David Garcia49379ce2021-02-24 13:48:22 +010075class ConfigModel(ModelValidator):
76 region_id: str
77 keystone_db_password: str
78 admin_username: str
79 admin_password: str
80 admin_project: str
81 service_username: str
82 service_password: str
83 service_project: str
84 user_domain_name: str
85 project_domain_name: str
86 token_expiration: int
87 max_file_size: int
88 site_url: Optional[str]
89 ingress_whitelist_source_range: Optional[str]
90 tls_secret_name: Optional[str]
David Garciaab11f842020-12-16 17:25:15 +010091
David Garcia49379ce2021-02-24 13:48:22 +010092 @validator("max_file_size")
93 def validate_max_file_size(cls, v):
94 if v < 0:
95 raise ValueError("value must be equal or greater than 0")
96 return v
97
98 @validator("site_url")
99 def validate_site_url(cls, v):
100 if v:
101 parsed = urlparse(v)
102 if not parsed.scheme.startswith("http"):
103 raise ValueError("value must start with http")
104 return v
105
106 @validator("ingress_whitelist_source_range")
107 def validate_ingress_whitelist_source_range(cls, v):
108 if v:
109 ip_network(v)
110 return v
David Garciaab11f842020-12-16 17:25:15 +0100111
112
David Garcia49379ce2021-02-24 13:48:22 +0100113class ConfigLdapModel(ModelValidator):
114 ldap_enabled: bool
115 ldap_authentication_domain_name: Optional[str]
116 ldap_url: Optional[str]
117 ldap_bind_user: Optional[str]
118 ldap_bind_password: Optional[str]
119 ldap_chase_referrals: Optional[str]
120 ldap_page_size: Optional[int]
121 ldap_user_tree_dn: Optional[str]
122 ldap_user_objectclass: Optional[str]
123 ldap_user_id_attribute: Optional[str]
124 ldap_user_name_attribute: Optional[str]
125 ldap_user_pass_attribute: Optional[str]
126 ldap_user_filter: Optional[str]
127 ldap_user_enabled_attribute: Optional[str]
128 ldap_user_enabled_mask: Optional[int]
129 ldap_user_enabled_default: Optional[bool]
130 ldap_user_enabled_invert: Optional[bool]
131 ldap_group_objectclass: Optional[str]
132 ldap_group_tree_dn: Optional[str]
133 ldap_use_starttls: Optional[bool]
134 ldap_tls_cacert_base64: Optional[str]
135 ldap_tls_req_cert: Optional[str]
David Garciaab11f842020-12-16 17:25:15 +0100136
David Garcia95ba7e12021-02-03 11:10:28 +0100137
David Garcia49379ce2021-02-24 13:48:22 +0100138class KeystoneCharm(CharmedOsmBase):
sousaedu738bf6f2020-10-10 00:25:26 +0100139 def __init__(self, *args) -> NoReturn:
David Garcia49379ce2021-02-24 13:48:22 +0100140 super().__init__(*args, oci_image="image")
sousaedu738bf6f2020-10-10 00:25:26 +0100141 self.state.set_default(fernet_keys=None)
142 self.state.set_default(credential_keys=None)
143 self.state.set_default(keys_timestamp=0)
David Garcia009a5d62020-08-27 16:53:44 +0200144
David Garcia49379ce2021-02-24 13:48:22 +0100145 self.keystone_server = KeystoneServer(self, "keystone")
146 self.mysql_client = MysqlClient(self, "db")
147 self.framework.observe(self.on["db"].relation_changed, self.configure_pod)
148 self.framework.observe(self.on["db"].relation_broken, self.configure_pod)
David Garcia009a5d62020-08-27 16:53:44 +0200149
David Garcia009a5d62020-08-27 16:53:44 +0200150 self.framework.observe(
David Garcia49379ce2021-02-24 13:48:22 +0100151 self.on["keystone"].relation_joined, self._publish_keystone_info
David Garcia009a5d62020-08-27 16:53:44 +0200152 )
153
David Garcia49379ce2021-02-24 13:48:22 +0100154 def _publish_keystone_info(self, event):
155 if self.unit.is_leader():
156 config = ConfigModel(**dict(self.config))
157 self.keystone_server.publish_info(
158 host=f"http://{self.app.name}:{PORT}/v3",
159 port=PORT,
160 user_domain_name=config.user_domain_name,
161 project_domain_name=config.project_domain_name,
162 username=config.service_username,
163 password=config.service_password,
164 service=config.service_project,
165 keystone_db_password=config.keystone_db_password,
166 region_id=config.region_id,
167 admin_username=config.admin_username,
168 admin_password=config.admin_password,
169 admin_project_name=config.admin_project,
David Garciaab11f842020-12-16 17:25:15 +0100170 )
David Garciaab11f842020-12-16 17:25:15 +0100171
David Garcia49379ce2021-02-24 13:48:22 +0100172 def _check_missing_dependencies(self, config: ConfigModel):
173 missing_relations = []
174 if self.mysql_client.is_missing_data_in_unit():
175 missing_relations.append("mysql")
176 if missing_relations:
177 raise RelationsMissing(missing_relations)
David Garcia009a5d62020-08-27 16:53:44 +0200178
sousaedu738bf6f2020-10-10 00:25:26 +0100179 def _generate_keys(self) -> Tuple[List[str], List[str]]:
180 """Generating new fernet tokens.
David Garcia009a5d62020-08-27 16:53:44 +0200181
sousaedu738bf6f2020-10-10 00:25:26 +0100182 Returns:
183 Tuple[List[str], List[str]]: contains two lists of strings. First
184 list contains strings that represent
185 the keys for fernet and the second
186 list contains strins that represent
187 the keys for credentials.
188 """
189 fernet_keys = [
190 Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS)
191 ]
192 credential_keys = [
193 Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS)
194 ]
195
196 return (fernet_keys, credential_keys)
197
David Garcia49379ce2021-02-24 13:48:22 +0100198 def _get_keys(self):
199 keys_timestamp = self.state.keys_timestamp
sousaedu738bf6f2020-10-10 00:25:26 +0100200 if fernet_keys := self.state.fernet_keys:
201 fernet_keys = json.loads(fernet_keys)
202
203 if credential_keys := self.state.credential_keys:
204 credential_keys = json.loads(credential_keys)
205
206 now = datetime.now().timestamp()
David Garcia49379ce2021-02-24 13:48:22 +0100207 token_expiration = self.config["token_expiration"]
sousaedu738bf6f2020-10-10 00:25:26 +0100208
209 valid_keys = (now - keys_timestamp) < token_expiration
210 if not credential_keys or not fernet_keys or not valid_keys:
211 fernet_keys, credential_keys = self._generate_keys()
212 self.state.fernet_keys = json.dumps(fernet_keys)
213 self.state.credential_keys = json.dumps(credential_keys)
214 self.state.keys_timestamp = now
David Garcia49379ce2021-02-24 13:48:22 +0100215 return credential_keys, fernet_keys
sousaedu738bf6f2020-10-10 00:25:26 +0100216
David Garcia49379ce2021-02-24 13:48:22 +0100217 def _build_files(self, config: ConfigModel):
218 credentials_files_builder = FilesV3Builder()
219 fernet_files_builder = FilesV3Builder()
David Garcia009a5d62020-08-27 16:53:44 +0200220
David Garcia49379ce2021-02-24 13:48:22 +0100221 credential_keys, fernet_keys = self._get_keys()
David Garcia009a5d62020-08-27 16:53:44 +0200222
David Garcia49379ce2021-02-24 13:48:22 +0100223 for (key_id, value) in enumerate(credential_keys):
224 credentials_files_builder.add_file(str(key_id), value)
225 for (key_id, value) in enumerate(fernet_keys):
226 fernet_files_builder.add_file(str(key_id), value)
227 return credentials_files_builder.build(), fernet_files_builder.build()
228
229 def build_pod_spec(self, image_info):
230 # Validate config
231 config = ConfigModel(**dict(self.config))
232 config_ldap = ConfigLdapModel(**dict(self.config))
233 # Check relations
234 self._check_missing_dependencies(config)
235 # Create Builder for the PodSpec
236 pod_spec_builder = PodSpecV3Builder()
237 # Build Container
238 container_builder = ContainerV3Builder(self.app.name, image_info)
239 container_builder.add_port(name=self.app.name, port=PORT)
240 # Build files
241 credential_files, fernet_files = self._build_files(config)
242 container_builder.add_volume_config(
243 "credential-keys", CREDENTIAL_KEYS_PATH, credential_files
244 )
245 container_builder.add_volume_config(
246 "fernet-keys", FERNET_KEYS_PATH, fernet_files
247 )
248 container_builder.add_envs(
249 {
250 "DB_HOST": self.mysql_client.host,
251 "DB_PORT": self.mysql_client.port,
252 "ROOT_DB_USER": "root",
253 "ROOT_DB_PASSWORD": self.mysql_client.root_password,
254 "KEYSTONE_DB_PASSWORD": config.keystone_db_password,
255 "REGION_ID": config.region_id,
256 "KEYSTONE_HOST": self.app.name,
257 "ADMIN_USERNAME": config.admin_username,
258 "ADMIN_PASSWORD": config.admin_password,
259 "ADMIN_PROJECT": config.admin_project,
260 "SERVICE_USERNAME": config.service_username,
261 "SERVICE_PASSWORD": config.service_password,
262 "SERVICE_PROJECT": config.service_project,
263 }
264 )
265
266 if config_ldap.ldap_enabled:
267
268 container_builder.add_envs(
David Garcia009a5d62020-08-27 16:53:44 +0200269 {
David Garcia49379ce2021-02-24 13:48:22 +0100270 "LDAP_AUTHENTICATION_DOMAIN_NAME": config_ldap.ldap_authentication_domain_name,
271 "LDAP_URL": config_ldap.ldap_url,
272 "LDAP_PAGE_SIZE": config_ldap.ldap_page_size,
273 "LDAP_USER_OBJECTCLASS": config_ldap.ldap_user_objectclass,
274 "LDAP_USER_ID_ATTRIBUTE": config_ldap.ldap_user_id_attribute,
275 "LDAP_USER_NAME_ATTRIBUTE": config_ldap.ldap_user_name_attribute,
276 "LDAP_USER_PASS_ATTRIBUTE": config_ldap.ldap_user_pass_attribute,
277 "LDAP_USER_ENABLED_MASK": config_ldap.ldap_user_enabled_mask,
278 "LDAP_USER_ENABLED_DEFAULT": config_ldap.ldap_user_enabled_default,
279 "LDAP_USER_ENABLED_INVERT": config_ldap.ldap_user_enabled_invert,
280 "LDAP_GROUP_OBJECTCLASS": config_ldap.ldap_group_objectclass,
David Garcia009a5d62020-08-27 16:53:44 +0200281 }
David Garcia49379ce2021-02-24 13:48:22 +0100282 )
283 if config_ldap.ldap_bind_user:
284 container_builder.add_envs(
285 {"LDAP_BIND_USER": config_ldap.ldap_bind_user}
286 )
sousaedu738bf6f2020-10-10 00:25:26 +0100287
David Garcia49379ce2021-02-24 13:48:22 +0100288 if config_ldap.ldap_bind_password:
289 container_builder.add_envs(
290 {"LDAP_BIND_PASSWORD": config_ldap.ldap_bind_password}
291 )
sousaedu738bf6f2020-10-10 00:25:26 +0100292
David Garcia49379ce2021-02-24 13:48:22 +0100293 if config_ldap.ldap_user_tree_dn:
294 container_builder.add_envs(
295 {"LDAP_USER_TREE_DN": config_ldap.ldap_user_tree_dn}
296 )
297
298 if config_ldap.ldap_user_filter:
299 container_builder.add_envs(
300 {"LDAP_USER_FILTER": config_ldap.ldap_user_filter}
301 )
302
303 if config_ldap.ldap_user_enabled_attribute:
304 container_builder.add_envs(
305 {
306 "LDAP_USER_ENABLED_ATTRIBUTE": config_ldap.ldap_user_enabled_attribute
307 }
308 )
309
310 if config_ldap.ldap_chase_referrals:
311 container_builder.add_envs(
312 {"LDAP_CHASE_REFERRALS": config_ldap.ldap_chase_referrals}
313 )
314
315 if config_ldap.ldap_group_tree_dn:
316 container_builder.add_envs(
317 {"LDAP_GROUP_TREE_DN": config_ldap.ldap_group_tree_dn}
318 )
319
320 if config_ldap.ldap_use_starttls:
321 container_builder.add_envs(
322 {
323 "LDAP_USE_STARTTLS": config_ldap.ldap_use_starttls,
324 "LDAP_TLS_CACERT_BASE64": config_ldap.ldap_tls_cacert_base64,
325 "LDAP_TLS_REQ_CERT": config_ldap.ldap_tls_req_cert,
326 }
327 )
328 container = container_builder.build()
329 # Add container to pod spec
330 pod_spec_builder.add_container(container)
331 # Add ingress resources to pod spec if site url exists
332 if config.site_url:
333 parsed = urlparse(config.site_url)
334 annotations = {
335 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
336 str(config.max_file_size) + "m"
337 if config.max_file_size > 0
338 else config.max_file_size
339 ),
340 }
341 ingress_resource_builder = IngressResourceV3Builder(
342 f"{self.app.name}-ingress", annotations
343 )
344
345 if config.ingress_whitelist_source_range:
346 annotations[
347 "nginx.ingress.kubernetes.io/whitelist-source-range"
348 ] = config.ingress_whitelist_source_range
349
350 if parsed.scheme == "https":
351 ingress_resource_builder.add_tls(
352 [parsed.hostname], config.tls_secret_name
353 )
354 else:
355 annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
356
357 ingress_resource_builder.add_rule(parsed.hostname, self.app.name, PORT)
358 ingress_resource = ingress_resource_builder.build()
359 pod_spec_builder.add_ingress_resource(ingress_resource)
360 return pod_spec_builder.build()
David Garcia009a5d62020-08-27 16:53:44 +0200361
362
363if __name__ == "__main__":
364 main(KeystoneCharm)
David Garcia49379ce2021-02-24 13:48:22 +0100365
366# LOGGER = logging.getLogger(__name__)
367
368
369# class ConfigurePodEvent(EventBase):
370# """Configure Pod event"""
371
372# pass
373
374
375# class KeystoneEvents(CharmEvents):
376# """Keystone Events"""
377
378# configure_pod = EventSource(ConfigurePodEvent)
379
380# class KeystoneCharm(CharmBase):
381# """Keystone K8s Charm"""
382
383# state = StoredState()
384# on = KeystoneEvents()
385
386# def __init__(self, *args) -> NoReturn:
387# """Constructor of the Charm object.
388# Initializes internal state and register events it can handle.
389# """
390# super().__init__(*args)
391# self.state.set_default(db_host=None)
392# self.state.set_default(db_port=None)
393# self.state.set_default(db_user=None)
394# self.state.set_default(db_password=None)
395# self.state.set_default(pod_spec=None)
396# self.state.set_default(fernet_keys=None)
397# self.state.set_default(credential_keys=None)
398# self.state.set_default(keys_timestamp=0)
399
400# # Register all of the events we want to observe
401# self.framework.observe(self.on.config_changed, self.configure_pod)
402# self.framework.observe(self.on.start, self.configure_pod)
403# self.framework.observe(self.on.upgrade_charm, self.configure_pod)
404# self.framework.observe(self.on.leader_elected, self.configure_pod)
405# self.framework.observe(self.on.update_status, self.configure_pod)
406
407# # Registering custom internal events
408# self.framework.observe(self.on.configure_pod, self.configure_pod)
409
410# # Register relation events
411# self.framework.observe(
412# self.on.db_relation_changed, self._on_db_relation_changed
413# )
414# self.framework.observe(
415# self.on.db_relation_broken, self._on_db_relation_broken
416# )
417# self.framework.observe(
418# self.on.keystone_relation_joined, self._publish_keystone_info
419# )
420
421# def _publish_keystone_info(self, event: EventBase) -> NoReturn:
422# """Publishes keystone information for NBI usage through the keystone
423# relation.
424
425# Args:
426# event (EventBase): Keystone relation event to update NBI.
427# """
428# config = self.model.config
429# rel_data = {
430# "host": f"http://{self.app.name}:{KEYSTONE_PORT}/v3",
431# "port": str(KEYSTONE_PORT),
432# "keystone_db_password": config["keystone_db_password"],
433# "region_id": config["region_id"],
434# "user_domain_name": config["user_domain_name"],
435# "project_domain_name": config["project_domain_name"],
436# "admin_username": config["admin_username"],
437# "admin_password": config["admin_password"],
438# "admin_project_name": config["admin_project"],
439# "username": config["service_username"],
440# "password": config["service_password"],
441# "service": config["service_project"],
442# }
443# for k, v in rel_data.items():
444# event.relation.data[self.model.unit][k] = v
445
446# def _on_db_relation_changed(self, event: EventBase) -> NoReturn:
447# """Reads information about the DB relation, in order for keystone to
448# access it.
449
450# Args:
451# event (EventBase): DB relation event to access database
452# information.
453# """
454# if not event.unit in event.relation.data:
455# return
456# relation_data = event.relation.data[event.unit]
457# db_host = relation_data.get("host")
458# db_port = int(relation_data.get("port", 3306))
459# db_user = "root"
460# db_password = relation_data.get("root_password")
461
462# if (
463# db_host
464# and db_port
465# and db_user
466# and db_password
467# and (
468# self.state.db_host != db_host
469# or self.state.db_port != db_port
470# or self.state.db_user != db_user
471# or self.state.db_password != db_password
472# )
473# ):
474# self.state.db_host = db_host
475# self.state.db_port = db_port
476# self.state.db_user = db_user
477# self.state.db_password = db_password
478# self.on.configure_pod.emit()
479
480
481# def _on_db_relation_broken(self, event: EventBase) -> NoReturn:
482# """Clears data from db relation.
483
484# Args:
485# event (EventBase): DB relation event.
486
487# """
488# self.state.db_host = None
489# self.state.db_port = None
490# self.state.db_user = None
491# self.state.db_password = None
492# self.on.configure_pod.emit()
493
494# def _check_settings(self) -> str:
495# """Check if there any settings missing from Keystone configuration.
496
497# Returns:
498# str: Information about the problems found (if any).
499# """
500# problems = []
501# config = self.model.config
502
503# for setting in REQUIRED_SETTINGS:
504# if not config.get(setting):
505# problem = f"missing config {setting}"
506# problems.append(problem)
507
508# return ";".join(problems)
509
510# def _make_pod_image_details(self) -> Dict[str, str]:
511# """Generate the pod image details.
512
513# Returns:
514# Dict[str, str]: pod image details.
515# """
516# config = self.model.config
517# image_details = {
518# "imagePath": config["image"],
519# }
520# if config["image_username"]:
521# image_details.update(
522# {
523# "username": config["image_username"],
524# "password": config["image_password"],
525# }
526# )
527# return image_details
528
529# def _make_pod_ports(self) -> List[Dict[str, Any]]:
530# """Generate the pod ports details.
531
532# Returns:
533# List[Dict[str, Any]]: pod ports details.
534# """
535# return [
536# {"name": "keystone", "containerPort": KEYSTONE_PORT, "protocol": "TCP"},
537# ]
538
539# def _make_pod_envconfig(self) -> Dict[str, Any]:
540# """Generate pod environment configuraiton.
541
542# Returns:
543# Dict[str, Any]: pod environment configuration.
544# """
545# config = self.model.config
546
547# envconfig = {
548# "DB_HOST": self.state.db_host,
549# "DB_PORT": self.state.db_port,
550# "ROOT_DB_USER": self.state.db_user,
551# "ROOT_DB_PASSWORD": self.state.db_password,
552# "KEYSTONE_DB_PASSWORD": config["keystone_db_password"],
553# "REGION_ID": config["region_id"],
554# "KEYSTONE_HOST": self.app.name,
555# "ADMIN_USERNAME": config["admin_username"],
556# "ADMIN_PASSWORD": config["admin_password"],
557# "ADMIN_PROJECT": config["admin_project"],
558# "SERVICE_USERNAME": config["service_username"],
559# "SERVICE_PASSWORD": config["service_password"],
560# "SERVICE_PROJECT": config["service_project"],
561# }
562
563# if config.get("ldap_enabled"):
564# envconfig["LDAP_AUTHENTICATION_DOMAIN_NAME"] = config[
565# "ldap_authentication_domain_name"
566# ]
567# envconfig["LDAP_URL"] = config["ldap_url"]
568# envconfig["LDAP_PAGE_SIZE"] = config["ldap_page_size"]
569# envconfig["LDAP_USER_OBJECTCLASS"] = config["ldap_user_objectclass"]
570# envconfig["LDAP_USER_ID_ATTRIBUTE"] = config["ldap_user_id_attribute"]
571# envconfig["LDAP_USER_NAME_ATTRIBUTE"] = config["ldap_user_name_attribute"]
572# envconfig["LDAP_USER_PASS_ATTRIBUTE"] = config["ldap_user_pass_attribute"]
573# envconfig["LDAP_USER_ENABLED_MASK"] = config["ldap_user_enabled_mask"]
574# envconfig["LDAP_USER_ENABLED_DEFAULT"] = config["ldap_user_enabled_default"]
575# envconfig["LDAP_USER_ENABLED_INVERT"] = config["ldap_user_enabled_invert"]
576# envconfig["LDAP_GROUP_OBJECTCLASS"] = config["ldap_group_objectclass"]
577
578# if config["ldap_bind_user"]:
579# envconfig["LDAP_BIND_USER"] = config["ldap_bind_user"]
580
581# if config["ldap_bind_password"]:
582# envconfig["LDAP_BIND_PASSWORD"] = config["ldap_bind_password"]
583
584# if config["ldap_user_tree_dn"]:
585# envconfig["LDAP_USER_TREE_DN"] = config["ldap_user_tree_dn"]
586
587# if config["ldap_user_filter"]:
588# envconfig["LDAP_USER_FILTER"] = config["ldap_user_filter"]
589
590# if config["ldap_user_enabled_attribute"]:
591# envconfig["LDAP_USER_ENABLED_ATTRIBUTE"] = config[
592# "ldap_user_enabled_attribute"
593# ]
594
595# if config["ldap_chase_referrals"]:
596# envconfig["LDAP_CHASE_REFERRALS"] = config["ldap_chase_referrals"]
597
598# if config["ldap_group_tree_dn"]:
599# envconfig["LDAP_GROUP_TREE_DN"] = config["ldap_group_tree_dn"]
600
601# if config["ldap_use_starttls"]:
602# envconfig["LDAP_USE_STARTTLS"] = config["ldap_use_starttls"]
603# envconfig["LDAP_TLS_CACERT_BASE64"] = config["ldap_tls_cacert_base64"]
604# envconfig["LDAP_TLS_REQ_CERT"] = config["ldap_tls_req_cert"]
605
606# return envconfig
607
608# def _make_pod_ingress_resources(self) -> List[Dict[str, Any]]:
609# """Generate pod ingress resources.
610
611# Returns:
612# List[Dict[str, Any]]: pod ingress resources.
613# """
614# site_url = self.model.config["site_url"]
615
616# if not site_url:
617# return
618
619# parsed = urlparse(site_url)
620
621# if not parsed.scheme.startswith("http"):
622# return
623
624# max_file_size = self.model.config["max_file_size"]
625# ingress_whitelist_source_range = self.model.config[
626# "ingress_whitelist_source_range"
627# ]
628
629# annotations = {
630# "nginx.ingress.kubernetes.io/proxy-body-size": "{}m".format(max_file_size)
631# }
632
633# if ingress_whitelist_source_range:
634# annotations[
635# "nginx.ingress.kubernetes.io/whitelist-source-range"
636# ] = ingress_whitelist_source_range
637
638# ingress_spec_tls = None
639
640# if parsed.scheme == "https":
641# ingress_spec_tls = [{"hosts": [parsed.hostname]}]
642# tls_secret_name = self.model.config["tls_secret_name"]
643# if tls_secret_name:
644# ingress_spec_tls[0]["secretName"] = tls_secret_name
645# else:
646# annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
647
648# ingress = {
649# "name": "{}-ingress".format(self.app.name),
650# "annotations": annotations,
651# "spec": {
652# "rules": [
653# {
654# "host": parsed.hostname,
655# "http": {
656# "paths": [
657# {
658# "path": "/",
659# "backend": {
660# "serviceName": self.app.name,
661# "servicePort": KEYSTONE_PORT,
662# },
663# }
664# ]
665# },
666# }
667# ],
668# },
669# }
670# if ingress_spec_tls:
671# ingress["spec"]["tls"] = ingress_spec_tls
672
673# return [ingress]
674
675# def _generate_keys(self) -> Tuple[List[str], List[str]]:
676# """Generating new fernet tokens.
677
678# Returns:
679# Tuple[List[str], List[str]]: contains two lists of strings. First
680# list contains strings that represent
681# the keys for fernet and the second
682# list contains strins that represent
683# the keys for credentials.
684# """
685# fernet_keys = [
686# Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS)
687# ]
688# credential_keys = [
689# Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS)
690# ]
691
692# return (fernet_keys, credential_keys)
693
694# def configure_pod(self, event: EventBase) -> NoReturn:
695# """Assemble the pod spec and apply it, if possible.
696
697# Args:
698# event (EventBase): Hook or Relation event that started the
699# function.
700# """
701# if not self.state.db_host:
702# self.unit.status = WaitingStatus("Waiting for database relation")
703# event.defer()
704# return
705
706# if not self.unit.is_leader():
707# self.unit.status = ActiveStatus("ready")
708# return
709
710# if fernet_keys := self.state.fernet_keys:
711# fernet_keys = json.loads(fernet_keys)
712
713# if credential_keys := self.state.credential_keys:
714# credential_keys = json.loads(credential_keys)
715
716# now = datetime.now().timestamp()
717# keys_timestamp = self.state.keys_timestamp
718# token_expiration = self.model.config["token_expiration"]
719
720# valid_keys = (now - keys_timestamp) < token_expiration
721# if not credential_keys or not fernet_keys or not valid_keys:
722# fernet_keys, credential_keys = self._generate_keys()
723# self.state.fernet_keys = json.dumps(fernet_keys)
724# self.state.credential_keys = json.dumps(credential_keys)
725# self.state.keys_timestamp = now
726
727# # Check problems in the settings
728# problems = self._check_settings()
729# if problems:
730# self.unit.status = BlockedStatus(problems)
731# return
732
733# self.unit.status = BlockedStatus("Assembling pod spec")
734# image_details = self._make_pod_image_details()
735# ports = self._make_pod_ports()
736# env_config = self._make_pod_envconfig()
737# ingress_resources = self._make_pod_ingress_resources()
738# files = self._make_pod_files(fernet_keys, credential_keys)
739
740# pod_spec = {
741# "version": 3,
742# "containers": [
743# {
744# "name": self.framework.model.app.name,
745# "imageDetails": image_details,
746# "ports": ports,
747# "envConfig": env_config,
748# "volumeConfig": files,
749# }
750# ],
751# "kubernetesResources": {"ingressResources": ingress_resources or []},
752# }
753
754# if self.state.pod_spec != (
755# pod_spec_json := json.dumps(pod_spec, sort_keys=True)
756# ):
757# self.state.pod_spec = pod_spec_json
758# self.model.pod.set_spec(pod_spec)
759
760# self.unit.status = ActiveStatus("ready")
761
762
763# if __name__ == "__main__":
764# main(KeystoneCharm)