From 97ed4d34702ca518871d2cbc3f3b1672c71bbc2f Mon Sep 17 00:00:00 2001 From: Rohitash-Karan Date: Mon, 23 Mar 2026 09:28:23 -0400 Subject: [PATCH 1/9] DAPCS-1965: Changes to support multi-vault for ripple Signed-off-by: Rohitash-Karan --- contracts/ripple/backend/backend.yml.tftpl | 116 ++++++++++++- contracts/ripple/backend/user_data_backend.tf | 2 +- contracts/ripple/backend/variables.tf | 3 +- .../frontend_plugin/frontend_plugin.yml.tftpl | 8 + .../frontend_plugin/terraform.tfvars.template | 1 + .../user_data_frontend_plugin.tf | 4 + contracts/ripple/frontend_plugin/variables.tf | 24 +++ .../backend_plugin/backend_plugin_manager.py | 152 ++++++++---------- .../oso_ripple_plugins/common/pre_request.py | 2 +- .../frontend_plugin_manager.py | 139 +++++++++------- 10 files changed, 298 insertions(+), 153 deletions(-) diff --git a/contracts/ripple/backend/backend.yml.tftpl b/contracts/ripple/backend/backend.yml.tftpl index c4df6a7..18647dc 100644 --- a/contracts/ripple/backend/backend.yml.tftpl +++ b/contracts/ripple/backend/backend.yml.tftpl @@ -14,6 +14,15 @@ spec: # This must be same as VAULT_ID below - name: Vault__Ids__0 value: "${tpl.vault_id}" + - name: Vault__Ids__1 + value: "b5c95ea1-0e12-4b2f-8426-87c572b99d09" + - name: Vault__Ids__2 + value: "1e14abd7-9086-4196-8385-a74907fee202" + - name: Vault__Ids__3 + value: "4a2b3c11-93c2-4b9f-affd-ff6893774b98" + - name: Vault__Ids__4 + value: "11111111-1111-1111-1111-111111111111" + # Passphrase: "{{EMPTY}}" is needed so that the bridge data is not cyphered and can be managed by OSO - name: Passphrase value: "${tpl.passphrase}" @@ -29,6 +38,8 @@ spec: - name: cert mountPath: /data/cert readOnly: true + - name: ibm-cfg + mountPath: /app/cfg/ibm.cfg - name: cold-vault image: ${tpl.cold_vault_image} @@ -36,18 +47,104 @@ spec: - name: PLATFORM value: "kms" - name: VAULT_BRIDGE_LOGLEVEL - value: "7" + value: "6" - name: VAULT_CORE_LOGLEVEL - value: "7" + value: "3" - name: VAULT_KMS_ENDPOINT value: "localhost:10000" - name: VAULT_TRUSTED_SIG - value: "pem:${tpl.notary_messaging_public_key}" + value: "${tpl.notary_messaging_public_key}" - name: VAULT_ID value: "${tpl.vault_id}" # This comes from the docker container that is spun up above. If it's normal vault, then it would be API end point. - name: HARMONIZE_CORE_ENDPOINT value: "http://localhost:8080/internal/v1" + - name: HMZ_FEATURE_OPTIONAL_MAXIMUM_FEE + value: "true" + + - name: vault-releases + image: ${tpl.cold_vault_image} + env: + - name: PLATFORM + value: "mock" + - name: MOCK_PHRASE + value: "ibmosocold" + - name: TRUSTED_SIG + value: "ed25519:50692dfa472f013e2f87e5d210be40cefe178e33787be4688d5da0afe06ed149" + - name: VAULT_ID + value: "b5c95ea1-0e12-4b2f-8426-87c572b99d09" + - name: HARMONIZE_CORE_ENDPOINT + value: "http://localhost:8080/internal/v1" + volumeMounts: + - name: mock-vault-cfg + mountPath: /opt/vault-core/cfg/vault.cfg + subPath: vault.cfg + - name: mock-cfg + mountPath: /opt/vault-core/cfg/mock.cfg + subPath: mock.cfg + + - name: vault-releases2 + image: ${tpl.cold_vault_image} + env: + - name: PLATFORM + value: "mock" + - name: MOCK_PHRASE + value: "ibmosocold2" + - name: TRUSTED_SIG + value: "ed25519:50692dfa472f013e2f87e5d210be40cefe178e33787be4688d5da0afe06ed149" + - name: VAULT_ID + value: "1e14abd7-9086-4196-8385-a74907fee202" + - name: HARMONIZE_CORE_ENDPOINT + value: "http://localhost:8080/internal/v1" + volumeMounts: + - name: mock-vault-cfg + mountPath: /opt/vault-core/cfg/vault.cfg + subPath: vault.cfg + - name: mock-cfg + mountPath: /opt/vault-core/cfg/mock.cfg + subPath: mock.cfg + + - name: vault-releases3 + image: ${tpl.cold_vault_image} + env: + - name: PLATFORM + value: "mock" + - name: MOCK_PHRASE + value: "ibmosocold4" + - name: TRUSTED_SIG + value: "ed25519:50692dfa472f013e2f87e5d210be40cefe178e33787be4688d5da0afe06ed149" + - name: VAULT_ID + value: "4a2b3c11-93c2-4b9f-affd-ff6893774b98" + - name: HARMONIZE_CORE_ENDPOINT + value: "http://localhost:8080/internal/v1" + volumeMounts: + - name: mock-vault-cfg + mountPath: /opt/vault-core/cfg/vault.cfg + subPath: vault.cfg + - name: mock-cfg + mountPath: /opt/vault-core/cfg/mock.cfg + subPath: mock.cfg + + - name: vault-releases4 + image: ${tpl.cold_vault_image} + env: + - name: PLATFORM + value: "mock" + - name: MOCK_PHRASE + value: "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222" + - name: TRUSTED_SIG + value: "ed25519:50692dfa472f013e2f87e5d210be40cefe178e33787be4688d5da0afe06ed149" + - name: VAULT_ID + value: "11111111-1111-1111-1111-111111111111" + - name: HARMONIZE_CORE_ENDPOINT + value: "http://localhost:8080/internal/v1" + volumeMounts: + - name: mock-vault-cfg + mountPath: /opt/vault-core/cfg/vault.cfg + subPath: vault.cfg + - name: mock-cfg + mountPath: /opt/vault-core/cfg/mock.cfg + subPath: mock.cfg - name: backend-plugin image: ${tpl.backend_plugin_image} @@ -58,7 +155,7 @@ spec: env: - name: COMPONENT value: "backend_plugin" - - name: SEED + - name: OSOENCRYPTIONPASS value: "${tpl.seed}" - name: PORT value: "4000" @@ -111,6 +208,17 @@ spec: path: ./cert type: Directory + - name: mock-vault-cfg + hostPath: + path: ./mock-cfg/vault.cfg + type: File + + - name: mock-cfg + hostPath: + path: ./mock-cfg/mock.cfg + type: File + + %{~ if tpl.enable_ep11server ~} %{ if !tpl.crypto_pass_enable } - name: cfg diff --git a/contracts/ripple/backend/user_data_backend.tf b/contracts/ripple/backend/user_data_backend.tf index 8b7d1c3..6760b91 100644 --- a/contracts/ripple/backend/user_data_backend.tf +++ b/contracts/ripple/backend/user_data_backend.tf @@ -49,8 +49,8 @@ resource "local_file" "podman-play" { vault_id = var.VAULT_ID, passphrase = var.PASSPHRASE, notary_messaging_public_key = var.NOTARY_MESSAGING_PUBLIC_KEY, - seed = var.SEED, cold_bridge_endpoint = var.COLD_BRIDGE_ENDPOINT, + seed = var.OSOENCRYPTIONPASS, enable_ep11server = var.INTERNAL_GREP11, crypto_pass_enable = var.CRYPTO_PASSTHROUGH_ENABLEMENT, grep11_image = var.GREP11_IMAGE, diff --git a/contracts/ripple/backend/variables.tf b/contracts/ripple/backend/variables.tf index 74f42d2..9fcc18d 100644 --- a/contracts/ripple/backend/variables.tf +++ b/contracts/ripple/backend/variables.tf @@ -15,6 +15,7 @@ variable "PREFIX" { type = string + default = "" } variable "DEBUG" { @@ -23,7 +24,7 @@ variable "DEBUG" { default = false } -variable "SEED" { +variable "OSOENCRYPTIONPASS" { type = string description = "Encrypt data through the iteration pipeline (should be the same value as frontend plugin)" default = "" diff --git a/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl b/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl index 33477e2..c57d669 100644 --- a/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl +++ b/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl @@ -17,16 +17,24 @@ spec: value: "4000" - name: SK value: "${tpl.SK}" + - name: HMZ_USER_SK + value: "${tpl.HMZ_USER_SK}" - name: VAULTID value: "${tpl.VAULTID}" - name: HMZ_AUTH_HOSTNAME value: "${tpl.HMZ_AUTH_HOSTNAME}" - name: HMZ_API_HOSTNAME value: "${tpl.HMZ_API_HOSTNAME}" + - name: HMZ_AUTH_CUSTOMERID + value: "${tpl.HMZ_AUTH_CUSTOMERID}" + - name: HMZ_AUTH_PATH + value: "${tpl.HMZ_AUTH_PATH}" - name: ROOTCERT value: "${tpl.ROOTCERT}" - name: SEED value: "${tpl.SEED}" + - name: OSOENCRYPTIONPASS + value: "${tpl.OSOENCRYPTIONPASS}" - name: TOKEN_EXP value: "${tpl.TOKEN_EXP}" ports: diff --git a/contracts/ripple/frontend_plugin/terraform.tfvars.template b/contracts/ripple/frontend_plugin/terraform.tfvars.template index 49984d2..41b02d6 100644 --- a/contracts/ripple/frontend_plugin/terraform.tfvars.template +++ b/contracts/ripple/frontend_plugin/terraform.tfvars.template @@ -6,3 +6,4 @@ HMZ_AUTH_HOSTNAME="" HMZ_API_HOSTNAME="" VAULT_ID="" SK="" +HMZ_USER_SK="" diff --git a/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf b/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf index 84a1039..654ce73 100644 --- a/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf +++ b/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf @@ -19,11 +19,15 @@ resource "local_file" "frontend_plugin_podman_play" { { tpl = { image = var.FRONTEND_PLUGIN_IMAGE, SK = var.SK, + HMZ_USER_SK = var.HMZ_USER_SK, VAULTID = var.VAULT_ID, HMZ_AUTH_HOSTNAME = var.HMZ_AUTH_HOSTNAME, HMZ_API_HOSTNAME = var.HMZ_API_HOSTNAME, + HMZ_AUTH_PATH = var.HMZ_AUTH_PATH, + HMZ_AUTH_CUSTOMERID = var.HMZ_AUTH_CUSTOMERID, ROOTCERT = var.ROOTCERT, SEED = var.SEED, + OSOENCRYPTIONPASS = var.OSOENCRYPTIONPASS, TOKEN_EXP = var.TOKEN_EXP } }, ) diff --git a/contracts/ripple/frontend_plugin/variables.tf b/contracts/ripple/frontend_plugin/variables.tf index 1ec565e..d9a31d2 100644 --- a/contracts/ripple/frontend_plugin/variables.tf +++ b/contracts/ripple/frontend_plugin/variables.tf @@ -69,3 +69,27 @@ variable "TOKEN_EXP" { description = "Ripple configured bearer token expiration (#h#m#s format)" default = "4h0m0s" } + +variable "OSOENCRYPTIONPASS" { + type = string + description = "Encrypt data through the iteration pipeline (should be same value as backend plugin)" + default = "" +} + +variable "HMZ_USER_SK" { + type = string + description = "Private (secret) key of a registered user used to login to Ripple" + default = "" +} + +variable "HMZ_AUTH_PATH" { + type = string + description = "Harmonize path to get auth toekn" + default = "/token" +} + +variable "HMZ_AUTH_CUSTOMERID" { + type = string + description = "Harmonize customer id used to authent" + default = "customer_api" +} diff --git a/ripple-plugin/src/oso_ripple_plugins/backend_plugin/backend_plugin_manager.py b/ripple-plugin/src/oso_ripple_plugins/backend_plugin/backend_plugin_manager.py index 3b2eea2..6aaa1ed 100644 --- a/ripple-plugin/src/oso_ripple_plugins/backend_plugin/backend_plugin_manager.py +++ b/ripple-plugin/src/oso_ripple_plugins/backend_plugin/backend_plugin_manager.py @@ -35,7 +35,7 @@ class BackendPluginManager: def __init__(self): self.cold_bridge_endpoint = os.environ.get("COLD_BRIDGE_ENDPOINT", "http://localhost:8080") - self.seed = os.environ.get("SEED", "") + self.seed = os.environ.get("OSOENCRYPTIONPASS", "") logging.basicConfig(stream=sys.stdout, level=logging.INFO) self.logger = logging.getLogger(__name__) @@ -55,100 +55,83 @@ def bulk_download(self) -> List[Dict]: self.logger.info("Bulk download finished successfully") - empty_content = { - "accounts": [], - "transactions": [], - "manifests": [], - "vaults": [], - } - - def write_document_set(documents, content_key: str, id_key: str): - for item in response_json.get(content_key, []): - self.logger.info( - f"Saving document from {content_key} for bulk download" - ) - - try: - document_id = item.get(id_key) - self.logger.info(f"Saving document {document_id} for bulk download") - - content = copy.deepcopy(empty_content) - content.setdefault(content_key, []).append(item) - - # Encrypt content - if len(self.seed) > 0: - data = crypt.encrypt(json.dumps(content), self.seed) - else: - data = json.dumps(content) - - documents.append( - {"id": item.get(id_key), "content": data, "metadata": ""} - ) - - self.logger.info( - f"Successfully saved document {document_id} for bulk download" - ) - except Exception as err: - self.logger.exception(err) - continue - documents = [] - for content_key, id_key in [ - ("transactions", "transactionId"), - ("accounts", "accountId"), - ("manifests", "manifestId"), - ]: - write_document_set(documents, content_key, id_key) + + sections = [ + ("transactions", "transactionId", "transaction"), + ("accounts", "accountId", "account"), + ("manifests", "manifestId", "manifest"), + ] + for section, id_key, type_name in sections: + for item in response_json.get(section, []): + # Encrypt if seed is set + if self.seed and "signedPayload" in item: + item["signedPayloadCiphered"] = crypt.encrypt(item["signedPayload"], self.seed) + del item["signedPayload"] + + # Build content and metadata + content = { + "accounts": [item] if section == "accounts" else [], + "transactions": [item] if section == "transactions" else [], + "manifests": [item] if section == "manifests" else [], + "vaults": [], + } + meta = {"source": item["vaultId"], "type": type_name} + + documents.append({ + "id": item[id_key], + "content": json.dumps(content), + "metadata": json.dumps(meta) + }) return documents def bulk_upload(self, documents): - vault_id = None - transactions = [] - accounts = [] - manifests = [] + v_tx= {} + v_ac= {} + v_ma= {} self.logger.info("Saving documents for bulk upload") for document in documents: try: - document_id = document["id"] - self.logger.info(f"Saving document {document_id} for bulk upload") - - # Decrypt content - if len(self.seed) > 0: - contents = json.loads(crypt.decrypt(document["content"], self.seed)) - else: - contents = json.loads(document["content"]) - - transactions.extend(contents.get("transactions", [])) - accounts.extend(contents.get("accounts", [])) - manifests.extend(contents.get("manifests", [])) - - if vault_id is None: - vault_id = contents.get("vaultId") + contents = json.loads(document["content"]) + vaultid= contents.get("vaultId") + + if vaultid not in v_tx: + v_tx[vaultid]=[] + v_ac[vaultid]=[] + v_ma[vaultid]=[] + # Map sections to their storage dict + section_map = { + "transactions": v_tx[vaultid], + "accounts": v_ac[vaultid], + "manifests": v_ma[vaultid], + } + + for section, storage in section_map.items(): + for item in contents.get(section, []): + if self.seed and "signedPayloadCiphered" in item: + item["signedPayload"] = crypt.decrypt(item["signedPayloadCiphered"], self.seed) + del item["signedPayloadCiphered"] + storage.append(item) + + self.logger.info(f"Saving document {document['id']} for bulk upload") - self.logger.info( - f"Successfully saved document {document_id} for bulk upload" - ) except Exception as e: self.logger.exception(e) continue - if not vault_id: - return Exception("Could not get vault id") - - content = { - "vaultId": vault_id, - "accounts": accounts, - "transactions": transactions, - "manifests": manifests, - } - self.logger.info("Performing bulk upload to backend") - - try: - with tempfile.NamedTemporaryFile(mode="w", delete=False) as vault_file: - json.dump(content, vault_file) + for vaultid in v_tx.keys(): + content = { + "vaultId": vaultid, + "accounts": v_ac[vaultid], + "transactions": v_tx[vaultid], + "manifests": v_ma[vaultid], + } + try: + with tempfile.NamedTemporaryFile(mode="w", delete=False) as vault_file: + json.dump(content, vault_file) files = {"files": (vault_id, open(vault_file.name, "rb"))} response = requests.post( @@ -156,9 +139,10 @@ def bulk_upload(self, documents): files=files, ) response.raise_for_status() - except Exception as e: - raise e - finally: - os.remove(vault_file.name) + self.logger.info(f"Successfully uploaded vault {vaultid}") + except requests.HTTPError as http_err: + self.logger.error(f"HTTP error uploading vault {vaultid}: {http_err} - {response.text}") + except Exception as err: + self.logger.error(f"Unexpected error uploading vault {vaultid}: {err}") self.logger.info("Bulk upload finished successfully") diff --git a/ripple-plugin/src/oso_ripple_plugins/common/pre_request.py b/ripple-plugin/src/oso_ripple_plugins/common/pre_request.py index 9e95b6e..d184cee 100644 --- a/ripple-plugin/src/oso_ripple_plugins/common/pre_request.py +++ b/ripple-plugin/src/oso_ripple_plugins/common/pre_request.py @@ -72,7 +72,7 @@ def set_cert(): def bind_flask_before_request(sender: Flask, **extras) -> None: logger.info(f"HTTP Method: {request.method} URL Path: {request.path}") - + return client_verify = request.headers.get("X-SSL-CLIENT-VERIFY", "FAILED") if client_verify != "SUCCESS": logger.info(f"Could not verify certificate, client verify: {client_verify}") diff --git a/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py b/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py index 5bff2bf..77137b6 100644 --- a/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py +++ b/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py @@ -36,9 +36,9 @@ class FrontendPluginManager: def __init__(self): - if "SK" not in os.environ: - raise errors.ConfigError("SK not found") - private_key_b64 = os.environ["SK"] + if "HMZ_USER_SK" not in os.environ: + raise errors.ConfigError("Harmonize OSO user server key not found") + private_key_b64 = os.environ["HMZ_USER_SK"] private_key_decoded = base64.b64decode(private_key_b64) self.private_key = load_pem_private_key(private_key_decoded, password=None) self.public_key = base64.b64encode( @@ -52,15 +52,23 @@ def __init__(self): raise errors.ConfigError("HMZ_AUTH_HOSTNAME not found") self.hmz_auth_hostname = os.environ["HMZ_AUTH_HOSTNAME"] + if "HMZ_AUTH_PATH" not in os.environ: + raise errors.ConfigError("HMZ_AUTH_PATH not found") + self.hmz_auth_path = os.environ["HMZ_AUTH_PATH"] + + if "HMZ_AUTH_CUSTOMERID" not in os.environ: + raise errors.ConfigError("HMZ_AUTH_CUSTOMERID not found") + self.hmz_auth_customerid = os.environ["HMZ_AUTH_CUSTOMERID"] + if "HMZ_API_HOSTNAME" not in os.environ: raise errors.ConfigError("HMZ_API_HOSTNAME not found") self.hmz_api_hostname = os.environ["HMZ_API_HOSTNAME"] if "VAULTID" not in os.environ: raise errors.ConfigError("VAULTID not found") - self.vault_id = os.environ["VAULTID"] + self.vaultids = os.environ["VAULTID"].split() - self.seed = os.environ.get("SEED", "") + self.seed = os.environ.get("OSOENCRYPTIONPASS", "") self.root_cert_b64 = os.environ.get("ROOTCERT") with tempfile.NamedTemporaryFile(delete=False) as root_cert_file: @@ -127,7 +135,7 @@ def _get_token(self) -> Tuple[str, float]: challenge = str(uuid.uuid4()) signature = self._sign(challenge) data = { - "client_id": "customer_api", + "client_id": self.hmz_auth_customerid, "grant_type": "password", "challenge": challenge, "public_key": self.public_key, @@ -135,7 +143,7 @@ def _get_token(self) -> Tuple[str, float]: } response = requests.post( - f"https://{self.hmz_auth_hostname}/token", + f"https://{self.hmz_auth_hostname}{self.hmz_auth_path}", data=data, headers={"Content-Type": "application/x-www-form-urlencoded"}, verify=self.verify, @@ -173,62 +181,66 @@ def get_token(self) -> str: def bulk_download(self) -> list: self.logger.info("Performing bulk download from frontend") token = self.get_token() - url = f"https://{self.hmz_api_hostname}/v1/vaults/{self.vault_id}/operations/prepared" - response = requests.get( - url=url, - headers={"Authorization": "Bearer " + token}, - stream=True, - verify=self.verify, - ) - response.raise_for_status() - vault_json = response.json() - self.logger.info("Bulk download finished successfully") - - empty_content = { - "vaultId": "", - "accounts": [], - "transactions": [], - "manifests": [], - } + documents = [] + for vaultid in self.vaultids: - def write_document_set(documents, content_key: str, id_key: str): - for item in vault_json.get(content_key, []): - self.logger.info( - f"Saving document from {content_key} for bulk download" - ) + url = f"https://{self.hmz_api_hostname}/v1/vaults/{vaultid}/operations/prepared" + response = requests.get( + url=url, + headers={"Authorization": "Bearer " + token}, + stream=True, + verify=self.verify, + ) + response.raise_for_status() + vault_json = response.json() + self.logger.info(f"Bulk download finished successfully for vault {vaultid}") + empty_content = { + "vaultId": vaultid, + "accounts": [], + "transactions": [], + "manifests": [], + } + + def write_document_set(documents, content_key: str, id_key: str): + for item in vault_json.get(content_key, []): + self.logger.info( + f"Saving document from {content_key} for bulk download" + ) + try: + document_id = item.get(id_key) + self.logger.info(f"Saving document {document_id} for bulk download") - try: - document_id = item.get(id_key) - self.logger.info(f"Saving document {document_id} for bulk download") + content = copy.deepcopy(empty_content) + content.setdefault(content_key, []).append(item) - content = copy.deepcopy(empty_content) - content["vaultId"] = vault_json["vaultId"] - content.setdefault(content_key, []).append(item) + # Encrypt content + if self.seed: + for section in ["transactions", "manifests", "accounts"]: + for item in content.get(section, []): + if "signedPayload" in item: + item["signedPayloadCiphered"] = crypt.encrypt(item["signedPayload"], self.seed) + del item["signedPayload"] - # Encrypt content - if len(self.seed) > 0: - data = crypt.encrypt(json.dumps(content), self.seed) - else: data = json.dumps(content) - - documents.append( - {"id": document_id, "content": data, "metadata": ""} - ) - - self.logger.info( - f"Successfully saved document {document_id} for bulk download" - ) - except Exception as e: - self.logger.exception(e) - continue - - documents = [] - for content_key, id_key in [ - ("transactions", "transactionId"), - ("accounts", "accountId"), - ("manifests", "manifestId"), - ]: - write_document_set(documents, content_key, id_key) + meta = { "source" : vaultid, "type": content_key} + documents.append( + {"id": document_id, "content": data, "metadata": json.dumps(meta) } + ) + + self.logger.info( + f"Successfully saved document {document_id} for bulk download" + ) + except Exception as e: + self.logger.exception(e) + continue + + + for content_key, id_key in [ + ("transactions", "transactionId"), + ("accounts", "accountId"), + ("manifests", "manifestId"), + ]: + write_document_set(documents, content_key, id_key) return documents @@ -239,15 +251,18 @@ def bulk_upload(self, documents): manifests = [] self.logger.info("Saving documents for bulk upload") + contents = json.loads(document["content"]) for document in documents: try: document_id = document["id"] self.logger.info(f"Saving document {document_id} for bulk upload") # Decrypt content - if len(self.seed) > 0: - contents = json.loads(crypt.decrypt(document["content"], self.seed)) - else: - contents = json.loads(document["content"]) + if self.seed: + for section in ("transactions", "accounts", "manifests"): + for item in contents.get(section, []): + if "signedPayloadCiphered" in item: + item["signedPayload"] = crypt.decrypt(item["signedPayloadCiphered"], self.seed) + del item["signedPayloadCiphered"] transactions.extend(contents.get("transactions", [])) accounts.extend(contents.get("accounts", [])) From bbc5111a1b61bfd4586846adf38bced682bf0534 Mon Sep 17 00:00:00 2001 From: Rohitash-Karan Date: Fri, 27 Mar 2026 11:17:00 -0400 Subject: [PATCH 2/9] DAPCS-1965: Changes to support multi-vault for ripple --- contracts/ripple/backend/backend.yml.tftpl | 103 ++---------------- .../ripple/backend/terraform.tfvars.template | 7 ++ contracts/ripple/backend/user_data_backend.tf | 1 + contracts/ripple/backend/variables.tf | 9 ++ .../frontend_plugin/frontend_plugin.yml.tftpl | 14 +-- .../user_data_frontend_plugin.tf | 3 +- contracts/ripple/frontend_plugin/variables.tf | 6 - .../oso_ripple_plugins/common/pre_request.py | 2 +- 8 files changed, 34 insertions(+), 111 deletions(-) diff --git a/contracts/ripple/backend/backend.yml.tftpl b/contracts/ripple/backend/backend.yml.tftpl index 18647dc..181944d 100644 --- a/contracts/ripple/backend/backend.yml.tftpl +++ b/contracts/ripple/backend/backend.yml.tftpl @@ -14,15 +14,10 @@ spec: # This must be same as VAULT_ID below - name: Vault__Ids__0 value: "${tpl.vault_id}" - - name: Vault__Ids__1 - value: "b5c95ea1-0e12-4b2f-8426-87c572b99d09" - - name: Vault__Ids__2 - value: "1e14abd7-9086-4196-8385-a74907fee202" - - name: Vault__Ids__3 - value: "4a2b3c11-93c2-4b9f-affd-ff6893774b98" - - name: Vault__Ids__4 - value: "11111111-1111-1111-1111-111111111111" - +%{ for i, vault in tpl.mock_vaults ~} + - name: Vault__Ids__${i + 1} + value: "${vault.vault_id}" +%{ endfor ~} # Passphrase: "{{EMPTY}}" is needed so that the bridge data is not cyphered and can be managed by OSO - name: Passphrase value: "${tpl.passphrase}" @@ -62,89 +57,21 @@ spec: - name: HMZ_FEATURE_OPTIONAL_MAXIMUM_FEE value: "true" - - name: vault-releases - image: ${tpl.cold_vault_image} - env: - - name: PLATFORM - value: "mock" - - name: MOCK_PHRASE - value: "ibmosocold" - - name: TRUSTED_SIG - value: "ed25519:50692dfa472f013e2f87e5d210be40cefe178e33787be4688d5da0afe06ed149" - - name: VAULT_ID - value: "b5c95ea1-0e12-4b2f-8426-87c572b99d09" - - name: HARMONIZE_CORE_ENDPOINT - value: "http://localhost:8080/internal/v1" - volumeMounts: - - name: mock-vault-cfg - mountPath: /opt/vault-core/cfg/vault.cfg - subPath: vault.cfg - - name: mock-cfg - mountPath: /opt/vault-core/cfg/mock.cfg - subPath: mock.cfg - - - name: vault-releases2 +%{ for i, vault in tpl.mock_vaults ~} + - name: vault-releases-${i} image: ${tpl.cold_vault_image} env: - name: PLATFORM value: "mock" - name: MOCK_PHRASE - value: "ibmosocold2" + value: "${vault.mock_phrase}" - name: TRUSTED_SIG value: "ed25519:50692dfa472f013e2f87e5d210be40cefe178e33787be4688d5da0afe06ed149" - name: VAULT_ID - value: "1e14abd7-9086-4196-8385-a74907fee202" + value: "${vault.vault_id}" - name: HARMONIZE_CORE_ENDPOINT value: "http://localhost:8080/internal/v1" - volumeMounts: - - name: mock-vault-cfg - mountPath: /opt/vault-core/cfg/vault.cfg - subPath: vault.cfg - - name: mock-cfg - mountPath: /opt/vault-core/cfg/mock.cfg - subPath: mock.cfg - - - name: vault-releases3 - image: ${tpl.cold_vault_image} - env: - - name: PLATFORM - value: "mock" - - name: MOCK_PHRASE - value: "ibmosocold4" - - name: TRUSTED_SIG - value: "ed25519:50692dfa472f013e2f87e5d210be40cefe178e33787be4688d5da0afe06ed149" - - name: VAULT_ID - value: "4a2b3c11-93c2-4b9f-affd-ff6893774b98" - - name: HARMONIZE_CORE_ENDPOINT - value: "http://localhost:8080/internal/v1" - volumeMounts: - - name: mock-vault-cfg - mountPath: /opt/vault-core/cfg/vault.cfg - subPath: vault.cfg - - name: mock-cfg - mountPath: /opt/vault-core/cfg/mock.cfg - subPath: mock.cfg - - - name: vault-releases4 - image: ${tpl.cold_vault_image} - env: - - name: PLATFORM - value: "mock" - - name: MOCK_PHRASE - value: "22222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222" - - name: TRUSTED_SIG - value: "ed25519:50692dfa472f013e2f87e5d210be40cefe178e33787be4688d5da0afe06ed149" - - name: VAULT_ID - value: "11111111-1111-1111-1111-111111111111" - - name: HARMONIZE_CORE_ENDPOINT - value: "http://localhost:8080/internal/v1" - volumeMounts: - - name: mock-vault-cfg - mountPath: /opt/vault-core/cfg/vault.cfg - subPath: vault.cfg - - name: mock-cfg - mountPath: /opt/vault-core/cfg/mock.cfg - subPath: mock.cfg +%{ endfor ~} - name: backend-plugin image: ${tpl.backend_plugin_image} @@ -165,7 +92,6 @@ spec: - containerPort: 4000 hostPort: 4000 - %{ if tpl.enable_ep11server } - name: ep11server image: ${tpl.grep11_image} @@ -208,17 +134,6 @@ spec: path: ./cert type: Directory - - name: mock-vault-cfg - hostPath: - path: ./mock-cfg/vault.cfg - type: File - - - name: mock-cfg - hostPath: - path: ./mock-cfg/mock.cfg - type: File - - %{~ if tpl.enable_ep11server ~} %{ if !tpl.crypto_pass_enable } - name: cfg diff --git a/contracts/ripple/backend/terraform.tfvars.template b/contracts/ripple/backend/terraform.tfvars.template index b99bd05..f0a4033 100644 --- a/contracts/ripple/backend/terraform.tfvars.template +++ b/contracts/ripple/backend/terraform.tfvars.template @@ -9,6 +9,13 @@ COLD_BRIDGE_IMAGE="registry.control23.dap.local/metaco-ripple/vault-cold-bridge@ COLD_VAULT_IMAGE="registry.control23.dap.local/metaco-ripple/vault-releases@sha256:" KMSCONNECT_IMAGE="registry.control23.dap.local/metaco-ripple/kms-ibm@sha256:" +MOCK_VAULTS = [ + { + vault_id = "" + mock_phrase = "" + } +] + # Vault Configuration VAULT_ID="" NOTARY_MESSAGING_PUBLIC_KEY="" diff --git a/contracts/ripple/backend/user_data_backend.tf b/contracts/ripple/backend/user_data_backend.tf index 6760b91..f0e4e7f 100644 --- a/contracts/ripple/backend/user_data_backend.tf +++ b/contracts/ripple/backend/user_data_backend.tf @@ -54,6 +54,7 @@ resource "local_file" "podman-play" { enable_ep11server = var.INTERNAL_GREP11, crypto_pass_enable = var.CRYPTO_PASSTHROUGH_ENABLEMENT, grep11_image = var.GREP11_IMAGE, + mock_vaults = var.MOCK_VAULTS, } }, ) filename = "podman-play/play.yml" diff --git a/contracts/ripple/backend/variables.tf b/contracts/ripple/backend/variables.tf index 9fcc18d..a661a0c 100644 --- a/contracts/ripple/backend/variables.tf +++ b/contracts/ripple/backend/variables.tf @@ -200,3 +200,12 @@ variable "CRYPTO_PASSTHROUGH_ENABLEMENT" { default = true description = "Crypto passthrough enablement configuration" } + +variable "MOCK_VAULTS" { + type = list(object({ + vault_id = string + mock_phrase = string + })) + description = "List of mock vault configurations" + default = [] +} diff --git a/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl b/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl index c57d669..898e42a 100644 --- a/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl +++ b/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl @@ -15,8 +15,6 @@ spec: value: "frontend_plugin" - name: PORT value: "4000" - - name: SK - value: "${tpl.SK}" - name: HMZ_USER_SK value: "${tpl.HMZ_USER_SK}" - name: VAULTID @@ -25,16 +23,16 @@ spec: value: "${tpl.HMZ_AUTH_HOSTNAME}" - name: HMZ_API_HOSTNAME value: "${tpl.HMZ_API_HOSTNAME}" - - name: HMZ_AUTH_CUSTOMERID - value: "${tpl.HMZ_AUTH_CUSTOMERID}" - - name: HMZ_AUTH_PATH - value: "${tpl.HMZ_AUTH_PATH}" + - name: HMZ_AUTH_CUSTOMERID + value: "${tpl.HMZ_AUTH_CUSTOMERID}" + - name: HMZ_AUTH_PATH + value: "${tpl.HMZ_AUTH_PATH}" - name: ROOTCERT value: "${tpl.ROOTCERT}" - name: SEED value: "${tpl.SEED}" - - name: OSOENCRYPTIONPASS - value: "${tpl.OSOENCRYPTIONPASS}" + - name: OSOENCRYPTIONPASS + value: "${tpl.OSOENCRYPTIONPASS}" - name: TOKEN_EXP value: "${tpl.TOKEN_EXP}" ports: diff --git a/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf b/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf index 654ce73..1c8c64c 100644 --- a/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf +++ b/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf @@ -18,8 +18,7 @@ resource "local_file" "frontend_plugin_podman_play" { "${path.module}/frontend_plugin.yml.tftpl", { tpl = { image = var.FRONTEND_PLUGIN_IMAGE, - SK = var.SK, - HMZ_USER_SK = var.HMZ_USER_SK, + HMZ_USER_SK = var.HMZ_USER_SK, VAULTID = var.VAULT_ID, HMZ_AUTH_HOSTNAME = var.HMZ_AUTH_HOSTNAME, HMZ_API_HOSTNAME = var.HMZ_API_HOSTNAME, diff --git a/contracts/ripple/frontend_plugin/variables.tf b/contracts/ripple/frontend_plugin/variables.tf index d9a31d2..c3a9936 100644 --- a/contracts/ripple/frontend_plugin/variables.tf +++ b/contracts/ripple/frontend_plugin/variables.tf @@ -37,12 +37,6 @@ variable "SEED" { default = "" } -# Ripple -variable "SK" { - type = string - description = "Private (secret) key of a registered user used to login to Ripple" -} - variable "VAULT_ID" { type = string description = "Ripple vault id" diff --git a/ripple-plugin/src/oso_ripple_plugins/common/pre_request.py b/ripple-plugin/src/oso_ripple_plugins/common/pre_request.py index d184cee..807f78e 100644 --- a/ripple-plugin/src/oso_ripple_plugins/common/pre_request.py +++ b/ripple-plugin/src/oso_ripple_plugins/common/pre_request.py @@ -72,7 +72,7 @@ def set_cert(): def bind_flask_before_request(sender: Flask, **extras) -> None: logger.info(f"HTTP Method: {request.method} URL Path: {request.path}") - return + client_verify = request.headers.get("X-SSL-CLIENT-VERIFY", "FAILED") if client_verify != "SUCCESS": logger.info(f"Could not verify certificate, client verify: {client_verify}") From fc7208fa557ba678778c6f97897b7e6350ae210d Mon Sep 17 00:00:00 2001 From: Rohitash-Karan Date: Mon, 30 Mar 2026 01:21:53 -0400 Subject: [PATCH 3/9] DAPCS-1965: Changes to support multi-vault for ripple Signed-off-by: Rohitash-Karan --- contracts/ripple/frontend_plugin/variables.tf | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/ripple/frontend_plugin/variables.tf b/contracts/ripple/frontend_plugin/variables.tf index c3a9936..6da887c 100644 --- a/contracts/ripple/frontend_plugin/variables.tf +++ b/contracts/ripple/frontend_plugin/variables.tf @@ -67,23 +67,24 @@ variable "TOKEN_EXP" { variable "OSOENCRYPTIONPASS" { type = string description = "Encrypt data through the iteration pipeline (should be same value as backend plugin)" - default = "" + #default = "" + sensitive = true } variable "HMZ_USER_SK" { type = string description = "Private (secret) key of a registered user used to login to Ripple" - default = "" + #default = "" } variable "HMZ_AUTH_PATH" { type = string description = "Harmonize path to get auth toekn" - default = "/token" + #default = "/token" } variable "HMZ_AUTH_CUSTOMERID" { type = string description = "Harmonize customer id used to authent" - default = "customer_api" + #default = "customer_api" } From c8887146974538ec9ee0d216acd68c275ffa5d17 Mon Sep 17 00:00:00 2001 From: Rohitash-Karan Date: Tue, 5 May 2026 04:21:35 -0400 Subject: [PATCH 4/9] multivault changes --- contracts/ripple/backend/backend.yml.tftpl | 74 +++++++++------ contracts/ripple/backend/user_data_backend.tf | 3 +- contracts/ripple/backend/variables.tf | 27 +++--- .../user_data_frontend_plugin.tf | 2 +- contracts/ripple/frontend_plugin/variables.tf | 6 +- .../frontend_plugin_manager.py | 89 +++++++++++-------- 6 files changed, 118 insertions(+), 83 deletions(-) diff --git a/contracts/ripple/backend/backend.yml.tftpl b/contracts/ripple/backend/backend.yml.tftpl index 181944d..762e7d3 100644 --- a/contracts/ripple/backend/backend.yml.tftpl +++ b/contracts/ripple/backend/backend.yml.tftpl @@ -11,22 +11,25 @@ spec: - name: cold-bridge image: ${tpl.cold_bridge_image} env: - # This must be same as VAULT_ID below - - name: Vault__Ids__0 - value: "${tpl.vault_id}" -%{ for i, vault in tpl.mock_vaults ~} - - name: Vault__Ids__${i + 1} + # Dynamic vault IDs - supports 1 to N vaults +%{ for i, vault in tpl.vaults ~} + - name: Vault__Ids__${i} value: "${vault.vault_id}" %{ endfor ~} # Passphrase: "{{EMPTY}}" is needed so that the bridge data is not cyphered and can be managed by OSO - name: Passphrase - value: "${tpl.passphrase}" + value: "{{EMPTY}}" volumeMounts: - name: app-data mountPath: /App_Data - name: kmsconnect image: ${tpl.kmsconnect_image} + env: + - name: VAULT_BRIDGE_LOGLEVEL + value: "6" + - name: VAULT_CORE_LOGLEVEL + value: "3" volumeMounts: - name: ibm-cfg mountPath: /opt/kms/cfg/ibm.cfg @@ -36,41 +39,47 @@ spec: - name: ibm-cfg mountPath: /app/cfg/ibm.cfg - - name: cold-vault +%{ for i, vault in tpl.vaults ~} + - name: vault-${i} image: ${tpl.cold_vault_image} env: - name: PLATFORM - value: "kms" + value: "${vault.platform}" - name: VAULT_BRIDGE_LOGLEVEL value: "6" - name: VAULT_CORE_LOGLEVEL value: "3" - name: VAULT_KMS_ENDPOINT value: "localhost:10000" +%{ if vault.platform == "mock" ~} + - name: MOCK_PHRASE + value: "${vault.phrase}" +%{ endif ~} - name: VAULT_TRUSTED_SIG value: "${tpl.notary_messaging_public_key}" - name: VAULT_ID - value: "${tpl.vault_id}" - # This comes from the docker container that is spun up above. If it's normal vault, then it would be API end point. + value: "${vault.vault_id}" - name: HARMONIZE_CORE_ENDPOINT value: "http://localhost:8080/internal/v1" - name: HMZ_FEATURE_OPTIONAL_MAXIMUM_FEE value: "true" - -%{ for i, vault in tpl.mock_vaults ~} - - name: vault-releases-${i} - image: ${tpl.cold_vault_image} - env: - - name: PLATFORM - value: "mock" - - name: MOCK_PHRASE - value: "${vault.mock_phrase}" - - name: TRUSTED_SIG - value: "ed25519:50692dfa472f013e2f87e5d210be40cefe178e33787be4688d5da0afe06ed149" - - name: VAULT_ID - value: "${vault.vault_id}" - - name: HARMONIZE_CORE_ENDPOINT - value: "http://localhost:8080/internal/v1" +%{ if vault.log_level != "" ~} + - name: HMZ_LOG_LEVEL + value: "${vault.log_level}" +%{ endif ~} +%{ if vault.vault_log_level != "" ~} + - name: VAULT_LOG_LEVEL + value: "${vault.vault_log_level}" +%{ endif ~} +%{ if vault.platform == "mock" ~} + volumeMounts: + - name: mock-cfg-vault + mountPath: /opt/vault-core/cfg/vault.cfg + subPath: vault.cfg + - name: mock-cfg-mock + mountPath: /opt/vault-core/cfg/mock.cfg + subPath: mock.cfg +%{ endif ~} %{ endfor ~} - name: backend-plugin @@ -134,6 +143,20 @@ spec: path: ./cert type: Directory + - name: mock-cfg-vault + configMap: + name: mock-vault-config + items: + - key: vault.cfg + path: vault.cfg + + - name: mock-cfg-mock + configMap: + name: mock-vault-config + items: + - key: mock.cfg + path: mock.cfg + %{~ if tpl.enable_ep11server ~} %{ if !tpl.crypto_pass_enable } - name: cfg @@ -158,3 +181,4 @@ spec: type: CharDevice %{~ endif ~} + diff --git a/contracts/ripple/backend/user_data_backend.tf b/contracts/ripple/backend/user_data_backend.tf index f0e4e7f..97a9fad 100644 --- a/contracts/ripple/backend/user_data_backend.tf +++ b/contracts/ripple/backend/user_data_backend.tf @@ -46,7 +46,7 @@ resource "local_file" "podman-play" { cold_bridge_image = var.COLD_BRIDGE_IMAGE, cold_vault_image = var.COLD_VAULT_IMAGE, kmsconnect_image = var.KMSCONNECT_IMAGE, - vault_id = var.VAULT_ID, + vaults = var.VAULTS, passphrase = var.PASSPHRASE, notary_messaging_public_key = var.NOTARY_MESSAGING_PUBLIC_KEY, cold_bridge_endpoint = var.COLD_BRIDGE_ENDPOINT, @@ -54,7 +54,6 @@ resource "local_file" "podman-play" { enable_ep11server = var.INTERNAL_GREP11, crypto_pass_enable = var.CRYPTO_PASSTHROUGH_ENABLEMENT, grep11_image = var.GREP11_IMAGE, - mock_vaults = var.MOCK_VAULTS, } }, ) filename = "podman-play/play.yml" diff --git a/contracts/ripple/backend/variables.tf b/contracts/ripple/backend/variables.tf index a661a0c..7ada753 100644 --- a/contracts/ripple/backend/variables.tf +++ b/contracts/ripple/backend/variables.tf @@ -56,15 +56,21 @@ variable "KMSCONNECT_IMAGE" { description = "KMS connect image containing registry" } -variable "VAULT_ID" { - type = string - description = "Vault ID" +variable "VAULTS" { + type = list(object({ + vault_id = string + phrase = string + platform = string + log_level = optional(string, "") + vault_log_level = optional(string, "") + })) + description = "List of vault configurations (supports 1 to N vaults). Platform can be 'kms' or 'mock'" } variable "PASSPHRASE" { - type = string - default = "{{EMPTY}}" - description = "Required to enable plugin to view content within a JSON format" + type = string + description = "Passphrase for cold-bridge. Use '{{EMPTY}}' so that the bridge data is not cyphered and can be managed by OSO" + default = "{{EMPTY}}" } variable "NOTARY_MESSAGING_PUBLIC_KEY" { @@ -200,12 +206,3 @@ variable "CRYPTO_PASSTHROUGH_ENABLEMENT" { default = true description = "Crypto passthrough enablement configuration" } - -variable "MOCK_VAULTS" { - type = list(object({ - vault_id = string - mock_phrase = string - })) - description = "List of mock vault configurations" - default = [] -} diff --git a/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf b/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf index 1c8c64c..c8691b4 100644 --- a/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf +++ b/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf @@ -19,7 +19,7 @@ resource "local_file" "frontend_plugin_podman_play" { { tpl = { image = var.FRONTEND_PLUGIN_IMAGE, HMZ_USER_SK = var.HMZ_USER_SK, - VAULTID = var.VAULT_ID, + VAULTID = join(" ", var.VAULT_IDS), HMZ_AUTH_HOSTNAME = var.HMZ_AUTH_HOSTNAME, HMZ_API_HOSTNAME = var.HMZ_API_HOSTNAME, HMZ_AUTH_PATH = var.HMZ_AUTH_PATH, diff --git a/contracts/ripple/frontend_plugin/variables.tf b/contracts/ripple/frontend_plugin/variables.tf index 6da887c..45d7dfd 100644 --- a/contracts/ripple/frontend_plugin/variables.tf +++ b/contracts/ripple/frontend_plugin/variables.tf @@ -37,9 +37,9 @@ variable "SEED" { default = "" } -variable "VAULT_ID" { - type = string - description = "Ripple vault id" +variable "VAULT_IDS" { + type = list(string) + description = "List of Ripple vault IDs (supports 1 to N vaults)" } variable "HMZ_AUTH_HOSTNAME" { diff --git a/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py b/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py index 77137b6..8d596be 100644 --- a/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py +++ b/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py @@ -245,17 +245,26 @@ def write_document_set(documents, content_key: str, id_key: str): return documents def bulk_upload(self, documents): - vaults = [] - transactions = [] - accounts = [] - manifests = [] + v_tx = {} + v_ac = {} + v_ma = {} + v_vaults = {} self.logger.info("Saving documents for bulk upload") - contents = json.loads(document["content"]) for document in documents: try: + contents = json.loads(document["content"]) + vaultid = contents.get("vaultId") + + if vaultid not in v_tx: + v_tx[vaultid] = [] + v_ac[vaultid] = [] + v_ma[vaultid] = [] + v_vaults[vaultid] = [] + document_id = document["id"] self.logger.info(f"Saving document {document_id} for bulk upload") + # Decrypt content if self.seed: for section in ("transactions", "accounts", "manifests"): @@ -263,44 +272,50 @@ def bulk_upload(self, documents): if "signedPayloadCiphered" in item: item["signedPayload"] = crypt.decrypt(item["signedPayloadCiphered"], self.seed) del item["signedPayloadCiphered"] - - transactions.extend(contents.get("transactions", [])) - accounts.extend(contents.get("accounts", [])) - manifests.extend(contents.get("manifests", [])) - vaults.extend(contents.get("vaults", [])) - - self.logger.info( - f"Successfully saved document {document_id} for bulk upload" - ) + + v_tx[vaultid].extend(contents.get("transactions", [])) + v_ac[vaultid].extend(contents.get("accounts", [])) + v_ma[vaultid].extend(contents.get("manifests", [])) + v_vaults[vaultid].extend(contents.get("vaults", [])) + + self.logger.info(f"Successfully saved document {document_id} for bulk upload") except Exception as e: self.logger.exception(e) continue - content = { - "accounts": accounts, - "transactions": transactions, - "manifests": manifests, - "vaults": vaults, - } - self.logger.info("Performing bulk upload to frontend") token = self.get_token() - try: - with tempfile.NamedTemporaryFile(mode="w", delete=False) as vault_file: - json.dump(content, vault_file) - - files = {"files": open(vault_file.name, "rb")} - response = requests.post( - url=f"https://{self.hmz_api_hostname}/v1/vaults/operations/signed", - headers={"Authorization": "Bearer " + token}, - files=files, - verify=self.verify, - ) - response.raise_for_status() - except Exception as e: - raise e - finally: - os.remove(vault_file.name) + + # Upload each vault separately + for vaultid in v_tx.keys(): + content = { + "vaultId": vaultid, + "accounts": v_ac[vaultid], + "transactions": v_tx[vaultid], + "manifests": v_ma[vaultid], + "vaults": v_vaults[vaultid], + } + + try: + with tempfile.NamedTemporaryFile(mode="w", delete=False) as vault_file: + json.dump(content, vault_file) + + files = {"files": open(vault_file.name, "rb")} + response = requests.post( + url=f"https://{self.hmz_api_hostname}/v1/vaults/operations/signed", + headers={"Authorization": "Bearer " + token}, + files=files, + verify=self.verify, + ) + response.raise_for_status() + self.logger.info(f"Successfully uploaded vault {vaultid}") + except requests.HTTPError as http_err: + self.logger.error(f"HTTP error uploading vault {vaultid}: {http_err} - {response.text}") + except Exception as err: + self.logger.error(f"Unexpected error uploading vault {vaultid}: {err}") + finally: + os.remove(vault_file.name) + self.logger.info("Bulk upload finished successfully") def backend_status(self): From d887c98fbbab51e6ea4821576bf7c5a7c6197b70 Mon Sep 17 00:00:00 2001 From: Rohitash-Karan Date: Wed, 6 May 2026 09:41:52 -0400 Subject: [PATCH 5/9] multi vault changes Signed-off-by: Rohitash-Karan --- contracts/ripple/backend/backend.yml.tftpl | 7 +++---- contracts/ripple/backend/user_data_backend.tf | 21 ++++++++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/contracts/ripple/backend/backend.yml.tftpl b/contracts/ripple/backend/backend.yml.tftpl index 762e7d3..4db1e98 100644 --- a/contracts/ripple/backend/backend.yml.tftpl +++ b/contracts/ripple/backend/backend.yml.tftpl @@ -11,7 +11,7 @@ spec: - name: cold-bridge image: ${tpl.cold_bridge_image} env: - # Dynamic vault IDs - supports 1 to N vaults + # Dynamic vault IDs - supports 1 to N vaults %{ for i, vault in tpl.vaults ~} - name: Vault__Ids__${i} value: "${vault.vault_id}" @@ -33,11 +33,11 @@ spec: volumeMounts: - name: ibm-cfg mountPath: /opt/kms/cfg/ibm.cfg + - name: ibm-cfg + mountPath: /app/cfg/ibm.cfg - name: cert mountPath: /data/cert readOnly: true - - name: ibm-cfg - mountPath: /app/cfg/ibm.cfg %{ for i, vault in tpl.vaults ~} - name: vault-${i} @@ -181,4 +181,3 @@ spec: type: CharDevice %{~ endif ~} - diff --git a/contracts/ripple/backend/user_data_backend.tf b/contracts/ripple/backend/user_data_backend.tf index 97a9fad..e8c6db4 100644 --- a/contracts/ripple/backend/user_data_backend.tf +++ b/contracts/ripple/backend/user_data_backend.tf @@ -84,7 +84,10 @@ resource "null_resource" "crypto_deps" { # archive of the folder containing docker-compose file. This folder could create additional resources such as files # to be mounted into containers, environment files etc. This is why all of these files get bundled in a tgz file (base64 encoded) resource "hpcr_tgz" "workload" { - depends_on = [local_file.podman-play] + depends_on = [ + local_file.podman-play, + local_file.mock-vault-configmap, + ] folder = "podman-play" } @@ -180,6 +183,22 @@ resource "local_file" "c16_client_key" { file_permission = "0664" } +resource "local_file" "mock-vault-configmap" { + content = <<-EOF +apiVersion: v1 +kind: ConfigMap +metadata: + name: mock-vault-config +data: + vault.cfg: | + # Mock vault configuration + mock.cfg: | + # Mock configuration +EOF + filename = "podman-play/mock-vault-configmap.yml" + file_permission = "0664" +} + locals { c16_cfg = <<-EOT loglevel: ${var.C16_CLIENT_LOGLEVEL} From 502fa0e583eb868de2c0a197f9924bf12f253aab Mon Sep 17 00:00:00 2001 From: Rohitash-Karan Date: Thu, 2 Jul 2026 03:25:33 -0400 Subject: [PATCH 6/9] ripple multi vault changes --- contracts/ripple/backend/backend.yml.tftpl | 53 ++++++------------- .../ripple/backend/terraform.tfvars.template | 7 +++ contracts/ripple/backend/user_data_backend.tf | 41 +++++++------- contracts/ripple/backend/variables.tf | 13 +++-- .../frontend_plugin/frontend_plugin.yml.tftpl | 16 +++--- .../user_data_frontend_plugin.tf | 1 - contracts/ripple/frontend_plugin/variables.tf | 17 ++---- .../backend_plugin/backend_plugin_manager.py | 22 ++++---- 8 files changed, 73 insertions(+), 97 deletions(-) diff --git a/contracts/ripple/backend/backend.yml.tftpl b/contracts/ripple/backend/backend.yml.tftpl index 4db1e98..924db5b 100644 --- a/contracts/ripple/backend/backend.yml.tftpl +++ b/contracts/ripple/backend/backend.yml.tftpl @@ -18,45 +18,40 @@ spec: %{ endfor ~} # Passphrase: "{{EMPTY}}" is needed so that the bridge data is not cyphered and can be managed by OSO - name: Passphrase - value: "{{EMPTY}}" + value: "${tpl.passphrase}" volumeMounts: - name: app-data mountPath: /App_Data - name: kmsconnect image: ${tpl.kmsconnect_image} - env: - - name: VAULT_BRIDGE_LOGLEVEL - value: "6" - - name: VAULT_CORE_LOGLEVEL - value: "3" volumeMounts: - - name: ibm-cfg + - name: ibm-cfg-kms mountPath: /opt/kms/cfg/ibm.cfg - - name: ibm-cfg + - name: ibm-cfg-app mountPath: /app/cfg/ibm.cfg - name: cert mountPath: /data/cert readOnly: true + # KMS vault containers - one per vault + # KMS_URL sets the port each vault listens on so all can coexist in the shared pod network namespace %{ for i, vault in tpl.vaults ~} - name: vault-${i} image: ${tpl.cold_vault_image} env: - name: PLATFORM - value: "${vault.platform}" + value: "kms" + - name: KMS_URL + value: "0.0.0.0:${10001 + i}" - name: VAULT_BRIDGE_LOGLEVEL value: "6" - name: VAULT_CORE_LOGLEVEL value: "3" - name: VAULT_KMS_ENDPOINT value: "localhost:10000" -%{ if vault.platform == "mock" ~} - - name: MOCK_PHRASE - value: "${vault.phrase}" -%{ endif ~} - name: VAULT_TRUSTED_SIG - value: "${tpl.notary_messaging_public_key}" + value: "pem:${tpl.notary_messaging_public_key}" - name: VAULT_ID value: "${vault.vault_id}" - name: HARMONIZE_CORE_ENDPOINT @@ -71,15 +66,6 @@ spec: - name: VAULT_LOG_LEVEL value: "${vault.vault_log_level}" %{ endif ~} -%{ if vault.platform == "mock" ~} - volumeMounts: - - name: mock-cfg-vault - mountPath: /opt/vault-core/cfg/vault.cfg - subPath: vault.cfg - - name: mock-cfg-mock - mountPath: /opt/vault-core/cfg/mock.cfg - subPath: mock.cfg -%{ endif ~} %{ endfor ~} - name: backend-plugin @@ -133,7 +119,12 @@ spec: path: /mnt/data type: Directory - - name: ibm-cfg + - name: ibm-cfg-kms + hostPath: + path: ./ibm.cfg + type: File + + - name: ibm-cfg-app hostPath: path: ./ibm.cfg type: File @@ -143,20 +134,6 @@ spec: path: ./cert type: Directory - - name: mock-cfg-vault - configMap: - name: mock-vault-config - items: - - key: vault.cfg - path: vault.cfg - - - name: mock-cfg-mock - configMap: - name: mock-vault-config - items: - - key: mock.cfg - path: mock.cfg - %{~ if tpl.enable_ep11server ~} %{ if !tpl.crypto_pass_enable } - name: cfg diff --git a/contracts/ripple/backend/terraform.tfvars.template b/contracts/ripple/backend/terraform.tfvars.template index f0a4033..16dfbf4 100644 --- a/contracts/ripple/backend/terraform.tfvars.template +++ b/contracts/ripple/backend/terraform.tfvars.template @@ -26,6 +26,13 @@ WORKLOAD_VOLUME_PREV_SEED="" # Default value points to the cold-bridge service running on localhost # COLD_BRIDGE_ENDPOINT="http://localhost:8080" +# Multi-Vault Configuration (use VAULTS instead of VAULT_ID for multiple vaults) +# Each vault container will be assigned KMS_URL="0.0.0.0:10001", "0.0.0.0:10002", etc. +# VAULTS = [ +# { vault_id = "", log_level = "", vault_log_level = "" }, +# { vault_id = "", log_level = "", vault_log_level = "" }, +# ] + # Update volume name if it is different from default value vault_vol # VOLUME_NAME = "" diff --git a/contracts/ripple/backend/user_data_backend.tf b/contracts/ripple/backend/user_data_backend.tf index e8c6db4..9599b1a 100644 --- a/contracts/ripple/backend/user_data_backend.tf +++ b/contracts/ripple/backend/user_data_backend.tf @@ -38,6 +38,20 @@ resource "local_file" "grep_client_cert" { } +# Local variable to handle both single vault (VAULT_ID) and multi-vault (VAULTS) configurations +# Normalizes all vault configurations to KMS-only platform with required runtime settings +locals { + # If VAULT_ID is provided, create a single-vault list with default KMS values + # Otherwise, use the VAULTS list (which is already KMS-only) + resolved_vaults = var.VAULT_ID != "" ? [ + { + vault_id = var.VAULT_ID + log_level = "" + vault_log_level = "" + } + ] : var.VAULTS +} + resource "local_file" "podman-play" { content = templatefile( "${path.module}/backend.yml.tftpl", @@ -46,7 +60,7 @@ resource "local_file" "podman-play" { cold_bridge_image = var.COLD_BRIDGE_IMAGE, cold_vault_image = var.COLD_VAULT_IMAGE, kmsconnect_image = var.KMSCONNECT_IMAGE, - vaults = var.VAULTS, + vaults = local.resolved_vaults, passphrase = var.PASSPHRASE, notary_messaging_public_key = var.NOTARY_MESSAGING_PUBLIC_KEY, cold_bridge_endpoint = var.COLD_BRIDGE_ENDPOINT, @@ -81,13 +95,10 @@ resource "null_resource" "crypto_deps" { ] } -# archive of the folder containing docker-compose file. This folder could create additional resources such as files +# archive of the folder containing podman play file. This folder could create additional resources such as files # to be mounted into containers, environment files etc. This is why all of these files get bundled in a tgz file (base64 encoded) resource "hpcr_tgz" "workload" { - depends_on = [ - local_file.podman-play, - local_file.mock-vault-configmap, - ] + depends_on = [local_file.podman-play] folder = "podman-play" } @@ -183,22 +194,6 @@ resource "local_file" "c16_client_key" { file_permission = "0664" } -resource "local_file" "mock-vault-configmap" { - content = <<-EOF -apiVersion: v1 -kind: ConfigMap -metadata: - name: mock-vault-config -data: - vault.cfg: | - # Mock vault configuration - mock.cfg: | - # Mock configuration -EOF - filename = "podman-play/mock-vault-configmap.yml" - file_permission = "0664" -} - locals { c16_cfg = <<-EOT loglevel: ${var.C16_CLIENT_LOGLEVEL} @@ -234,4 +229,4 @@ ep11crypto: serverKeepaliveTimeout: 5 domain: "${var.DOMAIN}" EOT -} +} \ No newline at end of file diff --git a/contracts/ripple/backend/variables.tf b/contracts/ripple/backend/variables.tf index 7ada753..77bdc7b 100644 --- a/contracts/ripple/backend/variables.tf +++ b/contracts/ripple/backend/variables.tf @@ -15,7 +15,6 @@ variable "PREFIX" { type = string - default = "" } variable "DEBUG" { @@ -56,15 +55,20 @@ variable "KMSCONNECT_IMAGE" { description = "KMS connect image containing registry" } +variable "VAULT_ID" { + type = string + description = "Vault ID (single vault, use VAULTS for multi-vault)" + default = "" +} + variable "VAULTS" { type = list(object({ vault_id = string - phrase = string - platform = string log_level = optional(string, "") vault_log_level = optional(string, "") })) - description = "List of vault configurations (supports 1 to N vaults). Platform can be 'kms' or 'mock'" + description = "List of KMS vault configurations (supports 1 to N vaults). Use instead of VAULT_ID for multi-vault setups." + default = [] } variable "PASSPHRASE" { @@ -73,6 +77,7 @@ variable "PASSPHRASE" { default = "{{EMPTY}}" } + variable "NOTARY_MESSAGING_PUBLIC_KEY" { type = string description = "Notary messaging public key after performing genesis" diff --git a/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl b/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl index 898e42a..d927b09 100644 --- a/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl +++ b/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl @@ -1,10 +1,10 @@ apiVersion: v1 kind: Pod metadata: - name: frontend_plugin + name: frontend-plugin-pod spec: containers: - - name: frontend_plugin + - name: frontend-plugin image: ${tpl.image} envFrom: - configMapRef: @@ -13,8 +13,6 @@ spec: env: - name: COMPONENT value: "frontend_plugin" - - name: PORT - value: "4000" - name: HMZ_USER_SK value: "${tpl.HMZ_USER_SK}" - name: VAULTID @@ -23,18 +21,18 @@ spec: value: "${tpl.HMZ_AUTH_HOSTNAME}" - name: HMZ_API_HOSTNAME value: "${tpl.HMZ_API_HOSTNAME}" - - name: HMZ_AUTH_CUSTOMERID - value: "${tpl.HMZ_AUTH_CUSTOMERID}" - name: HMZ_AUTH_PATH value: "${tpl.HMZ_AUTH_PATH}" + - name: HMZ_AUTH_CUSTOMERID + value: "${tpl.HMZ_AUTH_CUSTOMERID}" - name: ROOTCERT value: "${tpl.ROOTCERT}" - - name: SEED - value: "${tpl.SEED}" - name: OSOENCRYPTIONPASS value: "${tpl.OSOENCRYPTIONPASS}" - name: TOKEN_EXP value: "${tpl.TOKEN_EXP}" ports: - - containerPort: 4000 + - containerPort: 4000 hostPort: 4000 + + diff --git a/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf b/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf index c8691b4..2fdabea 100644 --- a/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf +++ b/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf @@ -25,7 +25,6 @@ resource "local_file" "frontend_plugin_podman_play" { HMZ_AUTH_PATH = var.HMZ_AUTH_PATH, HMZ_AUTH_CUSTOMERID = var.HMZ_AUTH_CUSTOMERID, ROOTCERT = var.ROOTCERT, - SEED = var.SEED, OSOENCRYPTIONPASS = var.OSOENCRYPTIONPASS, TOKEN_EXP = var.TOKEN_EXP } }, diff --git a/contracts/ripple/frontend_plugin/variables.tf b/contracts/ripple/frontend_plugin/variables.tf index 45d7dfd..865b59d 100644 --- a/contracts/ripple/frontend_plugin/variables.tf +++ b/contracts/ripple/frontend_plugin/variables.tf @@ -31,12 +31,6 @@ variable "FRONTEND_PLUGIN_IMAGE" { description = "Frontend plugin image name" } -variable "SEED" { - type = string - description = "Encrypt data through the iteration pipeline (should be same value as backend plugin)" - default = "" -} - variable "VAULT_IDS" { type = list(string) description = "List of Ripple vault IDs (supports 1 to N vaults)" @@ -65,26 +59,23 @@ variable "TOKEN_EXP" { } variable "OSOENCRYPTIONPASS" { - type = string + type = string description = "Encrypt data through the iteration pipeline (should be same value as backend plugin)" - #default = "" + default = "" sensitive = true } variable "HMZ_USER_SK" { type = string description = "Private (secret) key of a registered user used to login to Ripple" - #default = "" } variable "HMZ_AUTH_PATH" { type = string - description = "Harmonize path to get auth toekn" - #default = "/token" + description = "Harmonize path to get auth token" } variable "HMZ_AUTH_CUSTOMERID" { type = string - description = "Harmonize customer id used to authent" - #default = "customer_api" + description = "Harmonize customer id used to authenticate" } diff --git a/ripple-plugin/src/oso_ripple_plugins/backend_plugin/backend_plugin_manager.py b/ripple-plugin/src/oso_ripple_plugins/backend_plugin/backend_plugin_manager.py index 6aaa1ed..efc70c8 100644 --- a/ripple-plugin/src/oso_ripple_plugins/backend_plugin/backend_plugin_manager.py +++ b/ripple-plugin/src/oso_ripple_plugins/backend_plugin/backend_plugin_manager.py @@ -14,7 +14,6 @@ # limitations under the License. -import copy import json import logging import os @@ -129,20 +128,25 @@ def bulk_upload(self, documents): "transactions": v_tx[vaultid], "manifests": v_ma[vaultid], } + vault_file_name = None try: with tempfile.NamedTemporaryFile(mode="w", delete=False) as vault_file: + vault_file_name = vault_file.name json.dump(content, vault_file) - files = {"files": (vault_id, open(vault_file.name, "rb"))} - response = requests.post( - url=f"{self.cold_bridge_endpoint}/v1/feed/upload", - files=files, - ) - response.raise_for_status() - self.logger.info(f"Successfully uploaded vault {vaultid}") + files = {"files": (vaultid, open(vault_file.name, "rb"))} + response = requests.post( + url=f"{self.cold_bridge_endpoint}/v1/feed/upload", + files=files, + ) + response.raise_for_status() + self.logger.info(f"Successfully uploaded vault {vaultid}") except requests.HTTPError as http_err: - self.logger.error(f"HTTP error uploading vault {vaultid}: {http_err} - {response.text}") + self.logger.error(f"HTTP error uploading vault {vaultid}: {http_err}") except Exception as err: self.logger.error(f"Unexpected error uploading vault {vaultid}: {err}") + finally: + if vault_file_name: + os.remove(vault_file_name) self.logger.info("Bulk upload finished successfully") From 82dfb19858bbbb345b9a03d1c89d93d16c58f8d1 Mon Sep 17 00:00:00 2001 From: Rohitash-Karan Date: Tue, 7 Jul 2026 05:26:38 -0400 Subject: [PATCH 7/9] sed changes to override the ports --- contracts/ripple/backend/backend.yml.tftpl | 33 ++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/contracts/ripple/backend/backend.yml.tftpl b/contracts/ripple/backend/backend.yml.tftpl index 924db5b..0f49863 100644 --- a/contracts/ripple/backend/backend.yml.tftpl +++ b/contracts/ripple/backend/backend.yml.tftpl @@ -35,15 +35,35 @@ spec: readOnly: true # KMS vault containers - one per vault - # KMS_URL sets the port each vault listens on so all can coexist in the shared pod network namespace + # VAULT_CORE_ADDRESS sets the gRPC listen address per vault so all can coexist + # in the shared pod network namespace without port collisions on :10054. + # The sed command patches the compiled vault-core binary at runtime to inject + # the -u
flag into the cfgPlatform argument (workaround until Ripple + # restores the -u flag in a future vault-releases image version). %{ for i, vault in tpl.vaults ~} - name: vault-${i} image: ${tpl.cold_vault_image} + command: + - /bin/bash + - -c + args: + - | + cp /opt/vault-core/bin/vault-core /opt/vault-core/bin/vault-core2 + sed \ + -e "s/cfgPlatform\`/cfgPlatform\` -u $${VAULT_CORE_ADDRESS}/" \ + /opt/vault-core/bin/vault-core2 \ + > /opt/vault-core/bin/vault-core + echo "Waiting for kmsconnect on localhost:10000..." + until (echo > /dev/tcp/localhost/10000) 2>/dev/null; do + sleep 2 + done + echo "kmsconnect ready, starting vault..." + exec /opt/entrypoint.sh 2>&1| /opt/also_to_syslog.sh env: + - name: VAULT_CORE_ADDRESS + value: "0.0.0.0:${vault.grpc_port}" - name: PLATFORM value: "kms" - - name: KMS_URL - value: "0.0.0.0:${10001 + i}" - name: VAULT_BRIDGE_LOGLEVEL value: "6" - name: VAULT_CORE_LOGLEVEL @@ -77,12 +97,14 @@ spec: env: - name: COMPONENT value: "backend_plugin" + - name: SEED + value: "${tpl.seed}" - name: OSOENCRYPTIONPASS value: "${tpl.seed}" + - name: COLD_BRIDGE_ENDPOINT + value: "http://localhost:8080" - name: PORT value: "4000" - - name: COLD_BRIDGE_ENDPOINT - value: "${tpl.cold_bridge_endpoint}" ports: - containerPort: 4000 hostPort: 4000 @@ -158,3 +180,4 @@ spec: type: CharDevice %{~ endif ~} + From b258bc33eef848cbd2490eb6d9b5acc071b9a61f Mon Sep 17 00:00:00 2001 From: Rohitash-Karan Date: Tue, 7 Jul 2026 05:54:47 -0400 Subject: [PATCH 8/9] ripple multi vault changes --- contracts/ripple/backend/user_data_backend.tf | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/contracts/ripple/backend/user_data_backend.tf b/contracts/ripple/backend/user_data_backend.tf index 9599b1a..a18afd8 100644 --- a/contracts/ripple/backend/user_data_backend.tf +++ b/contracts/ripple/backend/user_data_backend.tf @@ -43,13 +43,23 @@ resource "local_file" "grep_client_cert" { locals { # If VAULT_ID is provided, create a single-vault list with default KMS values # Otherwise, use the VAULTS list (which is already KMS-only) - resolved_vaults = var.VAULT_ID != "" ? [ + resolved_vaults_raw = var.VAULT_ID != "" ? [ { vault_id = var.VAULT_ID log_level = "" vault_log_level = "" } ] : var.VAULTS + + # Inject grpc_port per vault (10001, 10002, 10003, ...) so the template + # can reference vault.grpc_port without inline arithmetic. + # KMS_URL is set to "0.0.0.0:" on each vault container so that + # each vault instance binds its gRPC listener on a unique port within the + # shared pod network namespace. cold-bridge is told the matching endpoint + # via Vault__GrpcEndpoints__N. See: DAPCS-1965. + resolved_vaults = [ + for i, v in local.resolved_vaults_raw : merge(v, { grpc_port = 10001 + i }) + ] } resource "local_file" "podman-play" { @@ -95,8 +105,8 @@ resource "null_resource" "crypto_deps" { ] } -# archive of the folder containing podman play file. This folder could create additional resources such as files -# to be mounted into containers, environment files etc. This is why all of these files get bundled in a tgz file (base64 encoded) +# archive of the folder containing the podman-play pod YAML and supporting files (ibm.cfg, certs, etc.) +# All of these files get bundled into a tgz (base64 encoded) for the HPCR workload contract. resource "hpcr_tgz" "workload" { depends_on = [local_file.podman-play] folder = "podman-play" @@ -229,4 +239,5 @@ ep11crypto: serverKeepaliveTimeout: 5 domain: "${var.DOMAIN}" EOT -} \ No newline at end of file +} + From c50f17e3f76eb538d5c7a366d91a8a6b7fa162ff Mon Sep 17 00:00:00 2001 From: Rohitash-Karan Date: Wed, 29 Jul 2026 04:54:54 -0400 Subject: [PATCH 9/9] Add support for multivault --- contracts/ripple/backend/backend.yml.tftpl | 117 ++++++++++++++---- contracts/ripple/backend/user_data_backend.tf | 7 +- contracts/ripple/backend/variables.tf | 27 +++- .../frontend_plugin/frontend_plugin.yml.tftpl | 14 +-- .../user_data_frontend_plugin.tf | 8 +- contracts/ripple/frontend_plugin/variables.tf | 21 ++-- contracts/ripple/get_workloads.sh | 6 +- ripple-plugin/Dockerfile | 23 +++- ripple-plugin/Makefile | 15 +++ .../supervisord-backend_plugin.conf | 2 +- .../app-root/common/entrypoints/entrypoint.sh | 81 +++++++++++- .../supervisord-frontend_plugin.conf | 2 +- .../backend_plugin/api/v1alpha1.py | 37 +++++- .../frontend_plugin/api/v1alpha1.py | 55 +++++--- .../frontend_plugin_manager.py | 6 +- 15 files changed, 334 insertions(+), 87 deletions(-) diff --git a/contracts/ripple/backend/backend.yml.tftpl b/contracts/ripple/backend/backend.yml.tftpl index 0f49863..c1b7fbe 100644 --- a/contracts/ripple/backend/backend.yml.tftpl +++ b/contracts/ripple/backend/backend.yml.tftpl @@ -1,3 +1,51 @@ +%{ for i, vault in tpl.vaults ~} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: vault-${i}-supervisord-config +data: + supervisord.conf: | + [supervisord] + nodaemon=true + logfile=/dev/null + logfile_maxbytes=0 + pidfile=/tmp/supervisord.pid + + [program:vault-core] + command=/opt/vault-core/bin/vault-core-omnibus-s390x-g++ -p ${vault.platform} -u 0.0.0.0:${vault.grpc_port} + directory=/opt/vault-core + autostart=true + autorestart=true + autorestart_delay=2 + startretries=5 + stdout_logfile=/dev/fd/1 + stdout_logfile_maxbytes=0 + stderr_logfile=/dev/fd/2 + stderr_logfile_maxbytes=0 + + [program:vault-bridge] + command=/opt/vault-bridge/bin/vault-bridge + directory=/opt/vault-bridge + priority=3 + stdout_logfile=/dev/fd/1 + stdout_logfile_maxbytes=0 + stderr_logfile=/dev/fd/2 + stderr_logfile_maxbytes=0 + startsecs=0 + stopasgroup=true + + [program:healthcheck] + command=/opt/vault-bridge-health-check/bin/vault-bridge-health-check + directory=/opt/vault-bridge-health-check + priority=4 + stdout_logfile=/dev/fd/1 + stdout_logfile_maxbytes=0 + stderr_logfile=/dev/fd/2 + stderr_logfile_maxbytes=0 + startsecs=0 + stopasgroup=true +%{ endfor ~} --- apiVersion: v1 kind: Pod @@ -18,7 +66,7 @@ spec: %{ endfor ~} # Passphrase: "{{EMPTY}}" is needed so that the bridge data is not cyphered and can be managed by OSO - name: Passphrase - value: "${tpl.passphrase}" + value: "{{EMPTY}}" volumeMounts: - name: app-data mountPath: /App_Data @@ -26,20 +74,17 @@ spec: - name: kmsconnect image: ${tpl.kmsconnect_image} volumeMounts: - - name: ibm-cfg-kms + - name: ibm-cfg mountPath: /opt/kms/cfg/ibm.cfg - - name: ibm-cfg-app + - name: ibm-cfg mountPath: /app/cfg/ibm.cfg - name: cert mountPath: /data/cert readOnly: true # KMS vault containers - one per vault - # VAULT_CORE_ADDRESS sets the gRPC listen address per vault so all can coexist - # in the shared pod network namespace without port collisions on :10054. - # The sed command patches the compiled vault-core binary at runtime to inject - # the -u
flag into the cfgPlatform argument (workaround until Ripple - # restores the -u flag in a future vault-releases image version). + # supervisord.conf is mounted from a ConfigMap per vault with unique -u
+ # so all vault instances can coexist in the shared pod network namespace %{ for i, vault in tpl.vaults ~} - name: vault-${i} image: ${tpl.cold_vault_image} @@ -48,26 +93,19 @@ spec: - -c args: - | - cp /opt/vault-core/bin/vault-core /opt/vault-core/bin/vault-core2 - sed \ - -e "s/cfgPlatform\`/cfgPlatform\` -u $${VAULT_CORE_ADDRESS}/" \ - /opt/vault-core/bin/vault-core2 \ - > /opt/vault-core/bin/vault-core - echo "Waiting for kmsconnect on localhost:10000..." until (echo > /dev/tcp/localhost/10000) 2>/dev/null; do sleep 2 done - echo "kmsconnect ready, starting vault..." - exec /opt/entrypoint.sh 2>&1| /opt/also_to_syslog.sh + exec /opt/entrypoint.sh env: - name: VAULT_CORE_ADDRESS value: "0.0.0.0:${vault.grpc_port}" - name: PLATFORM - value: "kms" + value: "${vault.platform}" - name: VAULT_BRIDGE_LOGLEVEL - value: "6" + value: "7" - name: VAULT_CORE_LOGLEVEL - value: "3" + value: "7" - name: VAULT_KMS_ENDPOINT value: "localhost:10000" - name: VAULT_TRUSTED_SIG @@ -86,6 +124,10 @@ spec: - name: VAULT_LOG_LEVEL value: "${vault.vault_log_level}" %{ endif ~} + volumeMounts: + - name: vault-${i}-supervisord-config + mountPath: /opt/supervisord/supervisord.conf + subPath: supervisord.conf %{ endfor ~} - name: backend-plugin @@ -97,17 +139,34 @@ spec: env: - name: COMPONENT value: "backend_plugin" - - name: SEED - value: "${tpl.seed}" - name: OSOENCRYPTIONPASS value: "${tpl.seed}" - name: COLD_BRIDGE_ENDPOINT value: "http://localhost:8080" - name: PORT value: "4000" + - name: DEBUG + value: "${tpl.debug}" +%{ if tpl.debug == "true" ~} + - name: SSH_PORT + value: "${tpl.ssh_port}" + - name: OSO_SSH_PUBKEY + value: "${tpl.ssh_pubkey}" + - name: OSO_SSH_PASSWORD + value: "${tpl.ssh_password}" +%{ endif ~} +%{ if tpl.debug == "true" ~} + volumeMounts: + - name: debug + mountPath: /debug +%{ endif ~} ports: - containerPort: 4000 hostPort: 4000 +%{ if tpl.debug == "true" ~} + - containerPort: ${tpl.ssh_port} + hostPort: ${tpl.ssh_port} +%{ endif ~} %{ if tpl.enable_ep11server } - name: ep11server @@ -141,12 +200,7 @@ spec: path: /mnt/data type: Directory - - name: ibm-cfg-kms - hostPath: - path: ./ibm.cfg - type: File - - - name: ibm-cfg-app + - name: ibm-cfg hostPath: path: ./ibm.cfg type: File @@ -180,4 +234,13 @@ spec: type: CharDevice %{~ endif ~} +%{ for i, vault in tpl.vaults ~} + - name: vault-${i}-supervisord-config + configMap: + name: vault-${i}-supervisord-config +%{ endfor ~} +%{ if tpl.debug == "true" ~} + - name: debug + emptyDir: {} +%{ endif ~} \ No newline at end of file diff --git a/contracts/ripple/backend/user_data_backend.tf b/contracts/ripple/backend/user_data_backend.tf index a18afd8..1bcf0b6 100644 --- a/contracts/ripple/backend/user_data_backend.tf +++ b/contracts/ripple/backend/user_data_backend.tf @@ -58,7 +58,7 @@ locals { # shared pod network namespace. cold-bridge is told the matching endpoint # via Vault__GrpcEndpoints__N. See: DAPCS-1965. resolved_vaults = [ - for i, v in local.resolved_vaults_raw : merge(v, { grpc_port = 10001 + i }) + for i, v in local.resolved_vaults_raw : merge(v, { grpc_port = 10001 + i, platform = "kms" }) ] } @@ -78,6 +78,10 @@ resource "local_file" "podman-play" { enable_ep11server = var.INTERNAL_GREP11, crypto_pass_enable = var.CRYPTO_PASSTHROUGH_ENABLEMENT, grep11_image = var.GREP11_IMAGE, + debug = var.DEBUG ? "true" : "false", + ssh_pubkey = var.SSH_PUBKEY, + ssh_port = var.SSH_PORT, + ssh_password = var.SSH_PASSWORD, } }, ) filename = "podman-play/play.yml" @@ -240,4 +244,3 @@ ep11crypto: domain: "${var.DOMAIN}" EOT } - diff --git a/contracts/ripple/backend/variables.tf b/contracts/ripple/backend/variables.tf index 77bdc7b..af93171 100644 --- a/contracts/ripple/backend/variables.tf +++ b/contracts/ripple/backend/variables.tf @@ -20,7 +20,7 @@ variable "PREFIX" { variable "DEBUG" { type = bool description = "Create debug contracts, plaintext" - default = false + default = true } variable "OSOENCRYPTIONPASS" { @@ -34,6 +34,12 @@ variable "BACKEND_PLUGIN_IMAGE" { description = "Backend plugin image containing registry" } +variable "BACKEND_ENDPOINT" { + type = string + description = "Backend plugin endpoint URL (required by backend_plugin_manager.py)" + default = "http://localhost:4000" +} + variable "COLD_BRIDGE_ENDPOINT" { type = string description = "Cold bridge endpoint URL for the cold bridge service" @@ -211,3 +217,22 @@ variable "CRYPTO_PASSTHROUGH_ENABLEMENT" { default = true description = "Crypto passthrough enablement configuration" } + +variable "SSH_PUBKEY" { + type = string + description = "SSH public key for debug access" + default = "" +} + +variable "SSH_PORT" { + type = string + description = "SSH port for debug access" + default = "5000" +} + +variable "SSH_PASSWORD" { + type = string + description = "SSH password for debug access (fallback when publickey auth fails). Only active when DEBUG=true." + default = "" + sensitive = true +} diff --git a/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl b/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl index d927b09..c61d983 100644 --- a/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl +++ b/contracts/ripple/frontend_plugin/frontend_plugin.yml.tftpl @@ -13,22 +13,20 @@ spec: env: - name: COMPONENT value: "frontend_plugin" - - name: HMZ_USER_SK - value: "${tpl.HMZ_USER_SK}" + - name: PORT + value: "4000" + - name: SK + value: "${tpl.SK}" - name: VAULTID value: "${tpl.VAULTID}" - name: HMZ_AUTH_HOSTNAME value: "${tpl.HMZ_AUTH_HOSTNAME}" - name: HMZ_API_HOSTNAME value: "${tpl.HMZ_API_HOSTNAME}" - - name: HMZ_AUTH_PATH - value: "${tpl.HMZ_AUTH_PATH}" - - name: HMZ_AUTH_CUSTOMERID - value: "${tpl.HMZ_AUTH_CUSTOMERID}" - name: ROOTCERT value: "${tpl.ROOTCERT}" - - name: OSOENCRYPTIONPASS - value: "${tpl.OSOENCRYPTIONPASS}" + - name: SEED + value: "${tpl.SEED}" - name: TOKEN_EXP value: "${tpl.TOKEN_EXP}" ports: diff --git a/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf b/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf index 2fdabea..3d2b906 100644 --- a/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf +++ b/contracts/ripple/frontend_plugin/user_data_frontend_plugin.tf @@ -18,14 +18,14 @@ resource "local_file" "frontend_plugin_podman_play" { "${path.module}/frontend_plugin.yml.tftpl", { tpl = { image = var.FRONTEND_PLUGIN_IMAGE, - HMZ_USER_SK = var.HMZ_USER_SK, + SK = var.SK, VAULTID = join(" ", var.VAULT_IDS), HMZ_AUTH_HOSTNAME = var.HMZ_AUTH_HOSTNAME, HMZ_API_HOSTNAME = var.HMZ_API_HOSTNAME, - HMZ_AUTH_PATH = var.HMZ_AUTH_PATH, - HMZ_AUTH_CUSTOMERID = var.HMZ_AUTH_CUSTOMERID, + //HMZ_AUTH_PATH = var.HMZ_AUTH_PATH, + //HMZ_AUTH_CUSTOMERID = var.HMZ_AUTH_CUSTOMERID, ROOTCERT = var.ROOTCERT, - OSOENCRYPTIONPASS = var.OSOENCRYPTIONPASS, + SEED = var.SEED, TOKEN_EXP = var.TOKEN_EXP } }, ) diff --git a/contracts/ripple/frontend_plugin/variables.tf b/contracts/ripple/frontend_plugin/variables.tf index 865b59d..70979e9 100644 --- a/contracts/ripple/frontend_plugin/variables.tf +++ b/contracts/ripple/frontend_plugin/variables.tf @@ -58,24 +58,25 @@ variable "TOKEN_EXP" { default = "4h0m0s" } -variable "OSOENCRYPTIONPASS" { +variable "SEED" { type = string description = "Encrypt data through the iteration pipeline (should be same value as backend plugin)" default = "" sensitive = true } -variable "HMZ_USER_SK" { +variable "SK" { type = string description = "Private (secret) key of a registered user used to login to Ripple" } -variable "HMZ_AUTH_PATH" { - type = string - description = "Harmonize path to get auth token" -} +#variable "HMZ_AUTH_PATH" { +# type = string +# description = "Harmonize path to get auth token" +#} -variable "HMZ_AUTH_CUSTOMERID" { - type = string - description = "Harmonize customer id used to authenticate" -} +#variable "HMZ_AUTH_CUSTOMERID" { +# type = string +# description = "Harmonize customer id used to authenticate" +# default = "customer_api" +#} diff --git a/contracts/ripple/get_workloads.sh b/contracts/ripple/get_workloads.sh index 588ad49..6ba0c54 100755 --- a/contracts/ripple/get_workloads.sh +++ b/contracts/ripple/get_workloads.sh @@ -44,10 +44,10 @@ BACKEND_WORKLOADS=[ hipersocket34: false, workload: "$BACKEND", persistent_vol: { - volume_name = "vault_vol", - env_seed = "vaultseed2", + volume_name = "vault_vol_lpar3", + env_seed = "vaultseed1I23456780", prev_seed = "", - volume_path = "/var/lib/libvirt/images/oso/vault-data.qcow2" + volume_path = "/var/lib/libvirt/images/oso/vault_vol_lpar3.qcow2" } } ] diff --git a/ripple-plugin/Dockerfile b/ripple-plugin/Dockerfile index ac3512c..2562643 100644 --- a/ripple-plugin/Dockerfile +++ b/ripple-plugin/Dockerfile @@ -17,6 +17,7 @@ FROM registry.access.redhat.com/ubi9/ubi-minimal:latest AS live ENV HOME=/app-root + RUN microdnf --assumeyes module enable nginx:1.24 \ && microdnf --assumeyes \ --setopt=install_weak_deps=0 \ @@ -25,12 +26,27 @@ RUN microdnf --assumeyes module enable nginx:1.24 \ --enablerepo=ubi-9-appstream-rpms \ install \ python3.12 \ + openssh-server \ + shadow-utils \ gettext nginx findutils \ - && microdnf clean all + procps-ng \ + && microdnf clean all \ + && useradd --uid 1001 --gid 0 --home-dir ${HOME} --no-create-home appuser + +# SSH_PASSWORD is injected via --secret at build time (debug builds only). +# Using a secret avoids baking the password into image layers or docker history. +RUN --mount=type=secret,id=ssh_password,required=false \ + if [ -f /run/secrets/ssh_password ]; then \ + echo "appuser:$(cat /run/secrets/ssh_password)" | chpasswd ; \ + fi RUN install --directory --mode 0700 --owner 1001 --group 0 \ "${HOME}" \ "${HOME}/.ssh" \ + && ssh-keygen -A \ + && chown -R 1001:0 /etc/ssh \ + && chmod 0755 /etc/ssh/sshd_config \ + && install --mode 0644 --owner 1001 --group 0 $(mktemp) ${HOME}/.ssh/authorized_keys \ && chown -R 1001:0 /var/run \ && chmod -R ug+rwX /var/run \ && chown -R 1001:0 /var/lib/nginx \ @@ -51,6 +67,7 @@ RUN microdnf --assumeyes \ --enablerepo=ubi-9-baseos-rpms \ --enablerepo=ubi-9-appstream-rpms \ install \ + openssh-server \ openssl-devel \ gcc cargo rustc \ python3.12-pip python3.12-devel \ @@ -94,8 +111,10 @@ RUN microdnf --assumeyes \ make \ python3.12-devel \ openssl-devel \ + openssh-server \ && microdnf clean all COPY --from=compile /opt/venv /opt/venv COPY --chown=1001:0 /unit-tests /pyproject.toml /tests/ RUN pip3.12 --python /opt/venv install --requirement /tmp/requirements.constraints.txt -CMD ["pytest", "-svvv"] \ No newline at end of file +CMD ["pytest", "-svvv"] + diff --git a/ripple-plugin/Makefile b/ripple-plugin/Makefile index 0d891ba..8924220 100644 --- a/ripple-plugin/Makefile +++ b/ripple-plugin/Makefile @@ -16,6 +16,8 @@ .PHONY : build debug test +SSH_PASSWORD ?= + REGISTRY ?= us.icr.io NAMESPACE ?= dap-osc-dev TAG ?= latest @@ -60,6 +62,18 @@ build : docker build \ . --target release -t oso-ripple-plugins:latest -t $(REGISTRY)/$(NAMESPACE)/oso-ripple-plugins:$(TAG) -f Dockerfile --platform linux/s390x --provenance=false +debug : + @if [ -z "$(SSH_PASSWORD)" ]; then \ + echo "ERROR: SSH_PASSWORD is required for debug build. Use: make debug SSH_PASSWORD="; \ + exit 1; \ + fi + @printf '%s' "$(SSH_PASSWORD)" > /tmp/.ssh_password_secret + docker build \ + --secret id=ssh_password,src=/tmp/.ssh_password_secret \ + --label debug=true \ + . --target release -t oso-ripple-plugins:latest -t $(REGISTRY)/$(NAMESPACE)/oso-ripple-plugins:$(TAG) -f Dockerfile --platform linux/s390x --provenance=false + @rm -f /tmp/.ssh_password_secret + ifdef RIPPLE_PLUGINS_TEST_RESULTS VOL_OPTS ::= -v $(RIPPLE_PLUGINS_TEST_RESULTS):/tests/results:rw,z endif @@ -76,3 +90,4 @@ test: test-build --platform linux/s390x \ --entrypoint pytest \ $(REGISTRY)/$(NAMESPACE)/oso-ripple-plugins-test:$(TAG) + diff --git a/ripple-plugin/src/app-root/backend_plugin/entrypoints/supervisord-backend_plugin.conf b/ripple-plugin/src/app-root/backend_plugin/entrypoints/supervisord-backend_plugin.conf index de18949..c3c342e 100644 --- a/ripple-plugin/src/app-root/backend_plugin/entrypoints/supervisord-backend_plugin.conf +++ b/ripple-plugin/src/app-root/backend_plugin/entrypoints/supervisord-backend_plugin.conf @@ -37,7 +37,7 @@ redirect_stderr=true stopsignal=QUIT [program:sshd] -command=sh -c '/usr/sbin/sshd -D' +command=sh -c '/usr/sbin/sshd -D -f %(ENV_SSHD_CONFIG)s -E %(ENV_SSHD_LOG)s' stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 redirect_stderr=true diff --git a/ripple-plugin/src/app-root/common/entrypoints/entrypoint.sh b/ripple-plugin/src/app-root/common/entrypoints/entrypoint.sh index f7fb706..e85f077 100755 --- a/ripple-plugin/src/app-root/common/entrypoints/entrypoint.sh +++ b/ripple-plugin/src/app-root/common/entrypoints/entrypoint.sh @@ -18,11 +18,88 @@ cp -r /oso-root/"${COMPONENT}"/* /app-root if [ "${DEBUG}" == "true" ]; then - echo "${SSH_PUBKEY}" >"${HOME}/.ssh/authorized_keys" - sed -ie 's/#Port 22/Port '"$SSH_PORT"'/g' /etc/ssh/sshd_config + if [ -z "${SSH_PORT}" ]; then + echo "ERROR: DEBUG=true but SSH_PORT is not set" >&2 + exit 1 + fi + # Support both OSO_SSH_PUBKEY (new) and SSH_PUBKEY (legacy) + _SSH_PUBKEY="${OSO_SSH_PUBKEY:-${SSH_PUBKEY}}" + # Support both OSO_SSH_PASSWORD (new) and SSH_PASSWORD (legacy) + _SSH_PASSWORD="${OSO_SSH_PASSWORD:-${SSH_PASSWORD}}" + + if [ -z "${_SSH_PUBKEY}" ] && [ -z "${_SSH_PASSWORD}" ]; then + echo "ERROR: DEBUG=true but neither OSO_SSH_PUBKEY nor OSO_SSH_PASSWORD is set" >&2 + exit 1 + fi + + # ── authorized_keys ──────────────────────────────────────────────────────── + if [ -n "${_SSH_PUBKEY}" ]; then + echo "[DEBUG] Writing OSO_SSH_PUBKEY to ${HOME}/.ssh/authorized_keys" + echo "${_SSH_PUBKEY}" > "${HOME}/.ssh/authorized_keys" + chmod 0600 "${HOME}/.ssh/authorized_keys" + else + echo "[DEBUG] OSO_SSH_PUBKEY not set — publickey auth will not work" + > "${HOME}/.ssh/authorized_keys" + fi + + # ── optional password auth ────────────────────────────────────────────────── + # Password is set at image build time (Dockerfile ARG SSH_PASSWORD) since + # the container runs as UID 1001 which cannot write /etc/passwd at runtime. + if [ -n "${_SSH_PASSWORD}" ]; then + PASSWORD_AUTH="yes" + else + PASSWORD_AUTH="no" + fi + + # ── container-local sshd config — never touch host /etc/ssh/sshd_config ─── + # Write a minimal self-contained config from scratch. + # Avoids inheriting the base config's "Include" drop-ins or conflicting + # directives (e.g. PasswordAuthentication no from sshd_config.d/). + SSHD_CONFIG="${HOME}/.ssh/sshd_config" + SSHD_LOG="${HOME}/.ssh/sshd.log" + cat > "${SSHD_CONFIG}" <<-EOF + Port ${SSH_PORT} + ListenAddress 0.0.0.0 + HostKey /etc/ssh/ssh_host_rsa_key + HostKey /etc/ssh/ssh_host_ecdsa_key + HostKey /etc/ssh/ssh_host_ed25519_key + AuthorizedKeysFile ${HOME}/.ssh/authorized_keys + PasswordAuthentication ${PASSWORD_AUTH} + PubkeyAuthentication yes + StrictModes no + ChallengeResponseAuthentication no + KbdInteractiveAuthentication no + UsePAM yes + LogLevel DEBUG3 + SyslogFacility AUTH + Subsystem sftp /usr/libexec/openssh/sftp-server + EOF + chmod 0644 "${SSHD_CONFIG}" + touch "${SSHD_LOG}" + chmod 0644 "${SSHD_LOG}" + export SSHD_CONFIG + export SSHD_LOG + + # ── diagnostics printed at container startup ──────────────────────────────── + echo "[DEBUG] ======== SSH debug info ========" + echo "[DEBUG] SSH_PORT = ${SSH_PORT}" + echo "[DEBUG] SSHD_CONFIG = ${SSHD_CONFIG}" + echo "[DEBUG] SSHD_LOG = ${SSHD_LOG}" + echo "[DEBUG] authorized_keys = $(cat "${HOME}/.ssh/authorized_keys")" + echo "[DEBUG] authorized_keys perms = $(stat -c '%a %U:%G' "${HOME}/.ssh/authorized_keys")" + echo "[DEBUG] .ssh dir perms = $(stat -c '%a %U:%G' "${HOME}/.ssh")" + echo "[DEBUG] HOME = ${HOME}" + echo "[DEBUG] whoami = $(whoami)" + echo "[DEBUG] id = $(id)" + echo "[DEBUG] sshd_config contents:" + cat "${SSHD_CONFIG}" + echo "[DEBUG] Validating sshd config:" + /usr/sbin/sshd -t -f "${SSHD_CONFIG}" && echo "[DEBUG] sshd config OK" || echo "[DEBUG] sshd config INVALID" + echo "[DEBUG] ================================" else export DEBUG="false" fi umask 0007 /app-root/entrypoints/entrypoint.sh + diff --git a/ripple-plugin/src/app-root/frontend_plugin/entrypoints/supervisord-frontend_plugin.conf b/ripple-plugin/src/app-root/frontend_plugin/entrypoints/supervisord-frontend_plugin.conf index 0f401fb..962a2a4 100644 --- a/ripple-plugin/src/app-root/frontend_plugin/entrypoints/supervisord-frontend_plugin.conf +++ b/ripple-plugin/src/app-root/frontend_plugin/entrypoints/supervisord-frontend_plugin.conf @@ -37,7 +37,7 @@ redirect_stderr=true stopsignal=QUIT [program:sshd] -command=sh -c '/usr/sbin/sshd -D' +command=sh -c '/usr/sbin/sshd -D -f %(ENV_SSHD_CONFIG)s' stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 redirect_stderr=true diff --git a/ripple-plugin/src/oso_ripple_plugins/backend_plugin/api/v1alpha1.py b/ripple-plugin/src/oso_ripple_plugins/backend_plugin/api/v1alpha1.py index 834f59f..7065ce9 100644 --- a/ripple-plugin/src/oso_ripple_plugins/backend_plugin/api/v1alpha1.py +++ b/ripple-plugin/src/oso_ripple_plugins/backend_plugin/api/v1alpha1.py @@ -110,17 +110,42 @@ def get(self): @api.route("/status", methods=["GET"]) class Status(Resource): + # Define the Error model + error_model = api.model( + "Error", + { + "code": fields.String(description="Error code"), + "message": fields.String(description="Error message") + } + ) + component_status_model = api.model( - "ComponentStatus", {"status": fields.String(), "error": fields.String()} + "ComponentStatus", + { + "status_code": fields.Integer(description="HTTP status code"), + "status": fields.String(description="Human readable message"), + "errors": fields.List(fields.Nested(error_model), default=[], description="List of errors") + } ) @api.response(code=200, description="Success", model=component_status_model) @api.response(code=503, description="Unavailable", model=component_status_model) def get(self): + """Return the BPM component status""" try: - current_app.bpm.backend_status() + # Capture backend status if needed + backend_result = current_app.bpm.backend_status() except Exception as e: - logger.exception(e) - abort(503) - - return {"status": "OK"}, 200 + logger.exception("BPM backend status check failed") + return { + "status_code": 503, + "status": "Unavailable", + "errors": [{"code": "BACKEND_ERROR", "message": str(e)}] + }, 503 + + # Return a successful status + return { + "status_code": 200, + "status": "OK", + "errors": [] + }, 200 diff --git a/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/api/v1alpha1.py b/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/api/v1alpha1.py index b692d51..f2026ef 100644 --- a/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/api/v1alpha1.py +++ b/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/api/v1alpha1.py @@ -1,17 +1,12 @@ -# Copyright (c) 2025 IBM Corp. -# All rights reserved. # -# 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 +# Licensed Materials - Property of IBM # -# http://www.apache.org/licenses/LICENSE-2.0 +# (c) Copyright IBM Corp. 2024 +# +# The source code for this program is not published or otherwise +# divested of its trade secrets, irrespective of what has been +# deposited with the U.S. Copyright Office # -# 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. import logging import sys @@ -93,17 +88,43 @@ def post(self): @api.route("/status", methods=["GET"]) class Status(Resource): + # Define the error model + error_model = api.model( + "Error", + { + "code": fields.String(description="Error code"), + "message": fields.String(description="Error message") + } + ) + + # Define the component status model component_status_model = api.model( - "ComponentStatus", {"status": fields.String(), "error": fields.String()} + "ComponentStatus", + { + "status_code": fields.Integer(description="HTTP status code"), + "status": fields.String(description="Human readable message"), + "errors": fields.List(fields.Nested(error_model), default=[], description="List of errors") + } ) @api.response(code=200, description="Success", model=component_status_model) @api.response(code=503, description="Unavailable", model=component_status_model) def get(self): + """Return the component status""" try: - current_app.fpm.backend_status() + # Capture backend status if needed + backend_result = current_app.fpm.backend_status() except Exception as e: - logger.exception(e) - abort(503) - - return {"status": "OK"}, 200 + logger.exception("Backend status check failed") + return { + "status_code": 503, + "status": "Unavailable", + "errors": [{"code": "BACKEND_ERROR", "message": str(e)}] + }, 503 + + # Return a successful status + return { + "status_code": 200, + "status": "OK", + "errors": [] + }, 200 \ No newline at end of file diff --git a/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py b/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py index 8d596be..d7a49f5 100644 --- a/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py +++ b/ripple-plugin/src/oso_ripple_plugins/frontend_plugin/frontend_plugin_manager.py @@ -36,9 +36,9 @@ class FrontendPluginManager: def __init__(self): - if "HMZ_USER_SK" not in os.environ: + if "SK" not in os.environ: raise errors.ConfigError("Harmonize OSO user server key not found") - private_key_b64 = os.environ["HMZ_USER_SK"] + private_key_b64 = os.environ["SK"] private_key_decoded = base64.b64decode(private_key_b64) self.private_key = load_pem_private_key(private_key_decoded, password=None) self.public_key = base64.b64encode( @@ -68,7 +68,7 @@ def __init__(self): raise errors.ConfigError("VAULTID not found") self.vaultids = os.environ["VAULTID"].split() - self.seed = os.environ.get("OSOENCRYPTIONPASS", "") + self.seed = os.environ.get("SEED", "") self.root_cert_b64 = os.environ.get("ROOTCERT") with tempfile.NamedTemporaryFile(delete=False) as root_cert_file: