Files
qbit-secured-data/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataQBitConfig.java
T
james.maesandClaude Opus 5.5 0dec9463a6 chore: use core field access and audit-token APIs directly; remove stand-ins
QQQ #1172 and #1182 are released, so the standin package and the
reflective core bridges go; the resolver, key source and audit protector
register through core metadata, and the core-dependent tests run
unconditionally.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:10:18 -05:00

548 lines
20 KiB
Java

/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* 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
*
* https://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 com.kingsrook.qbits.secureddata;
import java.security.SecureRandom;
import java.time.Clock;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
import com.kingsrook.qqq.backend.core.actions.ai.DataClassification;
import com.kingsrook.qqq.backend.core.actions.permissions.fieldaccess.FieldAccessTier;
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfig;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** Configuration for policy resolution and bounded asynchronous access logging.
*******************************************************************************/
public class SecuredDataQBitConfig implements QBitConfig
{
private static final byte[] JVM_KEY = new SecureRandom().generateSeed(32);
private Map<String, String> defaultTierByClassification = new HashMap<>(Map.of("CONFIDENTIAL", "FULL", "PII", "MASKED", "PHI", "MASKED"));
private int anomalyDailyMedianMultiplier = 3;
private int anomalyMinimumDailyReveals = 10;
private int anomalyDistinctRecordsPerHour = 20;
private int anomalyExactMatchesPerHour = 50;
private String tablePathPrefix = "/app";
private String schedulerName;
private Integer backupRetentionDays;
private int wipeIntervalSeconds = 86400;
private com.kingsrook.qbits.secureddata.retention.ArchiveSinkInterface archiveSink;
/*******************************************************************************
** Known CNPG/WAL backup retention; null means unknown.
*******************************************************************************/
public Integer getBackupRetentionDays()
{
return (backupRetentionDays);
}
/*******************************************************************************
** Configure the physical backup recovery window.
*******************************************************************************/
public SecuredDataQBitConfig withBackupRetentionDays(Integer days)
{
if(days != null && days < 0)
{
throw (new IllegalArgumentException("Backup retention cannot be negative"));
}
backupRetentionDays = days;
return (this);
}
/*******************************************************************************
** Optional archive adapter.
*******************************************************************************/
public com.kingsrook.qbits.secureddata.retention.ArchiveSinkInterface getArchiveSink()
{
return (archiveSink);
}
/*******************************************************************************
** Supply a durable encrypted-object sink.
*******************************************************************************/
public SecuredDataQBitConfig withArchiveSink(com.kingsrook.qbits.secureddata.retention.ArchiveSinkInterface sink)
{
archiveSink = sink;
return (this);
}
/*******************************************************************************
** Daily by default.
*******************************************************************************/
public int getWipeIntervalSeconds()
{
return (wipeIntervalSeconds);
}
/*******************************************************************************
** Configure the host scheduler interval.
*******************************************************************************/
public SecuredDataQBitConfig withWipeIntervalSeconds(int seconds)
{
if(seconds < 1)
{
throw (new IllegalArgumentException("Wipe interval must be positive"));
}
wipeIntervalSeconds = seconds;
return (this);
}
private String backendName = "memory";
private Clock clock = Clock.systemUTC();
private int accessLogQueueCapacity = 4096;
private long accessLogFlushIntervalMillis = 250;
private int exactMatchPerHour = 20;
private int missAlertConsecutive = 5;
private int distinctValueAlert = 15;
private boolean minEntropyEnabled = true;
private boolean nameHeuristics = false;
private Set<DataClassification> viewLogClassifications = EnumSet.of(DataClassification.PII, DataClassification.PHI);
private Supplier<byte[]> fingerprintKeySupplier = () -> JVM_KEY.clone();
private MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer = null;
/*******************************************************************************
** Scheduler to run the once-per-minute break-glass expiry job.
*******************************************************************************/
public String getSchedulerName()
{
return (schedulerName);
}
/*******************************************************************************
** Configure the host scheduler; null leaves manual scheduling to the host.
*******************************************************************************/
public SecuredDataQBitConfig withSchedulerName(String schedulerName)
{
this.schedulerName = schedulerName;
return (this);
}
/*******************************************************************************
** Get backendName.
*******************************************************************************/
public String getBackendName()
{
return (backendName);
}
/*******************************************************************************
** Configure backendName.
*******************************************************************************/
public SecuredDataQBitConfig withBackendName(String backendName)
{
this.backendName = backendName;
return (this);
}
/*******************************************************************************
** Get clock.
*******************************************************************************/
public Clock getClock()
{
return (clock);
}
/*******************************************************************************
** Configure clock.
*******************************************************************************/
public SecuredDataQBitConfig withClock(Clock clock)
{
this.clock = clock;
return (this);
}
/*******************************************************************************
** Get accessLogQueueCapacity.
*******************************************************************************/
public int getAccessLogQueueCapacity()
{
return (accessLogQueueCapacity);
}
/*******************************************************************************
** Configure accessLogQueueCapacity.
*******************************************************************************/
public SecuredDataQBitConfig withAccessLogQueueCapacity(int accessLogQueueCapacity)
{
this.accessLogQueueCapacity = accessLogQueueCapacity;
return (this);
}
/*******************************************************************************
** Get accessLogFlushIntervalMillis.
*******************************************************************************/
public long getAccessLogFlushIntervalMillis()
{
return (accessLogFlushIntervalMillis);
}
/*******************************************************************************
** Configure accessLogFlushIntervalMillis.
*******************************************************************************/
public SecuredDataQBitConfig withAccessLogFlushIntervalMillis(long accessLogFlushIntervalMillis)
{
this.accessLogFlushIntervalMillis = accessLogFlushIntervalMillis;
return (this);
}
/*******************************************************************************
** Get exactMatchPerHour.
*******************************************************************************/
public int getExactMatchPerHour()
{
return (exactMatchPerHour);
}
/*******************************************************************************
** Configure exactMatchPerHour.
*******************************************************************************/
public SecuredDataQBitConfig withExactMatchPerHour(int exactMatchPerHour)
{
this.exactMatchPerHour = exactMatchPerHour;
return (this);
}
/*******************************************************************************
** Get missAlertConsecutive.
*******************************************************************************/
public int getMissAlertConsecutive()
{
return (missAlertConsecutive);
}
/*******************************************************************************
** Configure missAlertConsecutive.
*******************************************************************************/
public SecuredDataQBitConfig withMissAlertConsecutive(int missAlertConsecutive)
{
this.missAlertConsecutive = missAlertConsecutive;
return (this);
}
/*******************************************************************************
** Get distinctValueAlert.
*******************************************************************************/
public int getDistinctValueAlert()
{
return (distinctValueAlert);
}
/*******************************************************************************
** Configure distinctValueAlert.
*******************************************************************************/
public SecuredDataQBitConfig withDistinctValueAlert(int distinctValueAlert)
{
this.distinctValueAlert = distinctValueAlert;
return (this);
}
/*******************************************************************************
** Get minEntropyEnabled.
*******************************************************************************/
public boolean getMinEntropyEnabled()
{
return (minEntropyEnabled);
}
/*******************************************************************************
** Configure minEntropyEnabled.
*******************************************************************************/
public SecuredDataQBitConfig withMinEntropyEnabled(boolean minEntropyEnabled)
{
this.minEntropyEnabled = minEntropyEnabled;
return (this);
}
/*******************************************************************************
** Get nameHeuristics.
*******************************************************************************/
public boolean getNameHeuristics()
{
return (nameHeuristics);
}
/*******************************************************************************
** Configure nameHeuristics.
*******************************************************************************/
public SecuredDataQBitConfig withNameHeuristics(boolean nameHeuristics)
{
this.nameHeuristics = nameHeuristics;
return (this);
}
/*******************************************************************************
** Get viewLogClassifications.
*******************************************************************************/
public Set<DataClassification> getViewLogClassifications()
{
return (viewLogClassifications);
}
/*******************************************************************************
** Configure viewLogClassifications.
*******************************************************************************/
public SecuredDataQBitConfig withViewLogClassifications(Set<DataClassification> viewLogClassifications)
{
this.viewLogClassifications = viewLogClassifications;
return (this);
}
/*******************************************************************************
** Get fingerprintKeySupplier.
*******************************************************************************/
public Supplier<byte[]> getFingerprintKeySupplier()
{
return (fingerprintKeySupplier);
}
/*******************************************************************************
** Configure fingerprintKeySupplier.
*******************************************************************************/
public SecuredDataQBitConfig withFingerprintKeySupplier(Supplier<byte[]> fingerprintKeySupplier)
{
this.fingerprintKeySupplier = fingerprintKeySupplier;
return (this);
}
/*******************************************************************************
** Get tableMetaDataCustomizer.
*******************************************************************************/
@Override
public MetaDataCustomizerInterface<QTableMetaData> getTableMetaDataCustomizer()
{
return (tableMetaDataCustomizer);
}
/*******************************************************************************
** Configure tableMetaDataCustomizer.
*******************************************************************************/
public SecuredDataQBitConfig withTableMetaDataCustomizer(MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer)
{
this.tableMetaDataCustomizer = tableMetaDataCustomizer;
return (this);
}
/*******************************************************************************
** Default tier for a classification.
*******************************************************************************/
public SecuredDataQBitConfig withDefaultTier(DataClassification classification, FieldAccessTier tier)
{
defaultTierByClassification.put(classification.name(), tier.name());
return (this);
}
/*******************************************************************************
** Compatibility getter for scaffold configuration.
*******************************************************************************/
public Map<String, String> getDefaultTierByClassification()
{
return (Map.copyOf(defaultTierByClassification));
}
/*******************************************************************************
** Replace configured defaults; missing restricted classifications fail closed.
*******************************************************************************/
public SecuredDataQBitConfig withDefaultTierByClassification(Map<String, String> tiers)
{
tiers.forEach((classification, tier) ->
{
DataClassification.valueOf(classification);
FieldAccessTier.valueOf(tier);
});
defaultTierByClassification = new HashMap<>(tiers);
return (this);
}
/*******************************************************************************
** Resolve the configured default.
*******************************************************************************/
public FieldAccessTier defaultTier(DataClassification classification)
{
return (FieldAccessTier.valueOf(defaultTierByClassification.getOrDefault(classification.name(), "MASKED")));
}
/*******************************************************************************
** Reject invalid infrastructure and guardrail settings at startup.
*******************************************************************************/
public void validate()
{
if(backendName == null || backendName.isBlank() || accessLogQueueCapacity < 1 || accessLogFlushIntervalMillis < 1
|| anomalyDailyMedianMultiplier < 1 || anomalyMinimumDailyReveals < 1 || anomalyDistinctRecordsPerHour < 1
|| anomalyExactMatchesPerHour < 1 || tablePathPrefix == null || !tablePathPrefix.startsWith("/") || tablePathPrefix.startsWith("//")
|| exactMatchPerHour < 0 || missAlertConsecutive < 0 || distinctValueAlert < 0)
{
throw (new IllegalArgumentException("Invalid secured-data configuration"));
}
Objects.requireNonNull(clock);
Objects.requireNonNull(viewLogClassifications);
Objects.requireNonNull(fingerprintKeySupplier);
}
/*******************************************************************************
** Get anomalyDailyMedianMultiplier.
*******************************************************************************/
public int getAnomalyDailyMedianMultiplier()
{
return (anomalyDailyMedianMultiplier);
}
/*******************************************************************************
** Configure anomalyDailyMedianMultiplier.
*******************************************************************************/
public SecuredDataQBitConfig withAnomalyDailyMedianMultiplier(int value)
{
this.anomalyDailyMedianMultiplier = value;
return (this);
}
/*******************************************************************************
** Get anomalyMinimumDailyReveals.
*******************************************************************************/
public int getAnomalyMinimumDailyReveals()
{
return (anomalyMinimumDailyReveals);
}
/*******************************************************************************
** Configure anomalyMinimumDailyReveals.
*******************************************************************************/
public SecuredDataQBitConfig withAnomalyMinimumDailyReveals(int value)
{
this.anomalyMinimumDailyReveals = value;
return (this);
}
/*******************************************************************************
** Get anomalyDistinctRecordsPerHour.
*******************************************************************************/
public int getAnomalyDistinctRecordsPerHour()
{
return (anomalyDistinctRecordsPerHour);
}
/*******************************************************************************
** Configure anomalyDistinctRecordsPerHour.
*******************************************************************************/
public SecuredDataQBitConfig withAnomalyDistinctRecordsPerHour(int value)
{
this.anomalyDistinctRecordsPerHour = value;
return (this);
}
/*******************************************************************************
** Get anomalyExactMatchesPerHour.
*******************************************************************************/
public int getAnomalyExactMatchesPerHour()
{
return (anomalyExactMatchesPerHour);
}
/*******************************************************************************
** Configure anomalyExactMatchesPerHour.
*******************************************************************************/
public SecuredDataQBitConfig withAnomalyExactMatchesPerHour(int value)
{
this.anomalyExactMatchesPerHour = value;
return (this);
}
/*******************************************************************************
** Get tablePathPrefix.
*******************************************************************************/
public String getTablePathPrefix()
{
return (tablePathPrefix);
}
/*******************************************************************************
** Configure tablePathPrefix.
*******************************************************************************/
public SecuredDataQBitConfig withTablePathPrefix(String value)
{
this.tablePathPrefix = value;
return (this);
}
}