olas-operate-middleware 0.7.0__py3-none-any.whl → 0.8.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
operate/operate_types.py CHANGED
@@ -329,3 +329,13 @@ class AssetFundingValues(TypedDict):
329
329
 
330
330
 
331
331
  FundingValues = t.Dict[str, AssetFundingValues] # str is the asset address
332
+
333
+
334
+ @dataclass
335
+ class MechMarketplaceConfig:
336
+ """Mech Marketplace config."""
337
+
338
+ use_mech_marketplace: bool
339
+ mech_marketplace_address: str
340
+ priority_mech_address: str
341
+ priority_mech_service_id: int
@@ -73,7 +73,7 @@ def claim_staking_rewards(operate: "OperateApp", config_path: str) -> None:
73
73
  config = configure_local_config(template, operate)
74
74
  manager = operate.service_manager()
75
75
  service = get_service(manager, template)
76
- ask_password_if_needed(operate, config)
76
+ ask_password_if_needed(operate)
77
77
 
78
78
  # reload manger and config after setting operate.password
79
79
  manager = operate.service_manager()
@@ -82,7 +82,7 @@ def reset_staking(operate: "OperateApp", config_path: str) -> None:
82
82
  print("Cancelled.")
83
83
  return
84
84
 
85
- ask_password_if_needed(operate, config)
85
+ ask_password_if_needed(operate)
86
86
  manager = operate.service_manager()
87
87
  service = get_service(manager, template)
88
88
 
@@ -37,14 +37,7 @@ from operate.account.user import UserAccount
37
37
  from operate.constants import IPFS_ADDRESS, OPERATE_HOME
38
38
  from operate.data import DATA_DIR
39
39
  from operate.data.contracts.staking_token.contract import StakingTokenContract
