github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/python/bacalhau_sdk/config.py (about)

     1  """Utils for the SDK."""
     2  
     3  import base64
     4  import logging
     5  import os
     6  import stat
     7  from pathlib import Path
     8  from typing import Union
     9  
    10  import pem
    11  from bacalhau_apiclient import Configuration
    12  from Crypto.Hash import SHA256
    13  from Crypto.PublicKey import RSA
    14  from Crypto.Signature import pkcs1_15
    15  
    16  __client_id = None
    17  __user_id_key = None
    18  bits_per_key = 2048
    19  log = logging.getLogger(__name__)
    20  log.setLevel(logging.DEBUG)
    21  
    22  
    23  def get_client_id() -> Union[str, None]:
    24      """Return the client ID."""
    25      global __client_id
    26      return __client_id
    27  
    28  
    29  def set_client_id(client_id: str):
    30      """Set the client ID."""
    31      global __client_id
    32      __client_id = client_id
    33      log.debug("set client_id to %s", __client_id)
    34  
    35  
    36  def get_user_id_key():
    37      """Return the user ID key."""
    38      global __user_id_key
    39      return __user_id_key
    40  
    41  
    42  def set_user_id_key(user_id_key: RSA.RsaKey):
    43      """Set the user ID key."""
    44      global __user_id_key
    45      __user_id_key = user_id_key
    46  
    47  
    48  def init_config():
    49      """Initialize the config."""
    50      config_dir = __ensure_config_dir()
    51      log.debug("config_dir: {}".format(config_dir))
    52      __ensure_config_file()
    53      key_path = __ensure_user_id_key(config_dir)
    54      log.debug("key_path: {}".format(key_path))
    55      set_user_id_key(__load_user_id_key(key_path))
    56      log.debug("user_id_key set")
    57      set_client_id(__load_client_id(key_path))
    58      log.debug("client_id: {}".format(get_client_id()))
    59  
    60      conf = Configuration()
    61      if os.getenv("BACALHAU_API_HOST") and os.getenv("BACALHAU_API_PORT"):
    62          conf.host = "http://{}:{}".format(os.getenv("BACALHAU_API_HOST"), os.getenv("BACALHAU_API_PORT"))
    63          log.debug("Using BACALHAU_API_HOST and BACALHAU_API_PORT to set host: %s", conf.host)
    64  
    65      log.debug("init config done")
    66      return conf
    67  
    68  
    69  def __ensure_config_dir() -> Path:
    70      """Ensure the config directory exists and return its path."""
    71      config_dir_str = os.getenv("BACALHAU_DIR")
    72      if config_dir_str == "" or config_dir_str is None:
    73          log.debug("BACALHAU_DIR not set, using default of ~/.bacalhau")
    74          home_path = Path.home()
    75          config_dir = home_path.joinpath(".bacalhau")
    76          config_dir.mkdir(mode=700, parents=True, exist_ok=True)
    77      else:
    78          os.stat(config_dir_str)
    79          config_dir = Path(config_dir_str)
    80      log.debug("Using config dir: %s", config_dir.absolute().resolve())
    81      return config_dir.absolute().resolve()
    82  
    83  
    84  def __ensure_config_file() -> str:
    85      """Ensure that BACALHAU_DIR/config.yaml exists."""
    86      # warnings.warn("Not implemented - the BACALHAU_DIR/config.yaml config file is not used yet.")
    87      return ""
    88  
    89  
    90  def __ensure_user_id_key(config_dir: Path) -> Path:
    91      """Ensure that a default user ID key exists in the config dir."""
    92      key_file_name = "user_id.pem"
    93      user_id_key = config_dir.joinpath(key_file_name)
    94      if not os.path.isfile(user_id_key):
    95          log.info("User ID key not found at %s, generating one.", user_id_key)
    96          key = RSA.generate(bits_per_key)
    97          with open(user_id_key, "wb") as f:
    98              f.write(key.export_key("PEM", pkcs=1))
    99          os.chmod(user_id_key, stat.S_IRUSR | stat.S_IWUSR)
   100      else:
   101          log.info("Found user ID key at %s", user_id_key)
   102      return user_id_key
   103  
   104  
   105  def __load_user_id_key(key_file: Path) -> RSA.RsaKey:
   106      """Return the private key."""
   107      log.debug("Loading user ID key from %s", key_file)
   108      with open(key_file, "rb") as f:
   109          certs = pem.parse(f.read())
   110          private_key = RSA.import_key(certs[0].as_bytes())
   111      return private_key
   112  
   113  
   114  def __convert_to_client_id(key: RSA.RsaKey) -> str:
   115      """Return the client ID from a public key.
   116  
   117      e.g. `bae8c1b2adfa04cc647a2457e8c0c605cef8ed11bdea5ac1f19f94219d722dfz`.
   118      """
   119      der_key = key.public_key()
   120      hash_obj = SHA256.new()
   121      hash_obj.update(der_key.n.to_bytes(der_key.size_in_bytes(), byteorder="big"))
   122  
   123      return hash_obj.hexdigest()
   124  
   125  
   126  def __load_client_id(key_path: Path) -> str:
   127      """Return the client ID.
   128  
   129      Should be callable without the need of invoking init_config() first.
   130      """
   131      key = __load_user_id_key(key_path)
   132      return __convert_to_client_id(key)
   133  
   134  
   135  def sign_for_client(msg: bytes) -> str:
   136      """sign_for_client signs a message with the user's private ID key.
   137  
   138      Must be called after init_config().
   139      """
   140      signer = pkcs1_15.new(get_user_id_key())
   141      hash_obj = SHA256.new()
   142      hash_obj.update(msg)
   143  
   144      signed_payload = signer.sign(hash_obj)
   145      signature = base64.b64encode(signed_payload).decode()
   146  
   147      return signature
   148  
   149  
   150  def __clean_pem_pub_key(pem_pub_key: str) -> str:
   151      """Prepare a public key in the format expected by the API.
   152  
   153      - remove the header and footer
   154      - remove the newlines
   155      - remove the first 32 characters i.e. `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A`. \
   156          https://stackoverflow.com/questions/8784905/command-line-tool-to-export-rsa-private-key-to-rsapublickey
   157      """
   158      pem_public_key = (
   159          pem_pub_key.replace("-----BEGIN PUBLIC KEY-----", "")
   160          .replace("-----END PUBLIC KEY-----", "")
   161          .replace("\n", "")[32:]
   162      )
   163      return pem_public_key
   164  
   165  
   166  def get_client_public_key() -> str:
   167      """Return the client public key."""
   168      public_key = get_user_id_key().publickey()
   169      pem_public_key = public_key.export_key("PEM").decode()
   170      return __clean_pem_pub_key(pem_public_key)