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
89 security_context
: bool
91 @validator("max_file_size")
92 def validate_max_file_size(cls
, v
):
94 raise ValueError("value must be equal or greater than 0")
97 @validator("site_url")
98 def validate_site_url(cls
, v
):
101 if not parsed
.scheme
.startswith("http"):
102 raise ValueError("value must start with http")
105 @validator("ingress_whitelist_source_range")
106 def validate_ingress_whitelist_source_range(cls
, v
):
111 @validator("mysql_port")
112 def validate_mysql_port(cls
, v
):
113 if v
and (v
<= 0 or v
>= 65535):
114 raise ValueError("Mysql port out of range")
117 @validator("image_pull_policy")
118 def validate_image_pull_policy(cls
, v
):
121 "ifnotpresent": "IfNotPresent",
125 if v
not in values
.keys():
126 raise ValueError("value must be always, ifnotpresent or never")
130 class ConfigLdapModel(ModelValidator
):
132 ldap_authentication_domain_name
: Optional
[str]
133 ldap_url
: Optional
[str]
134 ldap_bind_user
: Optional
[str]
135 ldap_bind_password
: Optional
[str]
136 ldap_chase_referrals
: Optional
[str]
137 ldap_page_size
: Optional
[int]
138 ldap_user_tree_dn
: Optional
[str]
139 ldap_user_objectclass
: Optional
[str]
140 ldap_user_id_attribute
: Optional
[str]
141 ldap_user_name_attribute
: Optional
[str]
142 ldap_user_pass_attribute
: Optional
[str]
143 ldap_user_filter
: Optional
[str]
144 ldap_user_enabled_attribute
: Optional
[str]
145 ldap_user_enabled_mask
: Optional
[int]
146 ldap_user_enabled_default
: Optional
[str]
147 ldap_user_enabled_invert
: Optional
[bool]
148 ldap_group_objectclass
: Optional
[str]
149 ldap_group_tree_dn
: Optional
[str]
150 ldap_use_starttls
: Optional
[bool]
151 ldap_tls_cacert_base64
: Optional
[str]
152 ldap_tls_req_cert
: Optional
[str]
155 def validate_ldap_user_enabled_default(cls
, v
):
157 if v
not in ["true", "false"]:
158 raise ValueError('must be equal to "true" or "false"')
162 class KeystoneCharm(CharmedOsmBase
):
163 def __init__(self
, *args
) -> NoReturn
:
169 self
.state
.set_default(fernet_keys
=None)
170 self
.state
.set_default(credential_keys
=None)
171 self
.state
.set_default(keys_timestamp
=0)
173 self
.keystone_server
= KeystoneServer(self
, "keystone")
174 self
.mysql_client
= MysqlClient(self
, "db")
175 self
.framework
.observe(self
.on
["db"].relation_changed
, self
.configure_pod
)
176 self
.framework
.observe(self
.on
["db"].relation_broken
, self
.configure_pod
)
177 self
.framework
.observe(self
.on
.update_status
, self
.configure_pod
)
179 self
.framework
.observe(
180 self
.on
["keystone"].relation_joined
, self
._publish
_keystone
_info
183 def _publish_keystone_info(self
, event
):
184 if self
.unit
.is_leader():
185 config
= ConfigModel(**dict(self
.config
))
186 self
.keystone_server
.publish_info(
187 host
=f
"http://{self.app.name}:{PORT}/v3",
189 user_domain_name
=config
.user_domain_name
,
190 project_domain_name
=config
.project_domain_name
,
191 username
=config
.service_username
,
192 password
=config
.service_password
,
193 service
=config
.service_project
,
194 keystone_db_password
=config
.keystone_db_password
,
195 region_id
=config
.region_id
,
196 admin_username
=config
.admin_username
,
197 admin_password
=config
.admin_password
,
198 admin_project_name
=config
.admin_project
,
201 def _check_missing_dependencies(self
, config
: ConfigModel
, external_db
: bool):
202 missing_relations
= []
203 if not external_db
and self
.mysql_client
.is_missing_data_in_unit():
204 missing_relations
.append("mysql")
205 if missing_relations
:
206 raise RelationsMissing(missing_relations
)
208 def _generate_keys(self
) -> Tuple
[List
[str], List
[str]]:
209 """Generating new fernet tokens.
212 Tuple[List[str], List[str]]: contains two lists of strings. First
213 list contains strings that represent
214 the keys for fernet and the second
215 list contains strins that represent
216 the keys for credentials.
219 Fernet
.generate_key().decode() for _
in range(NUMBER_FERNET_KEYS
)
222 Fernet
.generate_key().decode() for _
in range(NUMBER_CREDENTIAL_KEYS
)
225 return (fernet_keys
, credential_keys
)
228 keys_timestamp
= self
.state
.keys_timestamp
229 if fernet_keys
:= self
.state
.fernet_keys
:
230 fernet_keys
= json
.loads(fernet_keys
)
232 if credential_keys
:= self
.state
.credential_keys
:
233 credential_keys
= json
.loads(credential_keys
)
235 now
= datetime
.now().timestamp()
236 token_expiration
= self
.config
["token_expiration"]
238 valid_keys
= (now
- keys_timestamp
) < token_expiration
239 if not credential_keys
or not fernet_keys
or not valid_keys
:
240 fernet_keys
, credential_keys
= self
._generate
_keys
()
241 self
.state
.fernet_keys
= json
.dumps(fernet_keys
)
242 self
.state
.credential_keys
= json
.dumps(credential_keys
)
243 self
.state
.keys_timestamp
= now
244 return credential_keys
, fernet_keys
247 self
, config
: ConfigModel
, credential_keys
: List
, fernet_keys
: List
249 credentials_files_builder
= FilesV3Builder()
250 fernet_files_builder
= FilesV3Builder()
251 for (key_id
, _
) in enumerate(credential_keys
):
252 credentials_files_builder
.add_file(str(key_id
), str(key_id
), secret
=True)
253 for (key_id
, _
) in enumerate(fernet_keys
):
254 fernet_files_builder
.add_file(str(key_id
), str(key_id
), secret
=True)
255 return credentials_files_builder
.build(), fernet_files_builder
.build()
257 def build_pod_spec(self
, image_info
, **kwargs
):
259 config
= ConfigModel(**dict(self
.config
))
260 mysql_config
= kwargs
["mysql_config"]
261 config_ldap
= ConfigLdapModel(**dict(self
.config
))
263 if mysql_config
.mysql_uri
and not self
.mysql_client
.is_missing_data_in_unit():
264 raise Exception("Mysql data cannot be provided via config and relation")
266 external_db
= True if mysql_config
.mysql_uri
else False
267 self
._check
_missing
_dependencies
(config
, external_db
)
269 # Create Builder for the PodSpec
270 pod_spec_builder
= PodSpecV3Builder(
271 enable_security_context
=config
.security_context
273 container_builder
= ContainerV3Builder(
276 config
.image_pull_policy
,
277 run_as_non_root
=config
.security_context
,
281 credential_keys
, fernet_keys
= self
._get
_keys
()
282 credential_files
, fernet_files
= self
._build
_files
(
283 config
, credential_keys
, fernet_keys
287 fernet_keys_secret_name
= f
"{self.app.name}-fernet-keys-secret"
288 pod_spec_builder
.add_secret(
289 fernet_keys_secret_name
,
290 {str(key_id
): value
for (key_id
, value
) in enumerate(fernet_keys
)},
292 credential_keys_secret_name
= f
"{self.app.name}-credential-keys-secret"
293 pod_spec_builder
.add_secret(
294 credential_keys_secret_name
,
295 {str(key_id
): value
for (key_id
, value
) in enumerate(credential_keys
)},
297 mysql_secret_name
= f
"{self.app.name}-mysql-secret"
299 pod_spec_builder
.add_secret(
302 "host": mysql_config
.host
,
303 "port": str(mysql_config
.port
),
304 "user": mysql_config
.username
,
305 "password": mysql_config
.password
,
307 if mysql_config
.mysql_uri
309 "host": self
.mysql_client
.host
,
310 "port": str(self
.mysql_client
.port
),
312 "password": self
.mysql_client
.root_password
,
315 keystone_secret_name
= f
"{self.app.name}-keystone-secret"
316 pod_spec_builder
.add_secret(
317 keystone_secret_name
,
319 "db_password": config
.keystone_db_password
,
320 "admin_username": config
.admin_username
,
321 "admin_password": config
.admin_password
,
322 "admin_project": config
.admin_project
,
323 "service_username": config
.service_username
,
324 "service_password": config
.service_password
,
325 "service_project": config
.service_project
,
329 container_builder
.add_volume_config(
331 CREDENTIAL_KEYS_PATH
,
333 secret_name
=credential_keys_secret_name
,
335 container_builder
.add_volume_config(
339 secret_name
=fernet_keys_secret_name
,
341 container_builder
.add_port(name
=self
.app
.name
, port
=PORT
)
342 container_builder
.add_envs(
344 "REGION_ID": config
.region_id
,
345 "KEYSTONE_HOST": self
.app
.name
,
348 container_builder
.add_secret_envs(
349 secret_name
=mysql_secret_name
,
353 "ROOT_DB_USER": "user",
354 "ROOT_DB_PASSWORD": "password",
357 container_builder
.add_secret_envs(
358 secret_name
=keystone_secret_name
,
360 "KEYSTONE_DB_PASSWORD": "db_password",
361 "ADMIN_USERNAME": "admin_username",
362 "ADMIN_PASSWORD": "admin_password",
363 "ADMIN_PROJECT": "admin_project",
364 "SERVICE_USERNAME": "service_username",
365 "SERVICE_PASSWORD": "service_password",
366 "SERVICE_PROJECT": "service_project",
369 ldap_secret_name
= f
"{self.app.name}-ldap-secret"
370 if config_ldap
.ldap_enabled
:
371 # Add ldap secrets and envs
373 "authentication_domain_name": config_ldap
.ldap_authentication_domain_name
,
374 "url": config_ldap
.ldap_url
,
375 "page_size": str(config_ldap
.ldap_page_size
),
376 "user_objectclass": config_ldap
.ldap_user_objectclass
,
377 "user_id_attribute": config_ldap
.ldap_user_id_attribute
,
378 "user_name_attribute": config_ldap
.ldap_user_name_attribute
,
379 "user_pass_attribute": config_ldap
.ldap_user_pass_attribute
,
380 "user_enabled_mask": str(config_ldap
.ldap_user_enabled_mask
),
381 "user_enabled_default": config_ldap
.ldap_user_enabled_default
,
382 "user_enabled_invert": str(config_ldap
.ldap_user_enabled_invert
),
383 "group_objectclass": config_ldap
.ldap_group_objectclass
,
386 "LDAP_AUTHENTICATION_DOMAIN_NAME": "authentication_domain_name",
388 "LDAP_PAGE_SIZE": "page_size",
389 "LDAP_USER_OBJECTCLASS": "user_objectclass",
390 "LDAP_USER_ID_ATTRIBUTE": "user_id_attribute",
391 "LDAP_USER_NAME_ATTRIBUTE": "user_name_attribute",
392 "LDAP_USER_PASS_ATTRIBUTE": "user_pass_attribute",
393 "LDAP_USER_ENABLED_MASK": "user_enabled_mask",
394 "LDAP_USER_ENABLED_DEFAULT": "user_enabled_default",
395 "LDAP_USER_ENABLED_INVERT": "user_enabled_invert",
396 "LDAP_GROUP_OBJECTCLASS": "group_objectclass",
398 if config_ldap
.ldap_bind_user
:
399 ldap_secrets
["bind_user"] = config_ldap
.ldap_bind_user
400 ldap_envs
["LDAP_BIND_USER"] = "bind_user"
402 if config_ldap
.ldap_bind_password
:
403 ldap_secrets
["bind_password"] = config_ldap
.ldap_bind_password
404 ldap_envs
["LDAP_BIND_PASSWORD"] = "bind_password"
406 if config_ldap
.ldap_user_tree_dn
:
407 ldap_secrets
["user_tree_dn"] = config_ldap
.ldap_user_tree_dn
408 ldap_envs
["LDAP_USER_TREE_DN"] = "user_tree_dn"
410 if config_ldap
.ldap_user_filter
:
411 ldap_secrets
["user_filter"] = config_ldap
.ldap_user_filter
412 ldap_envs
["LDAP_USER_FILTER"] = "user_filter"
414 if config_ldap
.ldap_user_enabled_attribute
:
416 "user_enabled_attribute"
417 ] = config_ldap
.ldap_user_enabled_attribute
418 ldap_envs
["LDAP_USER_ENABLED_ATTRIBUTE"] = "user_enabled_attribute"
419 if config_ldap
.ldap_chase_referrals
:
420 ldap_secrets
["chase_referrals"] = config_ldap
.ldap_chase_referrals
421 ldap_envs
["LDAP_CHASE_REFERRALS"] = "chase_referrals"
423 if config_ldap
.ldap_group_tree_dn
:
424 ldap_secrets
["group_tree_dn"] = config_ldap
.ldap_group_tree_dn
425 ldap_envs
["LDAP_GROUP_TREE_DN"] = "group_tree_dn"
427 if config_ldap
.ldap_tls_cacert_base64
:
428 ldap_secrets
["tls_cacert_base64"] = config_ldap
.ldap_tls_cacert_base64
429 ldap_envs
["LDAP_TLS_CACERT_BASE64"] = "tls_cacert_base64"
431 if config_ldap
.ldap_use_starttls
:
432 ldap_secrets
["use_starttls"] = str(config_ldap
.ldap_use_starttls
)
433 ldap_secrets
["tls_cacert_base64"] = config_ldap
.ldap_tls_cacert_base64
434 ldap_secrets
["tls_req_cert"] = config_ldap
.ldap_tls_req_cert
435 ldap_envs
["LDAP_USE_STARTTLS"] = "use_starttls"
436 ldap_envs
["LDAP_TLS_CACERT_BASE64"] = "tls_cacert_base64"
437 ldap_envs
["LDAP_TLS_REQ_CERT"] = "tls_req_cert"
439 pod_spec_builder
.add_secret(
443 container_builder
.add_secret_envs(
444 secret_name
=ldap_secret_name
,
448 container
= container_builder
.build()
450 # Add container to pod spec
451 pod_spec_builder
.add_container(container
)
453 # Add Pod Restart Policy
454 restart_policy
= PodRestartPolicy()
455 restart_policy
.add_secrets(
456 secret_names
=(mysql_secret_name
, keystone_secret_name
, ldap_secret_name
)
458 pod_spec_builder
.set_restart_policy(restart_policy
)
460 # Add ingress resources to pod spec if site url exists
462 parsed
= urlparse(config
.site_url
)
464 "nginx.ingress.kubernetes.io/proxy-body-size": "{}".format(
465 str(config
.max_file_size
) + "m"
466 if config
.max_file_size
> 0
467 else config
.max_file_size
470 if config
.ingress_class
:
471 annotations
["kubernetes.io/ingress.class"] = config
.ingress_class
472 ingress_resource_builder
= IngressResourceV3Builder(
473 f
"{self.app.name}-ingress", annotations
476 if config
.ingress_whitelist_source_range
:
478 "nginx.ingress.kubernetes.io/whitelist-source-range"
479 ] = config
.ingress_whitelist_source_range
481 if parsed
.scheme
== "https":
482 ingress_resource_builder
.add_tls(
483 [parsed
.hostname
], config
.tls_secret_name
486 annotations
["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
488 ingress_resource_builder
.add_rule(parsed
.hostname
, self
.app
.name
, PORT
)
489 ingress_resource
= ingress_resource_builder
.build()
490 pod_spec_builder
.add_ingress_resource(ingress_resource
)
491 return pod_spec_builder
.build()
494 if __name__
== "__main__":
497 # LOGGER = logging.getLogger(__name__)
500 # class ConfigurePodEvent(EventBase):
501 # """Configure Pod event"""
506 # class KeystoneEvents(CharmEvents):
507 # """Keystone Events"""
509 # configure_pod = EventSource(ConfigurePodEvent)
511 # class KeystoneCharm(CharmBase):
512 # """Keystone K8s Charm"""
514 # state = StoredState()
515 # on = KeystoneEvents()
517 # def __init__(self, *args) -> NoReturn:
518 # """Constructor of the Charm object.
519 # Initializes internal state and register events it can handle.
521 # super().__init__(*args)
522 # self.state.set_default(db_host=None)
523 # self.state.set_default(db_port=None)
524 # self.state.set_default(db_user=None)
525 # self.state.set_default(db_password=None)
526 # self.state.set_default(pod_spec=None)
527 # self.state.set_default(fernet_keys=None)
528 # self.state.set_default(credential_keys=None)
529 # self.state.set_default(keys_timestamp=0)
531 # # Register all of the events we want to observe
532 # self.framework.observe(self.on.config_changed, self.configure_pod)
533 # self.framework.observe(self.on.start, self.configure_pod)
534 # self.framework.observe(self.on.upgrade_charm, self.configure_pod)
535 # self.framework.observe(self.on.leader_elected, self.configure_pod)
536 # self.framework.observe(self.on.update_status, self.configure_pod)
538 # # Registering custom internal events
539 # self.framework.observe(self.on.configure_pod, self.configure_pod)
541 # # Register relation events
542 # self.framework.observe(
543 # self.on.db_relation_changed, self._on_db_relation_changed
545 # self.framework.observe(
546 # self.on.db_relation_broken, self._on_db_relation_broken
548 # self.framework.observe(
549 # self.on.keystone_relation_joined, self._publish_keystone_info
552 # def _publish_keystone_info(self, event: EventBase) -> NoReturn:
553 # """Publishes keystone information for NBI usage through the keystone
557 # event (EventBase): Keystone relation event to update NBI.
559 # config = self.model.config
561 # "host": f"http://{self.app.name}:{KEYSTONE_PORT}/v3",
562 # "port": str(KEYSTONE_PORT),
563 # "keystone_db_password": config["keystone_db_password"],
564 # "region_id": config["region_id"],
565 # "user_domain_name": config["user_domain_name"],
566 # "project_domain_name": config["project_domain_name"],
567 # "admin_username": config["admin_username"],
568 # "admin_password": config["admin_password"],
569 # "admin_project_name": config["admin_project"],
570 # "username": config["service_username"],
571 # "password": config["service_password"],
572 # "service": config["service_project"],
574 # for k, v in rel_data.items():
575 # event.relation.data[self.model.unit][k] = v
577 # def _on_db_relation_changed(self, event: EventBase) -> NoReturn:
578 # """Reads information about the DB relation, in order for keystone to
582 # event (EventBase): DB relation event to access database
585 # if not event.unit in event.relation.data:
587 # relation_data = event.relation.data[event.unit]
588 # db_host = relation_data.get("host")
589 # db_port = int(relation_data.get("port", 3306))
591 # db_password = relation_data.get("root_password")
599 # self.state.db_host != db_host
600 # or self.state.db_port != db_port
601 # or self.state.db_user != db_user
602 # or self.state.db_password != db_password
605 # self.state.db_host = db_host
606 # self.state.db_port = db_port
607 # self.state.db_user = db_user
608 # self.state.db_password = db_password
609 # self.on.configure_pod.emit()
612 # def _on_db_relation_broken(self, event: EventBase) -> NoReturn:
613 # """Clears data from db relation.
616 # event (EventBase): DB relation event.
619 # self.state.db_host = None
620 # self.state.db_port = None
621 # self.state.db_user = None
622 # self.state.db_password = None
623 # self.on.configure_pod.emit()
625 # def _check_settings(self) -> str:
626 # """Check if there any settings missing from Keystone configuration.
629 # str: Information about the problems found (if any).
632 # config = self.model.config
634 # for setting in REQUIRED_SETTINGS:
635 # if not config.get(setting):
636 # problem = f"missing config {setting}"
637 # problems.append(problem)
639 # return ";".join(problems)
641 # def _make_pod_image_details(self) -> Dict[str, str]:
642 # """Generate the pod image details.
645 # Dict[str, str]: pod image details.
647 # config = self.model.config
649 # "imagePath": config["image"],
651 # if config["image_username"]:
652 # image_details.update(
654 # "username": config["image_username"],
655 # "password": config["image_password"],
658 # return image_details
660 # def _make_pod_ports(self) -> List[Dict[str, Any]]:
661 # """Generate the pod ports details.
664 # List[Dict[str, Any]]: pod ports details.
667 # {"name": "keystone", "containerPort": KEYSTONE_PORT, "protocol": "TCP"},
670 # def _make_pod_envconfig(self) -> Dict[str, Any]:
671 # """Generate pod environment configuraiton.
674 # Dict[str, Any]: pod environment configuration.
676 # config = self.model.config
679 # "DB_HOST": self.state.db_host,
680 # "DB_PORT": self.state.db_port,
681 # "ROOT_DB_USER": self.state.db_user,
682 # "ROOT_DB_PASSWORD": self.state.db_password,
683 # "KEYSTONE_DB_PASSWORD": config["keystone_db_password"],
684 # "REGION_ID": config["region_id"],
685 # "KEYSTONE_HOST": self.app.name,
686 # "ADMIN_USERNAME": config["admin_username"],
687 # "ADMIN_PASSWORD": config["admin_password"],
688 # "ADMIN_PROJECT": config["admin_project"],
689 # "SERVICE_USERNAME": config["service_username"],
690 # "SERVICE_PASSWORD": config["service_password"],
691 # "SERVICE_PROJECT": config["service_project"],
694 # if config.get("ldap_enabled"):
695 # envconfig["LDAP_AUTHENTICATION_DOMAIN_NAME"] = config[
696 # "ldap_authentication_domain_name"
698 # envconfig["LDAP_URL"] = config["ldap_url"]
699 # envconfig["LDAP_PAGE_SIZE"] = config["ldap_page_size"]
700 # envconfig["LDAP_USER_OBJECTCLASS"] = config["ldap_user_objectclass"]
701 # envconfig["LDAP_USER_ID_ATTRIBUTE"] = config["ldap_user_id_attribute"]
702 # envconfig["LDAP_USER_NAME_ATTRIBUTE"] = config["ldap_user_name_attribute"]
703 # envconfig["LDAP_USER_PASS_ATTRIBUTE"] = config["ldap_user_pass_attribute"]
704 # envconfig["LDAP_USER_ENABLED_MASK"] = config["ldap_user_enabled_mask"]
705 # envconfig["LDAP_USER_ENABLED_DEFAULT"] = config["ldap_user_enabled_default"]
706 # envconfig["LDAP_USER_ENABLED_INVERT"] = config["ldap_user_enabled_invert"]
707 # envconfig["LDAP_GROUP_OBJECTCLASS"] = config["ldap_group_objectclass"]
709 # if config["ldap_bind_user"]:
710 # envconfig["LDAP_BIND_USER"] = config["ldap_bind_user"]
712 # if config["ldap_bind_password"]:
713 # envconfig["LDAP_BIND_PASSWORD"] = config["ldap_bind_password"]
715 # if config["ldap_user_tree_dn"]:
716 # envconfig["LDAP_USER_TREE_DN"] = config["ldap_user_tree_dn"]
718 # if config["ldap_user_filter"]:
719 # envconfig["LDAP_USER_FILTER"] = config["ldap_user_filter"]
721 # if config["ldap_user_enabled_attribute"]:
722 # envconfig["LDAP_USER_ENABLED_ATTRIBUTE"] = config[
723 # "ldap_user_enabled_attribute"
726 # if config["ldap_chase_referrals"]:
727 # envconfig["LDAP_CHASE_REFERRALS"] = config["ldap_chase_referrals"]
729 # if config["ldap_group_tree_dn"]:
730 # envconfig["LDAP_GROUP_TREE_DN"] = config["ldap_group_tree_dn"]
732 # if config["ldap_use_starttls"]:
733 # envconfig["LDAP_USE_STARTTLS"] = config["ldap_use_starttls"]
734 # envconfig["LDAP_TLS_CACERT_BASE64"] = config["ldap_tls_cacert_base64"]
735 # envconfig["LDAP_TLS_REQ_CERT"] = config["ldap_tls_req_cert"]
739 # def _make_pod_ingress_resources(self) -> List[Dict[str, Any]]:
740 # """Generate pod ingress resources.
743 # List[Dict[str, Any]]: pod ingress resources.
745 # site_url = self.model.config["site_url"]
750 # parsed = urlparse(site_url)
752 # if not parsed.scheme.startswith("http"):
755 # max_file_size = self.model.config["max_file_size"]
756 # ingress_whitelist_source_range = self.model.config[
757 # "ingress_whitelist_source_range"
761 # "nginx.ingress.kubernetes.io/proxy-body-size": "{}m".format(max_file_size)
764 # if ingress_whitelist_source_range:
766 # "nginx.ingress.kubernetes.io/whitelist-source-range"
767 # ] = ingress_whitelist_source_range
769 # ingress_spec_tls = None
771 # if parsed.scheme == "https":
772 # ingress_spec_tls = [{"hosts": [parsed.hostname]}]
773 # tls_secret_name = self.model.config["tls_secret_name"]
774 # if tls_secret_name:
775 # ingress_spec_tls[0]["secretName"] = tls_secret_name
777 # annotations["nginx.ingress.kubernetes.io/ssl-redirect"] = "false"
780 # "name": "{}-ingress".format(self.app.name),
781 # "annotations": annotations,
785 # "host": parsed.hostname,
791 # "serviceName": self.app.name,
792 # "servicePort": KEYSTONE_PORT,
801 # if ingress_spec_tls:
802 # ingress["spec"]["tls"] = ingress_spec_tls
806 # def _generate_keys(self) -> Tuple[List[str], List[str]]:
807 # """Generating new fernet tokens.
810 # Tuple[List[str], List[str]]: contains two lists of strings. First
811 # list contains strings that represent
812 # the keys for fernet and the second
813 # list contains strins that represent
814 # the keys for credentials.
817 # Fernet.generate_key().decode() for _ in range(NUMBER_FERNET_KEYS)
819 # credential_keys = [
820 # Fernet.generate_key().decode() for _ in range(NUMBER_CREDENTIAL_KEYS)
823 # return (fernet_keys, credential_keys)
825 # def configure_pod(self, event: EventBase) -> NoReturn:
826 # """Assemble the pod spec and apply it, if possible.
829 # event (EventBase): Hook or Relation event that started the
832 # if not self.state.db_host:
833 # self.unit.status = WaitingStatus("Waiting for database relation")
837 # if not self.unit.is_leader():
838 # self.unit.status = ActiveStatus("ready")
841 # if fernet_keys := self.state.fernet_keys:
842 # fernet_keys = json.loads(fernet_keys)
844 # if credential_keys := self.state.credential_keys:
845 # credential_keys = json.loads(credential_keys)
847 # now = datetime.now().timestamp()
848 # keys_timestamp = self.state.keys_timestamp
849 # token_expiration = self.model.config["token_expiration"]
851 # valid_keys = (now - keys_timestamp) < token_expiration
852 # if not credential_keys or not fernet_keys or not valid_keys:
853 # fernet_keys, credential_keys = self._generate_keys()
854 # self.state.fernet_keys = json.dumps(fernet_keys)
855 # self.state.credential_keys = json.dumps(credential_keys)
856 # self.state.keys_timestamp = now
858 # # Check problems in the settings
859 # problems = self._check_settings()
861 # self.unit.status = BlockedStatus(problems)
864 # self.unit.status = BlockedStatus("Assembling pod spec")
865 # image_details = self._make_pod_image_details()
866 # ports = self._make_pod_ports()
867 # env_config = self._make_pod_envconfig()
868 # ingress_resources = self._make_pod_ingress_resources()
869 # files = self._make_pod_files(fernet_keys, credential_keys)
875 # "name": self.framework.model.app.name,
876 # "imageDetails": image_details,
878 # "envConfig": env_config,
879 # "volumeConfig": files,
882 # "kubernetesResources": {"ingressResources": ingress_resources or []},
885 # if self.state.pod_spec != (
886 # pod_spec_json := json.dumps(pod_spec, sort_keys=True)
888 # self.state.pod_spec = pod_spec_json
889 # self.model.pod.set_spec(pod_spec)
891 # self.unit.status = ActiveStatus("ready")
894 # if __name__ == "__main__":
895 # main(KeystoneCharm)