40
- from operate.ledger.profiles import (
41
- DEFAULT_PRIORITY_MECH_ADDRESS,
42
- DEFAULT_PRIORITY_MECH_SERVICE_ID,
43
- NO_STAKING_PROGRAM_ID,
44
- STAKING,
45
- get_staking_contract,
46
- get_staking_program_mech_type,
47
- )
40
+ from operate.ledger.profiles import NO_STAKING_PROGRAM_ID, STAKING, get_staking_contract
48
41
  from operate.operate_types import (
49
42
  Chain,
50
43
  LedgerType,
@@ -208,9 +201,6 @@ def configure_local_config(
208
201
  )
209
202
  os.environ[f"{chain.upper()}_LEDGER_RPC"] = config.rpc[chain]
210
203
 
211
- if config.password_migrated is None:
212
- config.password_migrated = False
213
-
214
204
  config.principal_chain = template["home_chain"]
215
205
 
216
206
  home_chain = Chain.from_string(config.principal_chain)
@@ -396,15 +386,18 @@ def configure_local_config(
396
386
  ):
397
387
  print_section("Please enter the arguments that will be used by the service.")
398
388
 
399
- staking_program_mech_type = get_staking_program_mech_type(config.staking_program_id)
389
+ service_manager = operate.service_manager()
390
+ mech_configs = service_manager.get_mech_configs(
391
+ chain=config.principal_chain,
392
+ ledger_api=ledger_api,
393
+ staking_program_id=config.staking_program_id,
394
+ )
400
395
 
401
396
  for env_var_name, env_var_data in template["env_variables"].items():
402
397
  if env_var_data["provision_type"] == ServiceEnvProvisionType.USER:
403
398
  # PRIORITY_MECH_ADDRESS and PRIORITY_MECH_SERVICE_ID are given dynamic default values
404
399
  if env_var_name == "PRIORITY_MECH_ADDRESS":
405
- env_var_data["value"] = DEFAULT_PRIORITY_MECH_ADDRESS[
406
- staking_program_mech_type
407
- ]
400
+ env_var_data["value"] = mech_configs.priority_mech_address
408
401
  if (
409
402
  env_var_name in config.user_provided_args
410
403
  and env_var_data["value"] != config.user_provided_args[env_var_name]
@@ -412,9 +405,7 @@ def configure_local_config(
412
405
  del config.user_provided_args[env_var_name]
413
406
 
414
407
  if env_var_name == "PRIORITY_MECH_SERVICE_ID":
415
- env_var_data["value"] = str(
416
- DEFAULT_PRIORITY_MECH_SERVICE_ID.get(staking_program_mech_type, 0)
417
- )
408
+ env_var_data["value"] = mech_configs.priority_mech_service_id
418
409
  if (
419
410
  env_var_name in config.user_provided_args
420
411
  and env_var_data["value"] != config.user_provided_args[env_var_name]
@@ -456,7 +447,7 @@ def configure_local_config(
456
447
  return config
457
448
 
458
449
 
459
- def ask_password_if_needed(operate: "OperateApp", config: QuickstartConfig) -> None:
450
+ def ask_password_if_needed(operate: "OperateApp") -> None:
460
451
  """Ask password if needed."""
461
452
  if operate.user_account is None:
462
453
  print_section("Set up local user account")
@@ -466,8 +457,6 @@ def ask_password_if_needed(operate: "OperateApp", config: QuickstartConfig) -> N
466
457
  password=password,
467
458
  path=operate._path / "user.json",
468
459
  )
469
- config.password_migrated = True
470
- config.store()
471
460
  else:
472
461
  _password = None
473
462
  while _password is None:
@@ -620,8 +609,8 @@ def _ask_funds_from_requirements(
620
609
  return False
621
610
 
622
611
 
623
- def ensure_enough_funds(operate: "OperateApp", service: Service) -> None:
624
- """Ensure enough funds."""
612
+ def _maybe_create_master_eoa(operate: "OperateApp") -> None:
613
+ """Maybe create the Master EOA."""
625
614
  if not operate.wallet_manager.exists(ledger_type=LedgerType.ETHEREUM):
626
615
  print("Creating the Master EOA...")
627
616
  wallet, mnemonic = operate.wallet_manager.create(
@@ -636,9 +625,12 @@ def ensure_enough_funds(operate: "OperateApp", service: Service) -> None:
636
625
  ask_or_get_from_env(
637
626
  "Press enter to continue...", False, "CONTINUE", raise_if_missing=False
638
627
  )
639
- else:
640
- wallet = operate.wallet_manager.load(ledger_type=LedgerType.ETHEREUM)
641
628
 
629
+
630
+ def ensure_enough_funds(operate: "OperateApp", service: Service) -> None:
631
+ """Ensure enough funds."""
632
+ _maybe_create_master_eoa(operate)
633
+ wallet = operate.wallet_manager.load(ledger_type=LedgerType.ETHEREUM)
642
634
  manager = operate.service_manager()
643
635
 
644
636
  backup_owner = None
@@ -677,11 +669,12 @@ def run_service(
677
669
 
678
670
  operate.service_manager().migrate_service_configs()
679
671
  operate.wallet_manager.migrate_wallet_configs()
672
+ ask_password_if_needed(operate)
673
+ _maybe_create_master_eoa(operate)
680
674
 
681
675
  config = configure_local_config(template, operate)
682
676
  manager = operate.service_manager()
683
677
  service = get_service(manager, template)
684
- ask_password_if_needed(operate, config)
685
678
 
686
679
  # reload manger and config after setting operate.password
687
680
  manager = operate.service_manager(skip_dependency_check=skip_dependency_check)
@@ -62,7 +62,7 @@ def terminate_service(operate: "OperateApp", config_path: str) -> None:
62
62
  return
63
63
 
64
64
  config = configure_local_config(template, operate)
65
- ask_password_if_needed(operate, config)
65
+ ask_password_if_needed(operate)
66
66
  manager = operate.service_manager()
67
67
  service = get_service(manager, template)
68
68
  ensure_enough_funds(operate, service)
@@ -277,7 +277,6 @@ class QuickstartConfig(LocalResource):
277
277
 
278
278
  path: Path
279
279
  rpc: Optional[Dict[str, str]] = None
280
- password_migrated: Optional[bool] = None
281
280
  staking_program_id: Optional[str] = None
282
281
  principal_chain: Optional[str] = None
283
282
  user_provided_args: Optional[Dict[str, str]] = None
@@ -71,7 +71,7 @@ AGENTS_SUPPORTED = {
71
71
  owner="valory-xyz", repo="optimus", release="v0.0.103"
72
72
  ),
73
73
  "dvilela/memeooorr": AgentRelease(
74
- owner="valory-xyz", repo="meme-ooorr-test", release="v0.0.101"
74
+ owner="valory-xyz", repo="meme-ooorr", release="v0.0.101"
75
75
  ),
76
76
  }
77
77
 
@@ -36,7 +36,7 @@ from pathlib import Path
36
36
  import requests
37
37
  from aea.helpers.base import IPFSHash
38
38
  from aea.helpers.logging import setup_logger
39
- from aea_ledger_ethereum import EthereumCrypto
39
+ from aea_ledger_ethereum import EthereumCrypto, LedgerApi
40
40
  from autonomy.chain.base import registry_contracts
41
41
  from autonomy.chain.config import CHAIN_PROFILES, ChainType
42
42
 
@@ -52,19 +52,18 @@ from operate.ledger import PUBLIC_RPCS, get_currency_denom
52
52
  from operate.ledger.profiles import (
53
53
  CONTRACTS,
54
54
  DEFAULT_MASTER_EOA_FUNDS,
55
- DEFAULT_PRIORITY_MECH_ADDRESS,
56
- DEFAULT_PRIORITY_MECH_SERVICE_ID,
55
+ DEFAULT_PRIORITY_MECH,
57
56
  OLAS,
58
57
  STAKING,
59
58
  USDC,
60
59
  WRAPPED_NATIVE_ASSET,
61
60
  get_staking_contract,
62
- get_staking_program_mech_type,
63
61
  )
64
62
  from operate.operate_types import (
65
63
  Chain,
66
64
  FundingValues,
67
65
  LedgerConfig,
66
+ MechMarketplaceConfig,
68
67
  OnChainState,
69
68
  ServiceEnvProvisionType,
70
69
  ServiceTemplate,
@@ -520,6 +519,103 @@ class ServiceManager:
520
519
  chain=chain,
521
520
  )
522
521
 
522
+ def get_mech_configs(
523
+ self,
524
+ chain: str,
525
+ ledger_api: LedgerApi,
526
+ staking_program_id: str | None = None,
527
+ ) -> MechMarketplaceConfig:
528
+ """Get the mech configs."""
529
+ sftxb = self.get_eth_safe_tx_builder(
530
+ ledger_config=LedgerConfig(
531
+ chain=Chain(chain),
532
+ rpc=ledger_api.api.provider.endpoint_uri,
533
+ )
534
+ )
535
+ staking_contract = get_staking_contract(
536
+ chain=chain,
537
+ staking_program_id=staking_program_id,
538
+ )
539
+ if staking_contract is None:
540
+ return MechMarketplaceConfig(
541
+ use_mech_marketplace=False,
542
+ mech_marketplace_address=ZERO_ADDRESS,
543
+ priority_mech_address=ZERO_ADDRESS,
544
+ priority_mech_service_id=0,
545
+ )
546
+
547
+ target_staking_params = sftxb.get_staking_params(
548
+ staking_contract=get_staking_contract(
549
+ chain=chain,
550
+ staking_program_id=staking_program_id,
551
+ ),
552
+ )
553
+
554
+ try:
555
+ # Try if activity checker is a MechActivityChecker contract
556
+ mech_activity_contract = t.cast(
557
+ MechActivityContract,
558
+ MechActivityContract.from_dir(
559
+ directory=str(DATA_DIR / "contracts" / "mech_activity")
560
+ ),
561
+ )
562
+
563
+ priority_mech_address = (
564
+ mech_activity_contract.get_instance(
565
+ ledger_api=ledger_api,
566
+ contract_address=target_staking_params["activity_checker"],
567
+ )
568
+ .functions.agentMech()
569
+ .call()
570
+ )
571
+ use_mech_marketplace = False
572
+ mech_marketplace_address = ZERO_ADDRESS
573
+ priority_mech_service_id = 0
574
+
575
+ except Exception: # pylint: disable=broad-except
576
+ # Try if activity checker is a RequesterActivityChecker contract
577
+ try:
578
+ requester_activity_checker = t.cast(
579
+ RequesterActivityCheckerContract,
580
+ RequesterActivityCheckerContract.from_dir(
581
+ directory=str(
582
+ DATA_DIR / "contracts" / "requester_activity_checker"
583
+ )
584
+ ),
585
+ )
586
+
587
+ mech_marketplace_address = (
588
+ requester_activity_checker.get_instance(
589
+ ledger_api=ledger_api,
590
+ contract_address=target_staking_params["activity_checker"],
591
+ )
592
+ .functions.mechMarketplace()
593
+ .call()
594
+ )
595
+
596
+ use_mech_marketplace = True
597
+ priority_mech_address, priority_mech_service_id = DEFAULT_PRIORITY_MECH[
598
+ mech_marketplace_address
599
+ ]
600
+
601
+ except Exception as e: # pylint: disable=broad-except
602
+ self.logger.error(f"{e}: {traceback.format_exc()}")
603
+ self.logger.warning(
604
+ "Cannot determine type of activity checker contract. Using default parameters. "
605
+ "NOTE: This will be an exception in the future!"
606
+ )
607
+ priority_mech_address = "0x77af31De935740567Cf4fF1986D04B2c964A786a"
608
+ use_mech_marketplace = False
609
+ mech_marketplace_address = ZERO_ADDRESS
610
+ priority_mech_service_id = 0
611
+
612
+ return MechMarketplaceConfig(
613
+ use_mech_marketplace=use_mech_marketplace,
614
+ mech_marketplace_address=mech_marketplace_address,
615
+ priority_mech_address=priority_mech_address,
616
+ priority_mech_service_id=priority_mech_service_id,
617
+ )
618
+
523
619
  def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,too-many-locals
524
620
  self,
525
621
  service_config_id: str,
@@ -556,10 +652,6 @@ class ServiceManager:
556
652
  self.logger.info(f"Service state: {on_chain_state.name}")
557
653
 
558
654
  current_staking_program = self._get_current_staking_program(service, chain)
559
- staking_program_mech_type = get_staking_program_mech_type(
560
- user_params.staking_program_id
561
- )
562
- self.logger.info(f"{staking_program_mech_type=}")
563
655
  fallback_params = dict( # nosec
564
656
  staking_contract=NULL_ADDRESS,
565
657
  agent_ids=[user_params.agent_id],
@@ -588,94 +680,29 @@ class ServiceManager:
588
680
  # TODO A customized, arbitrary computation mechanism should be devised.
589
681
  env_var_to_value = {}
590
682
  if chain == service.home_chain:
591
- # Try if activity checker is a MechActivityChecker contract
592
- try:
593
- mech_activity_contract = t.cast(
594
- MechActivityContract,
595
- MechActivityContract.from_dir(
596
- directory=str(DATA_DIR / "contracts" / "mech_activity")
597
- ),
598
- )
599
-
600
- agent_mech = (
601
- mech_activity_contract.get_instance(
602
- ledger_api=sftxb.ledger_api,
603
- contract_address=target_staking_params["activity_checker"],
604
- )
605
- .functions.agentMech()
606
- .call()
607
- )
608
- use_mech_marketplace = False
609
- mech_marketplace_address = ZERO_ADDRESS
610
- priority_mech_address = ZERO_ADDRESS
611
- priority_mech_service_id = DEFAULT_PRIORITY_MECH_SERVICE_ID.get(
612
- staking_program_mech_type, 0
613
- )
614
-
615
- except Exception: # pylint: disable=broad-except
616
- # Try if activity checker is a RequesterActivityChecker contract
617
- try:
618
- requester_activity_checker = t.cast(
619
- RequesterActivityCheckerContract,
620
- RequesterActivityCheckerContract.from_dir(
621
- directory=str(
622
- DATA_DIR / "contracts" / "requester_activity_checker"
623
- )
624
- ),
625
- )
626
-
627
- mech_marketplace_address = (
628
- requester_activity_checker.get_instance(
629
- ledger_api=sftxb.ledger_api,
630
- contract_address=target_staking_params["activity_checker"],
631
- )
632
- .functions.mechMarketplace()
633
- .call()
634
- )
683
+ mech_configs: MechMarketplaceConfig = self.get_mech_configs(
684
+ chain=chain,
685
+ ledger_api=sftxb.ledger_api,
686
+ staking_program_id=user_params.staking_program_id,
687
+ )
635
688
 
636
- use_mech_marketplace = True
637
- if (
638
- "PRIORITY_MECH_ADDRESS" in service.env_variables
639
- and service.env_variables["PRIORITY_MECH_ADDRESS"][
640
- "provision_type"
641
- ]
642
- == ServiceEnvProvisionType.USER
643
- ):
644
- agent_mech = priority_mech_address = service.env_variables[
645
- "PRIORITY_MECH_ADDRESS"
646
- ]["value"]
647
- else:
648
- agent_mech = (
649
- priority_mech_address
650
- ) = DEFAULT_PRIORITY_MECH_ADDRESS[staking_program_mech_type]
651
-
652
- if (
653
- "PRIORITY_MECH_SERVICE_ID" in service.env_variables
654
- and service.env_variables["PRIORITY_MECH_SERVICE_ID"][
655
- "provision_type"
656
- ]
657
- == ServiceEnvProvisionType.USER
658
- ):
659
- priority_mech_service_id = service.env_variables[
660
- "PRIORITY_MECH_SERVICE_ID"
661
- ]["value"]
662
- else:
663
- priority_mech_service_id = DEFAULT_PRIORITY_MECH_SERVICE_ID.get(
664
- staking_program_mech_type, 0
665
- )
689
+ if (
690
+ "PRIORITY_MECH_ADDRESS" in service.env_variables
691
+ and service.env_variables["PRIORITY_MECH_ADDRESS"]["provision_type"]
692
+ == ServiceEnvProvisionType.USER
693
+ ):
694
+ mech_configs.priority_mech_address = service.env_variables[
695
+ "PRIORITY_MECH_ADDRESS"
696
+ ]["value"]
666
697
 
667
- except Exception: # pylint: disable=broad-except
668
- self.logger.warning(
669
- "Cannot determine type of activity checker contract. Using default parameters. "
670
- "NOTE: This will be an exception in the future!"
671
- )
672
- agent_mech = DEFAULT_PRIORITY_MECH_ADDRESS[
673
- staking_program_mech_type
674
- ]
675
- use_mech_marketplace = False
676
- mech_marketplace_address = ZERO_ADDRESS
677
- priority_mech_address = ZERO_ADDRESS
678
- priority_mech_service_id = 0
698
+ if (
699
+ "PRIORITY_MECH_SERVICE_ID" in service.env_variables
700
+ and service.env_variables["PRIORITY_MECH_SERVICE_ID"]["provision_type"]
701
+ == ServiceEnvProvisionType.USER
702
+ ):
703
+ mech_configs.priority_mech_service_id = service.env_variables[
704
+ "PRIORITY_MECH_SERVICE_ID"
705
+ ]["value"]
679
706
 
680
707
  env_var_to_value.update(
681
708
  {
@@ -693,10 +720,10 @@ class ServiceManager:
693
720
  "staking_contract"
694
721
  ),
695
722
  "MECH_MARKETPLACE_CONFIG": (
696
- f'{{"mech_marketplace_address":"{mech_marketplace_address}",'
697
- f'"priority_mech_address":"{priority_mech_address}",'
723
+ f'{{"mech_marketplace_address":"{mech_configs.mech_marketplace_address}",'
724
+ f'"priority_mech_address":"{mech_configs.priority_mech_address}",'
698
725
  f'"priority_mech_staking_instance_address":"0x998dEFafD094817EF329f6dc79c703f1CF18bC90",'
699
- f'"priority_mech_service_id":{priority_mech_service_id},'
726
+ f'"priority_mech_service_id":{mech_configs.priority_mech_service_id},'
700
727
  f'"requester_staking_instance_address":"{target_staking_params.get("staking_contract")}",'
701
728
  f'"response_timeout":300}}'
702
729
  ),
@@ -706,9 +733,9 @@ class ServiceManager:
706
733
  "MECH_ACTIVITY_CHECKER_CONTRACT": target_staking_params.get(
707
734
  "activity_checker"
708
735
  ),
709
- "MECH_CONTRACT_ADDRESS": agent_mech,
736
+ "MECH_CONTRACT_ADDRESS": mech_configs.priority_mech_address,
710
737
  "MECH_REQUEST_PRICE": "10000000000000000",
711
- "USE_MECH_MARKETPLACE": use_mech_marketplace,
738
+ "USE_MECH_MARKETPLACE": mech_configs.use_mech_marketplace,
712
739
  }
713
740
  )
714
741
 
@@ -88,6 +88,7 @@ from operate.operate_types import (
88
88
  from operate.resource import LocalResource
89
89
  from operate.services.deployment_runner import run_host_deployment, stop_host_deployment
90
90
  from operate.services.utils import tendermint
91
+ from operate.utils.ssl import create_ssl_certificate
91
92
 
92
93
 
93
94
  # pylint: disable=no-member,redefined-builtin,too-many-instance-attributes,too-many-locals
@@ -686,13 +687,35 @@ class Deployment(LocalResource):
686
687
  service = Service.load(path=self.path)
687
688
 
688
689
  if use_docker or use_kubernetes:
689
- service.update_env_variables_values({"STORE_PATH": "/data"})
690
+ ssl_key_path, ssl_cert_path = create_ssl_certificate(
691
+ ssl_dir=service.path / PERSISTENT_DATA_DIR / "ssl"
692
+ )
693
+ service.update_env_variables_values(
694
+ {
695
+ "STORE_PATH": "/data",
696
+ "SSL_KEY_PATH": (
697
+ Path("/data") / "ssl" / ssl_key_path.name
698
+ ).as_posix(),
699
+ "SSL_CERT_PATH": (
700
+ Path("/data") / "ssl" / ssl_cert_path.name
701
+ ).as_posix(),
702
+ }
703
+ )
690
704
  service.consume_env_variables()
691
705
  if use_docker:
692
706
  self._build_docker(force=force, chain=chain)
693
707
  if use_kubernetes:
694
708
  self._build_kubernetes(force=force)
695
709
  else:
710
+ ssl_key_path, ssl_cert_path = create_ssl_certificate(
711
+ ssl_dir=service.path / DEPLOYMENT / "ssl"
712
+ )
713
+ service.update_env_variables_values(
714
+ {
715
+ "SSL_KEY_PATH": str(ssl_key_path),
716
+ "SSL_CERT_PATH": str(ssl_cert_path),
717
+ }
718
+ )
696
719
  service.consume_env_variables()
697
720
  self._build_host(force=force, chain=chain)
698
721
 
operate/utils/ssl.py ADDED
@@ -0,0 +1,133 @@
1
+ # -*- coding: utf-8 -*-
2
+ # ------------------------------------------------------------------------------
3
+ #
4
+ # Copyright 2025 Valory AG
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # ------------------------------------------------------------------------------
19
+
20
+ """SSL certificate utilities."""
21
+
22
+ import datetime
23
+ import logging
24
+ import typing as t
25
+ from pathlib import Path
26
+
27
+ from cryptography import x509
28
+ from cryptography.hazmat.primitives import hashes, serialization
29
+ from cryptography.hazmat.primitives.asymmetric import rsa
30
+ from cryptography.x509.oid import NameOID
31
+
32
+
33
+ def create_ssl_certificate(
34
+ ssl_dir: Path,
35
+ key_filename: str = "key.pem",
36
+ cert_filename: str = "cert.pem",
37
+ validity_days: int = 365,
38
+ key_size: int = 2048,
39
+ common_name: str = "localhost",
40
+ ) -> t.Tuple[Path, Path]:
41
+ """
42
+ Create SSL certificate and private key files.
43
+
44
+ Args:
45
+ ssl_dir: Path to the ssl directory
46
+ key_filename: Name of the private key file
47
+ cert_filename: Name of the certificate file
48
+ validity_days: Number of days the certificate is valid
49
+ key_size: RSA key size in bits
50
+ common_name: Common name for the certificate
51
+
52
+ Returns:
53
+ Tuple of (key_path, cert_path) as Path objects
54
+ """
55
+ logger = logging.getLogger(__name__)
56
+
57
+ # Create SSL directory
58
+ ssl_dir.mkdir(parents=True, exist_ok=True)
59
+
60
+ key_path = ssl_dir / key_filename
61
+ cert_path = ssl_dir / cert_filename
62
+
63
+ # Generate RSA private key
64
+ private_key = rsa.generate_private_key(
65
+ public_exponent=65537,
66
+ key_size=key_size,
67
+ )
68
+
69
+ # Create certificate subject and issuer
70
+ subject = issuer = x509.Name(
71
+ [
72
+ x509.NameAttribute(NameOID.COUNTRY_NAME, "CH"),
73
+ x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "Local"),
74
+ x509.NameAttribute(NameOID.LOCALITY_NAME, "Local"),
75
+ x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Valory AG"),
76
+ x509.NameAttribute(NameOID.COMMON_NAME, common_name),
77
+ ]
78
+ )
79
+
80
+ # Create certificate
81
+ cert = (
82
+ x509.CertificateBuilder()
83
+ .subject_name(subject)
84
+ .issuer_name(issuer)
85
+ .public_key(private_key.public_key())
86
+ .serial_number(1)
87
+ .not_valid_before(datetime.datetime.now(datetime.timezone.utc))
88
+ .not_valid_after(
89
+ datetime.datetime.now(datetime.timezone.utc)
90
+ + datetime.timedelta(days=validity_days)
91
+ )
92
+ .add_extension(
93
+ x509.BasicConstraints(ca=False, path_length=None),
94
+ critical=True,
95
+ )
96
+ .add_extension(
97
+ x509.KeyUsage(
98
+ digital_signature=True,
99
+ key_encipherment=True,
100
+ key_agreement=False,
101
+ key_cert_sign=False,
102
+ crl_sign=False,
103
+ content_commitment=False,
104
+ data_encipherment=False,
105
+ encipher_only=False,
106
+ decipher_only=False,
107
+ ),
108
+ critical=True,
109
+ )
110
+ .add_extension(
111
+ x509.ExtendedKeyUsage([x509.ExtendedKeyUsageOID.SERVER_AUTH]),
112
+ critical=True,
113
+ )
114
+ .sign(private_key, hashes.SHA256())
115
+ )
116
+
117
+ # Write private key to file
118
+ with open(key_path, "wb") as f:
119
+ f.write(
120
+ private_key.private_bytes(
121
+ encoding=serialization.Encoding.PEM,
122
+ format=serialization.PrivateFormat.PKCS8,
123
+ encryption_algorithm=serialization.NoEncryption(),
124
+ )
125
+ )
126
+
127
+ # Write certificate to file
128
+ with open(cert_path, "wb") as f:
129
+ f.write(cert.public_bytes(serialization.Encoding.PEM))
130
+
131
+ logger.info(f"SSL certificate created successfully at {key_path} and {cert_path}")
132
+
133
+ return key_path, cert_path