2 # Copyright 2021 Canonical Ltd.
4 # 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
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # 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
16 # For those usages not covered by the Apache License, Version 2.0 please
17 # contact: legal@canonical.com
19 # To get in touch with the maintainers, please contact:
20 # osm-charmers@lists.launchpad.net
23 # pylint: disable=E0213
26 from datetime
import datetime
27 from ipaddress
import ip_network
30 from typing
import List
, NoReturn
, Optional
, Tuple
31 from urllib
.parse
import urlparse
33 from cryptography
.fernet
import Fernet
34 from ops
.main
import main
35 from opslib
.osm
.charm
import CharmedOsmBase
, RelationsMissing
36 from opslib
.osm
.interfaces
.keystone
import KeystoneServer
37 from opslib
.osm
.interfaces
.mysql
import MysqlClient
38 from opslib
.osm
.pod
import (
41 IngressResourceV3Builder
,
45 from opslib
.osm
.validator
import ModelValidator
, validator
48 logger
= logging
.getLogger(__name__
)
51 REQUIRED_SETTINGS
= ["token_expiration"]
53 # This is hardcoded in the keystone container script
54 DATABASE_NAME
= "keystone"
56 # We expect the keystone container to use the default port
59 # Number of keys need might need to be adjusted in the future
60 NUMBER_FERNET_KEYS
= 2
61 NUMBER_CREDENTIAL_KEYS
= 2
64 CREDENTIAL_KEYS_PATH
= "/etc/keystone/credential-keys"
65 FERNET_KEYS_PATH
= "/etc/keystone/fernet-keys"
68 class ConfigModel(ModelValidator
):
70 keystone_db_password
: str
78 project_domain_name
: str
81 site_url
: Optional
[str]
82 ingress_class
: Optional
[str]
83 ingress_whitelist_source_range
: Optional
[str]
84 tls_secret_name
: Optional
[str]
85 mysql_host
: Optional
[str]
86 mysql_port
: Optional
[int]
87 mysql_root_password
: Optional
[str]
88 image_pull_policy
: str
90 @validator("max_file_size")
91 def validate_max_file_size(cls
, v
):
93 raise ValueError("value must be equal or greater than 0")
96 @validator("site_url")
97 def validate_site_url(cls
, v
):
100 if not parsed
.scheme
.startswith("http"):
101 raise ValueError("value must start with http")
104 @validator("ingress_whitelist_source_range")
105 def validate_ingress_whitelist_source_range(cls
, v
):
110 @validator("mysql_port")
111 def validate_mysql_port(cls
, v
):
112 if v
and (v
<= 0 or v
>= 65535):
113 raise ValueError("Mysql port out of range")
116 @validator("image_pull_policy")
117 def validate_image_pull_policy(cls
, v
):
120 "ifnotpresent": "IfNotPresent",
124 if v
not in values
.keys():
125 raise ValueError("value must be always, ifnotpresent or never")
129 class ConfigLdapModel(ModelValidator
):
131 ldap_authentication_domain_name
: Optional
[str]
132 ldap_url
: Optional
[str]
133 ldap_bind_user
: Optional
[str]
134 ldap_bind_password
: Optional
[str]
135 ldap_chase_referrals
: Optional
[str]
136 ldap_page_size
: Optional
[int]
137 ldap_user_tree_dn
: Optional
[str]
138 ldap_user_objectclass
: Optional
[str]
139 ldap_user_id_attribute
: Optional
[str]
140 ldap_user_name_attribute
: Optional
[str]
141 ldap_user_pass_attribute
: Optional
[str]
142 ldap_user_filter
: Optional
[str]
143 ldap_user_enabled_attribute
: Optional
[str]
144 ldap_user_enabled_mask
: Optional
[int]
145 ldap_user_enabled_default
: Optional
[str]
146 ldap_user_enabled_invert
: Optional
[bool]
147 ldap_group_objectclass
: Optional
[str]
148 ldap_group_tree_dn
: Optional
[str]
149 ldap_use_starttls
: Optional
[bool]
150 ldap_tls_cacert_base64
: Optional
[str]
151 ldap_tls_req_cert
: Optional
[str]
154 def validate_ldap_user_enabled_default(cls
, v
):
156 if v
not in ["true", "false"]:
157 raise ValueError('must be equal to "true" or "false"')
161 class KeystoneCharm(CharmedOsmBase
):
162 def __init__(self
, *args
) -> NoReturn
:
168 self
.state
.set_default(fernet_keys
=None)
169 self
.state
.set_default(credential_keys
=None)
170 self
.state
.set_default(keys_timestamp
=0)
172 self
.keystone_server
= KeystoneServer(self
, "keystone")
173 self
.mysql_client
= MysqlClient(self
, "db")
174 self
.framework
.observe(self
.on
["db"].relation_changed
, self
.configure_pod
)
175 self
.framework
.observe(self
.on
["db"].relation_broken
, self
.configure_pod
)
176 self
.framework
.observe(self
.on
.update_status
, self
.configure_pod
)
178 self
.framework
.observe(
179 self
.on
["keystone"].relation_joined
, self
._publish
_keystone
_info
182 def _publish_keystone_info(self
, event
):
183 if self
.unit
.is_leader():
184 config
= ConfigModel(**dict(self
.config
))
185 self
.keystone_server
.publish_info(
186 host
=f
"http://{self.app.name}:{PORT}/v3",
188 user_domain_name
=config
.user_domain_name
,
189 project_domain_name
=config
.project_domain_name
,
190 username
=config
.service_username
,
191 password
=config
.service_password
,
192 service
=config
.service_project
,
193 keystone_db_password
=config
.keystone_db_password
,
194 region_id
=config
.region_id
,
195 admin_username
=config
.admin_username
,
196 admin_password
=config
.admin_password
,
197 admin_project_name
=config
.admin_project
,
200 def _check_missing_dependencies(self
, config
: ConfigModel
, external_db
: bool):
201 missing_relations
= []
202 if not external_db
and self
.mysql_client
.is_missing_data_in_unit():
203 missing_relations
.append("mysql")
204 if missing_relations
:
205 raise RelationsMissing(missing_relations
)
207 def _generate_keys(self
) -> Tuple
[List
[str], List
[str]]:
208 """Generating new fernet tokens.
211 Tuple[List[str], List[str]]: contains two lists of strings. First
212 list contains strings that represent
213 the keys for fernet and the second
214 list contains strins that represent
215 the keys for credentials.
218 Fernet
.generate_key().decode() for _
in range(NUMBER_FERNET_KEYS
)
221 Fernet
.generate_key().decode() for _
in range(NUMBER_CREDENTIAL_KEYS
)
224 return (fernet_keys
, credential_keys
)
227 keys_timestamp
= self
.state
.keys_timestamp
228 if fernet_keys
:= self
.state
.fernet_keys
:
229 fernet_keys
= json
.loads(fernet_keys
)
231 if credential_keys
:= self
.state
.credential_keys
:
232 credential_keys
= json
.loads(credential_keys
)
234 now
= datetime
.now().timestamp()
235 token_expiration
= self
.config
["token_expiration"]
237 valid_keys
= (now
- keys_timestamp
) < token_expiration
238 if not credential_keys
or not fernet_keys
or not valid_keys
:
239 fernet_keys
, credential_keys
= self
._generate
_keys
()
240 self
.state
.fernet_keys
= json
.dumps(fernet_keys
)
241 self
.state
.credential_keys
= json
.dumps(credential_keys
)
242 self
.state
.keys_timestamp
= now
243 return credential_keys
, fernet_keys
246 self
, config
: ConfigModel
, credential_keys
: List
, fernet_keys
: List
248 credentials_files_builder
= FilesV3Builder()
249 fernet_files_builder
= FilesV3Builder()
250 for (key_id
, _
) in enumerate(credential_keys
):
251 credentials_files_builder
.add_file(str(key_id
), str(key_id
), secret
=True)
252 for (key_id
, _
) in enumerate(fernet_keys
):
253 fernet_files_builder
.add_file(str(key_id
), str(key_id
), secret
=True)
254 return credentials_files_builder
.build(), fernet_files_builder
.build()
256 def build_pod_spec(self
, image_info
, **kwargs
):
258 config
= ConfigModel(**dict(self
.config
))
259 mysql_config
= kwargs
["mysql_config"]
260 config_ldap
= ConfigLdapModel(**dict(self
.config
))
262 if mysql_config
.mysql_uri
and not self
.mysql_client
.is_missing_data_in_unit():
263 raise Exception("Mysql data cannot be provided via config and relation")
265 external_db
= True if mysql_config
.mysql_uri
else False
266 self
._check
_missing
_dependencies
(config
, external_db
)
268 # Create Builder for the PodSpec
269 pod_spec_builder
= PodSpecV3Builder()
270 container_builder
= ContainerV3Builder(
271 self
.app
.name
, image_info
, config
.image_pull_policy
275 credential_keys
, fernet_keys
= self
._get
_keys
()
276 credential_files
, fernet_files
= self
._build
_files
(
277 config
, credential_keys
, fernet_keys
281 fernet_keys_secret_name
= f
"{self.app.name}-fernet-keys-secret"
282 pod_spec_builder
.add_secret(
283 fernet_keys_secret_name
,
284 {str(key_id
): value
for (key_id
, value
) in enumerate(fernet_keys
)},
286 credential_keys_secret_name
= f
"{self.app.name}-credential-keys-secret"
287 pod_spec_builder
.add_secret(
288 credential_keys_secret_name
,
289 {str(key_id
): value
for (key_id
, value
) in enumerate(credential_keys
)},
291 mysql_secret_name
= f
"{self.app.name}-mysql-secret"
293 pod_spec_builder
.add_secret(
296 "host": mysql_config
.host
,
297 "port": str(mysql_config
.port
),
298 "user": mysql_config
.username
,
299 "password": mysql_config
.password
,
301 if mysql_config
.mysql_uri
303 "host": self
.mysql_client
.host
,
304 "port": str(self
.mysql_client
.port
),
306 "password": self
.mysql_client
.root_password
,
309 keystone_secret_name
= f
"{self.app.name}-keystone-secret"
310 pod_spec_builder
.add_secret(
311 keystone_secret_name
,
313 "db_password": config
.keystone_db_password
,
314 "admin_username": config
.admin_username
,
315 "admin_password": config
.admin_password
,
316 "admin_project": config
.admin_project
,
317 "service_username": config
.service_username
,
318 "service_password": config
.service_password
,
319 "service_project": config
.service_project
,
323 container_builder
.add_volume_config(
325 CREDENTIAL_KEYS_PATH
,
327 secret_name
=credential_keys_secret_name
,
329 container_builder
.add_volume_config(
333 secret_name
=fernet_keys_secret_name
,
335 container_builder
.add_port(name
=self
.app
.name
, port
=PORT
)
336 container_builder
.add_envs(
338 "REGION_ID": config
.region_id
,
339 "KEYSTONE_HOST": self
.app
.name
,
342 container_builder
.add_secret_envs(
343 secret_name
=mysql_secret_name
,
347 "ROOT_DB_USER": "user",
348 "ROOT_DB_PASSWORD": "password",
351 container_builder
.add_secret_envs(
352 secret_name
=keystone_secret_name
,
354 "KEYSTONE_DB_PASSWORD": "db_password",
355 "ADMIN_USERNAME": "admin_username",
356 "ADMIN_PASSWORD": "admin_password",
357 "ADMIN_PROJECT": "admin_project",
358 "SERVICE_USERNAME": "service_username",
359 "SERVICE_PASSWORD": "service_password",
360 "SERVICE_PROJECT": "service_project",
363 ldap_secret_name
= f
"{self.app.name}-ldap-secret"
364 if config_ldap
.ldap_enabled
:
365 # Add ldap secrets and envs
367 "authentication_domain_name": config_ldap
.ldap_authentication_domain_name
,
368 "url": config_ldap
.ldap_url
,
369 "page_size": config_ldap
.ldap_page_size
,
370 "user_objectclass": config_ldap
.ldap_user_objectclass
,
371 "user_id_attribute": config_ldap
.ldap_user_id_attribute
,
372 "user_name_attribute": config_ldap
.ldap_user_name_attribute
,
373 "user_pass_attribute": config_ldap
.ldap_user_pass_attribute
,
374 "user_enabled_mask": config_ldap
.ldap_user_enabled_mask
,
375 "user_enabled_default": config_ldap
.ldap_user_enabled_default
,
376 "user_enabled_invert": config_ldap
.ldap_user_enabled_invert
,
377 "group_objectclass": config_ldap
.ldap_group_objectclass
,
380 "LDAP_AUTHENTICATION_DOMAIN_NAME": "authentication_domain_name",
382 "LDAP_PAGE_SIZE": "page_size",
383 "LDAP_USER_OBJECTCLASS": "user_objectclass",
384 "LDAP_USER_ID_ATTRIBUTE": "user_id_attribute",
385 "LDAP_USER_NAME_ATTRIBUTE": "user_name_attribute",
386 "LDAP_USER_PASS_ATTRIBUTE": "user_pass_attribute",
387 "LDAP_USER_ENABLED_MASK": "user_enabled_mask",
388 "LDAP_USER_ENABLED_DEFAULT": "user_enabled_default",
389 "LDAP_USER_ENABLED_INVERT": "user_enabled_invert",
390 "LDAP_GROUP_OBJECTCLASS": "group_objectclass",
392 if config_ldap
.ldap_bind_user
:
393 ldap_secrets
["bind_user"] = config_ldap
.ldap_bind_user
394 ldap_envs
["LDAP_BIND_USER"] = "bind_user"
396 if config_ldap
.ldap_bind_password
:
397 ldap_secrets
["bind_password"] = config_ldap
.ldap_bind_password
398 ldap_envs
["LDAP_BIND_PASSWORD"] = "bind_password"
400 if config_ldap
.ldap_user_tree_dn
:
401 ldap_secrets
["user_tree_dn"] = config_ldap
.ldap_user_tree_dn
402 ldap_envs
["LDAP_USER_TREE_DN"] = "user_tree_dn"
404 if config_ldap
.ldap_user_filter
:
405 ldap_secrets
["user_filter"] = config_ldap
.ldap_user_filter
406 ldap_envs
["LDAP_USER_FILTER"] = "user_filter"
408 if config_ldap
.ldap_user_enabled_attribute
:
410 "user_enabled_attribute"
411 ] = config_ldap
.ldap_user_enabled_attribute
412 ldap_envs
["LDAP_USER_ENABLED_ATTRIBUTE"] = "user_enabled_attribute"
413 if config_ldap
.ldap_chase_referrals
:
414 ldap_secrets
["chase_referrals"] = config_ldap
.ldap_chase_referrals
415 ldap_envs
["LDAP_CHASE_REFERRALS"] = "chase_referrals"
417 if config_ldap
.ldap_group_tree_dn
:
418 ldap_secrets
["group_tree_dn"] = config_ldap
.ldap_group_tree_dn
419 ldap_envs
["LDAP_GROUP_TREE_DN"] = "group_tree_dn"
421 if config_ldap
.ldap_tls_cacert_base64
:
422 ldap_secrets
["tls_cacert_base64"] = config_ldap
.ldap_tls_cacert_base64
423 ldap_envs
["LDAP_TLS_CACERT_BASE64"] = "tls_cacert_base64"
425 if config_ldap
.ldap_use_starttls
:
426 ldap_secrets
["use_starttls"] = config_ldap
.ldap_use_starttls
427 ldap_secrets
["tls_cacert_base64"] = config_ldap
.ldap_tls_cacert_base64
428 ldap_secrets
["tls_req_cert"] = config_ldap
.ldap_tls_req_cert
429 ldap_envs
["LDAP_USE_STARTTLS"] = "use_starttls"
430 ldap_envs
["LDAP_TLS_CACERT_BASE64"] = "tls_cacert_base64"
431 ldap_envs
["LDAP_TLS_REQ_CERT"] = "tls_req_cert"
433 pod_spec_builder
.add_secret(
437 container_builder
.add_secret_envs(
438 secret_name
=ldap_secret_name
,
442 container
= container_builder
.build()
444 # Add container to pod spec
445 pod_spec_builder
.add_container(container
)
447 # Add Pod Restart Policy
448 restart_policy
= PodRestartPolicy()
449 restart_policy
.add_secrets(
450 secret_names
=(mysql_secret_name
, keystone_secret_name
, ldap_secret_name
)
452 pod_spec_builder
.set_restart_policy(restart_policy
)
454 # Add ingress resources to pod spec if site url exists
456 parsed
= urlparse(config
.site_url
)
458 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
459 str(config
.max_file_size
) + "m"
460 if config
.max_file_size
> 0
461 else config
.max_file_size
464 if config
.ingress_class
:
465 annotations
["kubernetes.io/ingress.class"] = config
.ingress_class
466 ingress_resource_builder
= IngressResourceV3Builder(
467 f
"{self.app.name}-ingress", annotations
470 if config
.ingress_whitelist_source_range
:
472 "nginx.ingress.kubernetes.io/whitelist-source-range"
473 ] = config
.ingress_whitelist_source_range
475 if parsed
.scheme
== "https":
476 ingress_resource_builder
.add_tls(
477 [parsed
.hostname
], config
.tls_secret_name
480 annotations
["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
482 ingress_resource_builder
.add_rule(parsed
.hostname
, self
.app
.name
, PORT
)
483 ingress_resource
= ingress_resource_builder
.build()
484 pod_spec_builder
.add_ingress_resource(ingress_resource
)
485 return pod_spec_builder
.build()
488 if __name__
== "__main__":
491 # LOGGER = logging.getLogger(__name__)
494 # class ConfigurePodEvent(EventBase):
495 # """Configure Pod event"""
500 # class KeystoneEvents(CharmEvents):
501 # """Keystone Events"""
503 # configure_pod = EventSource(ConfigurePodEvent)
505 # class KeystoneCharm(CharmBase):
506 # """Keystone K8s Charm"""
508 # state = StoredState()
509 # on = KeystoneEvents()
511 # def __init__(self, *args) -> NoReturn:
512 # """Constructor of the Charm object.
513 # Initializes internal state and register events it can handle.
515 # super().__init__(*args)
516 # self.state.set_default(db_host=None)
517 # self.state.set_default(db_port=None)
518 # self.state.set_default(db_user=None)
519 # self.state.set_default(db_password=None)
520 # self.state.set_default(pod_spec=None)
521 # self.state.set_default(fernet_keys=None)
522 # self.state.set_default(credential_keys=None)
523 # self.state.set_default(keys_timestamp=0)
525 # # Register all of the events we want to observe
526 # self.framework.observe(self.on.config_changed, self.configure_pod)
527 # self.framework.observe(self.on.start, self.configure_pod)
528 # self.framework.observe(self.on.upgrade_charm, self.configure_pod)
529 # self.framework.observe(self.on.leader_elected, self.configure_pod)
530 # self.framework.observe(self.on.update_status, self.configure_pod)
532 # # Registering custom internal events
533 # self.framework.observe(self.on.configure_pod, self.configure_pod)
535 # # Register relation events
536 # self.framework.observe(
537 # self.on.db_relation_changed, self._on_db_relation_changed
539 # self.framework.observe(
540 # self.on.db_relation_broken, self._on_db_relation_broken
542 # self.framework.observe(
543 # self.on.keystone_relation_joined, self._publish_keystone_info
546 # def _publish_keystone_info(self, event: EventBase) -> NoReturn:
547 # """Publishes keystone information for NBI usage through the keystone
551 # event (EventBase): Keystone relation event to update NBI.
553 # config = self.model.config
555 # "host": f"http://{self.app.name}:{KEYSTONE_PORT}/v3",
556 # "port": str(KEYSTONE_PORT),
557 # "keystone_db_password": config["keystone_db_password"],
558 # "region_id": config["region_id"],
559 # "user_domain_name": config["user_domain_name"],
560 # "project_domain_name": config["project_domain_name"],
561 # "admin_username": config["admin_username"],
562 # "admin_password": config["admin_password"],
563 # "admin_project_name": config["admin_project"],
564 # "username": config["service_username"],
565 # "password": config["service_password"],
566 # "service": config["service_project"],
568 # for k, v in rel_data.items():
569 # event.relation.data[self.model.unit][k] = v
571 # def _on_db_relation_changed(self, event: EventBase) -> NoReturn:
572 # """Reads information about the DB relation, in order for keystone to
576 # event (EventBase): DB relation event to access database
579 # if not event.unit in event.relation.data:
581 # relation_data = event.relation.data[event.unit]
582 # db_host = relation_data.get("host")
583 # db_port = int(relation_data.get("port", 3306))
585 # db_password = relation_data.get("root_password")
593 # self.state.db_host != db_host
594 # or self.state.db_port != db_port
595 # or self.state.db_user != db_user
596 # or self.state.db_password != db_password
599 # self.state.db_host = db_host
600 # self.state.db_port = db_port
601 # self.state.db_user = db_user
602 # self.state.db_password = db_password
603 # self.on.configure_pod.emit()
606 # def _on_db_relation_broken(self, event: EventBase) -> NoReturn:
607 # """Clears data from db relation.
610 # event (EventBase): DB relation event.
613 # self.state.db_host = None
614 # self.state.db_port = None
615 # self.state.db_user = None
616 # self.state.db_password = None
617 # self.on.configure_pod.emit()
619 # def _check_settings(self) -> str:
620 # """Check if there any settings missing from Keystone configuration.
623 # str: Information about the problems found (if any).
626 # config = self.model.config
628 # for setting in REQUIRED_SETTINGS:
629 # if not config.get(setting):
630 # problem = f"missing config {setting}"
631 # problems.append(problem)
633 # return ";".join(problems)
635 # def _make_pod_image_details(self) -> Dict[str, str]:
636 # """Generate the pod image details.
639 # Dict[str, str]: pod image details.
641 # config = self.model.config
643 # "imagePath": config["image"],
645 # if config["image_username"]:
646 # image_details.update(
648 # "username": config["image_username"],
649 # "password": config["image_password"],
652 # return image_details
654 # def _make_pod_ports(self) -> List[Dict[str, Any]]:
655 # """Generate the pod ports details.
658 # List[Dict[str, Any]]: pod ports details.
661 # {"name": "keystone", "containerPort": KEYSTONE_PORT, "protocol": "TCP"},
664 # def _make_pod_envconfig(self) -> Dict[str, Any]:
665 # """Generate pod environment configuraiton.
668 # Dict[str, Any]: pod environment configuration.
670 # config = self.model.config
673 # "DB_HOST": self.state.db_host,
674 # "DB_PORT": self.state.db_port,
675 # "ROOT_DB_USER": self.state.db_user,
676 # "ROOT_DB_PASSWORD": self.state.db_password,
677 # "KEYSTONE_DB_PASSWORD": config["keystone_db_password"],
678 # "REGION_ID": config["region_id"],
679 # "KEYSTONE_HOST": self.app.name,
680 # "ADMIN_USERNAME": config["admin_username"],
681 # "ADMIN_PASSWORD": config["admin_password"],
682 # "ADMIN_PROJECT": config["admin_project"],
683 # "SERVICE_USERNAME": config["service_username"],
684 # "SERVICE_PASSWORD": config["service_password"],
685 # "SERVICE_PROJECT": config["service_project"],
688 # if config.get("ldap_enabled"):
689 # envconfig["LDAP_AUTHENTICATION_DOMAIN_NAME"] = config[
690 # "ldap_authentication_domain_name"
692 # envconfig["LDAP_URL"] = config["ldap_url"]
693 # envconfig["LDAP_PAGE_SIZE"] = config["ldap_page_size"]
694 # envconfig["LDAP_USER_OBJECTCLASS"] = config["ldap_user_objectclass"]
695 # envconfig["LDAP_USER_ID_ATTRIBUTE"] = config["ldap_user_id_attribute"]
696 # envconfig["LDAP_USER_NAME_ATTRIBUTE"] = config["ldap_user_name_attribute"]
697 # envconfig["LDAP_USER_PASS_ATTRIBUTE"] = config["ldap_user_pass_attribute"]
698 # envconfig["LDAP_USER_ENABLED_MASK"] = config["ldap_user_enabled_mask"]
699 # envconfig["LDAP_USER_ENABLED_DEFAULT"] = config["ldap_user_enabled_default"]
700 # envconfig["LDAP_USER_ENABLED_INVERT"] = config["ldap_user_enabled_invert"]
701 # envconfig["LDAP_GROUP_OBJECTCLASS"] = config["ldap_group_objectclass"]
703 # if config["ldap_bind_user"]:
704 # envconfig["LDAP_BIND_USER"] = config["ldap_bind_user"]
706 # if config["ldap_bind_password"]:
707 # envconfig["LDAP_BIND_PASSWORD"] = config["ldap_bind_password"]
709 # if config["ldap_user_tree_dn"]:
710 # envconfig["LDAP_USER_TREE_DN"] = config["ldap_user_tree_dn"]
712 # if config["ldap_user_filter"]:
713 # envconfig["LDAP_USER_FILTER"] = config["ldap_user_filter"]
715 # if config["ldap_user_enabled_attribute"]:
716 # envconfig["LDAP_USER_ENABLED_ATTRIBUTE"] = config[
717 # "ldap_user_enabled_attribute"
720 # if config["ldap_chase_referrals"]:
721 # envconfig["LDAP_CHASE_REFERRALS"] = config["ldap_chase_referrals"]
723 # if config["ldap_group_tree_dn"]:
724 # envconfig["LDAP_GROUP_TREE_DN"] = config["ldap_group_tree_dn"]
726 # if config["ldap_use_starttls"]:
727 # envconfig["LDAP_USE_STARTTLS"] = config["ldap_use_starttls"]
728 # envconfig["LDAP_TLS_CACERT_BASE64"] = config["ldap_tls_cacert_base64"]
729 # envconfig["LDAP_TLS_REQ_CERT"] = config["ldap_tls_req_cert"]
733 # def _make_pod_ingress_resources(self) -> List[Dict[str, Any]]:
734 # """Generate pod ingress resources.
737 # List[Dict[str, Any]]: pod ingress resources.
739 # site_url = self.model.config["site_url"]
744 # parsed = urlparse(site_url)
746 # if not parsed.scheme.startswith("http"):
749 # max_file_size = self.model.config["max_file_size"]
750 # ingress_whitelist_source_range = self.model.config[
751 # "ingress_whitelist_source_range"
755 # "nginx.ingress.kubernetes.io/proxy-body-size": "{}m".format(max_file_size)
758 # if ingress_whitelist_source_range:
760 # "nginx.ingress.kubernetes.io/whitelist-source-range"
761 # ] = ingress_whitelist_source_range
763 # ingress_spec_tls = None
765 # if parsed.scheme == "https":
766 # ingress_spec_tls = [{"hosts": [parsed.hostname]}]
767 # tls_secret_name = self.model.config["tls_secret_name"]
768 # if tls_secret_name:
769 # ingress_spec_tls[0]["secretName"] = tls_secret_name
771 # annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
774 # "name": "{}-ingress".format(self.app.name),
775 # "annotations": annotations,
779 # "host": parsed.hostname,
785 # "serviceName": self.app.name,
786 # "servicePort": KEYSTONE_PORT,
795 # if ingress_spec_tls:
796 # ingress["spec"]["tls"] = ingress_spec_tls
800 # def _generate_keys(self) -> Tuple[List[str], List[str]]:
801 # """Generating new fernet tokens.
804 # Tuple[List[str], List[str]]: contains two lists of strings. First
805 # list contains strings that represent
806 # the keys for fernet and the second
807 # list contains strins that represent
808 # the keys for credentials.
811 # Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS)
813 # credential_keys = [
814 # Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS)
817 # return (fernet_keys, credential_keys)
819 # def configure_pod(self, event: EventBase) -> NoReturn:
820 # """Assemble the pod spec and apply it, if possible.
823 # event (EventBase): Hook or Relation event that started the
826 # if not self.state.db_host:
827 # self.unit.status = WaitingStatus("Waiting for database relation")
831 # if not self.unit.is_leader():
832 # self.unit.status = ActiveStatus("ready")
835 # if fernet_keys := self.state.fernet_keys:
836 # fernet_keys = json.loads(fernet_keys)
838 # if credential_keys := self.state.credential_keys:
839 # credential_keys = json.loads(credential_keys)
841 # now = datetime.now().timestamp()
842 # keys_timestamp = self.state.keys_timestamp
843 # token_expiration = self.model.config["token_expiration"]
845 # valid_keys = (now - keys_timestamp) < token_expiration
846 # if not credential_keys or not fernet_keys or not valid_keys:
847 # fernet_keys, credential_keys = self._generate_keys()
848 # self.state.fernet_keys = json.dumps(fernet_keys)
849 # self.state.credential_keys = json.dumps(credential_keys)
850 # self.state.keys_timestamp = now
852 # # Check problems in the settings
853 # problems = self._check_settings()
855 # self.unit.status = BlockedStatus(problems)
858 # self.unit.status = BlockedStatus("Assembling pod spec")
859 # image_details = self._make_pod_image_details()
860 # ports = self._make_pod_ports()
861 # env_config = self._make_pod_envconfig()
862 # ingress_resources = self._make_pod_ingress_resources()
863 # files = self._make_pod_files(fernet_keys, credential_keys)
869 # "name": self.framework.model.app.name,
870 # "imageDetails": image_details,
872 # "envConfig": env_config,
873 # "volumeConfig": files,
876 # "kubernetesResources": {"ingressResources": ingress_resources or []},
879 # if self.state.pod_spec != (
880 # pod_spec_json := json.dumps(pod_spec, sort_keys=True)
882 # self.state.pod_spec = pod_spec_json
883 # self.model.pod.set_spec(pod_spec)
885 # self.unit.status = ActiveStatus("ready")
888 # if __name__ == "__main__":
889 # main(KeystoneCharm)