Source code for flip_api.scripts.generate_trust_internal_service_keys

# Copyright (c) Guy's and St Thomas' NHS Foundation Trust & King's College London
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#     http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""Generate per-trust internal-service keys used across the trust services.

This is the trust-side analogue of ``generate_internal_service_key.py`` (which
covers the hub's fl-server → flip-api boundary). Each trust gets a distinct
plaintext key shared by every trust-internal service (trust-api, imaging-api,
data-access-api, fl-client) — the receiver does a constant-time compare against
its own copy. The hub never sees these keys.

Trust names are read from the ``TRUST_NAMES`` env var (a JSON list). The
plaintext keys are written as a JSON dict into ``TRUST_INTERNAL_SERVICE_KEYS``
in the environment file. ``trust/Makefile`` extracts the per-trust value at
deploy time via ``get_json_value``, the same way it already handles
``TRUST_API_KEYS``.

Usage:
    make generate-trust-internal-service-keys
    make generate-trust-internal-service-keys ENV_FILE=.env.stag
    make generate-trust-internal-service-keys FORCE=1
"""

import argparse
import json
import sys
from pathlib import Path

from flip_api.scripts.env_utils import read_env_value, update_or_append
from flip_api.scripts.generate_trust_key import generate_trust_key

REPO_ROOT = Path(__file__).resolve().parents[4]


[docs] def _parse_trust_names(lines: list[str]) -> list[str]: """Extract trust names from the TRUST_NAMES env var line. Args: lines (list[str]): Lines of the environment file. Returns: list[str]: List of trust names, e.g. ``["Trust_1", "Trust_2"]``. """ for line in lines: if line.startswith("TRUST_NAMES="): return json.loads(line.split("=", 1)[1]) return []
[docs] def main() -> None: """Generate per-trust internal service keys and update the environment file. Existing per-trust keys are preserved unless ``--force`` is given. Raises: SystemExit: If the env file is missing or contains no ``TRUST_NAMES`` entry. """ parser = argparse.ArgumentParser( description="Generate per-trust internal-service keys and update an environment file.", ) parser.add_argument( "--env-file", type=Path, default=REPO_ROOT / ".env.development", help="Path to the environment file to update (default: .env.development)", ) parser.add_argument( "--force", action="store_true", default=False, help="Regenerate all keys even if they already exist in the env file.", ) args = parser.parse_args() env_file: Path = args.env_file if not env_file.exists(): print(f"Error: {env_file} not found.") sys.exit(1) lines = env_file.read_text().splitlines() trust_names = _parse_trust_names(lines) if not trust_names: print(f"Error: no TRUST_NAMES entry found in {env_file.name}.") sys.exit(1) existing_keys_json = read_env_value(lines, "TRUST_INTERNAL_SERVICE_KEYS") existing_keys: dict[str, str] = json.loads(existing_keys_json) if existing_keys_json else {} keys_dict: dict[str, str] = {} actions: dict[str, str] = {} for trust_name in trust_names: existing_key = existing_keys.get(trust_name) if not args.force and existing_key: keys_dict[trust_name] = existing_key actions[trust_name] = "skipped" else: key, _ = generate_trust_key() keys_dict[trust_name] = key actions[trust_name] = "generated" lines = update_or_append(lines, "TRUST_INTERNAL_SERVICE_KEYS", json.dumps(keys_dict)) env_file.write_text("\n".join(lines) + "\n") generated = sum(1 for a in actions.values() if a == "generated") skipped = sum(1 for a in actions.values() if a == "skipped") print(f"Updated {env_file.name}: {generated} trust internal-service keys generated, {skipped} skipped.") for name, action in actions.items(): print(f" {name}: {action}") if generated: print(f" TRUST_INTERNAL_SERVICE_KEYS updated with {len(trust_names)} entries")
if __name__ == "__main__": main()