From 9436eab1298da252ebe6f59cefdc1b2d3f2c5ea9 Mon Sep 17 00:00:00 2001 From: Nitesh Yadav Date: Tue, 29 Sep 2026 17:01:07 +0530 Subject: [PATCH] feat(security): seed a fixed default platform API key via migration instead of a random one at runtime Replaces the previous DefaultApiClientBootstrapper (ApplicationRunner minting a random key on first startup) with a plain Liquibase seed on the tenant-store database: one fixed, well-known api_key_hash row, inserted once via migration 0012, same on every deployment and every tenant -- exactly like this fork already seeds default codes/permissions, rather than generating a secret at runtime. The trade-off is explicit in the migration's own comment: a fixed key checked into source is a bootstrap convenience, not a secret, and any deployment beyond local/dev must rotate it (insert a new hash, deactivate this row) before going live -- rotation is a direct m_api_client write, same as the seed itself, since POST /v1/apiclients stays disabled. Co-Authored-By: Claude Sonnet 5 --- .../DefaultApiClientBootstrapper.java | 83 ------------------- .../tenant-store/changelog-tenant-store.xml | 1 + .../0012_seed_default_platform_api_client.xml | 62 ++++++++++++++ 3 files changed, 63 insertions(+), 83 deletions(-) delete mode 100644 fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/apiclient/DefaultApiClientBootstrapper.java create mode 100644 fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0012_seed_default_platform_api_client.xml diff --git a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/apiclient/DefaultApiClientBootstrapper.java b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/apiclient/DefaultApiClientBootstrapper.java deleted file mode 100644 index 076a56554..000000000 --- a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/security/apiclient/DefaultApiClientBootstrapper.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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. - */ -package org.apache.fineract.infrastructure.security.apiclient; - -import java.util.Map; -import lombok.RequiredArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.ApplicationArguments; -import org.springframework.boot.ApplicationRunner; -import org.springframework.stereotype.Component; - -/** - * Mints the very first platform-operator API key on a brand-new deployment, once, at startup -- the - * counterpart to {@link ApiClientAdminApiResource} deliberately disabling {@code POST /v1/apiclients}. - * With that HTTP path closed for security (see that class's javadoc), a fresh {@code m_api_client} - * table with zero rows would otherwise leave a first-time deployer with no way at all to reach - * {@code #/platform-admin/menu-access} except hand-writing SQL themselves -- this closes that gap - * without reopening the escalation path the disabled endpoint existed to close. - * - * Runs as an {@link ApplicationRunner}, which Spring Boot only invokes after the context has fully - * refreshed -- i.e. strictly after {@code TenantDatabaseUpgradeService} (an {@code InitializingBean}) - * has already created {@code m_api_client} via migration 0011. The guard is simply "does the table have - * any row yet": on every startup after the first, this is a no-op, so it never mints a second key - * behind anyone's back. - * - * The raw key is deliberately never written to the database (only its hash is, same as any other key) - * and never returned over HTTP -- it is logged ONCE, here, to this server's own application log. That - * is a real security boundary, not a formality: reading it requires access to the server's own log - * output, which is a materially different (and much smaller) trust boundary than "reachable over the - * network," which is exactly why the HTTP minting path was closed instead of left open. - */ -@Component -@RequiredArgsConstructor -public class DefaultApiClientBootstrapper implements ApplicationRunner { - - private static final Logger LOG = LoggerFactory.getLogger(DefaultApiClientBootstrapper.class); - - private final ApiClientCredentialService credentialService; - - @Override - public void run(final ApplicationArguments args) { - if (!credentialService.listClients().isEmpty()) { - return; - } - final Map created = credentialService.createClient("Default Platform Operator (auto-created on first startup)", - null, null); - final String rawKey = (String) created.get("apiKey"); - - LOG.warn(""" - - ================================================================================ - CBSNexora: first-run platform operator API key created. - - This key unlocks #/platform-admin/menu-access on this deployment. It is stored - only as a hash in m_api_client -- this is the ONLY place the raw value is ever - shown. Copy it now; it cannot be recovered later, only reissued as a new key. - - API Key: {} - - Minting a replacement over HTTP is deliberately disabled (see - ApiClientAdminApiResource) -- a further key can only be inserted directly into - m_api_client in the tenant-store database, the same way this one was. - ================================================================================ - """, rawKey); - } -} diff --git a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml index 852255252..0c4578281 100644 --- a/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml +++ b/fineract-provider/src/main/resources/db/changelog/tenant-store/changelog-tenant-store.xml @@ -32,4 +32,5 @@ + diff --git a/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0012_seed_default_platform_api_client.xml b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0012_seed_default_platform_api_client.xml new file mode 100644 index 000000000..a75be07a2 --- /dev/null +++ b/fineract-provider/src/main/resources/db/changelog/tenant-store/parts/0012_seed_default_platform_api_client.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + SELECT COUNT(*) FROM m_api_client + WHERE api_key_hash = '9d6d4dfa7a1953e33e0acde4fd775aad11689ec540340c7224796447118a3edd' + + + + + + + + + + + + + +