Files
qbit-secured-data/src/main/java/com/kingsrook/qbits/secureddata/SecuredDataQBitConfig.java
T
james.maesandClaude Opus 5.5 e26ba2b4ad feat: reveal, break-glass and the secured-data UI API extension (phase c)
Reveal service (options, reason validation, daily cap, grants, audit),
break-glass start/end/expiry with alerts and admin notification, status,
the secured-data 1.0.0 UI API extension (compiled under the uiapi profile
until RC.6) and a contingent field picker for classification rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:47:31 -05:00

394 lines
14 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.qbits.secureddata.standin.DataClassification;
import com.kingsrook.qbits.secureddata.standin.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 String schedulerName;
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
|| exactMatchPerHour < 0 || missAlertConsecutive < 0 || distinctValueAlert < 0)
{
throw (new IllegalArgumentException("Invalid secured-data configuration"));
}
Objects.requireNonNull(clock);
Objects.requireNonNull(viewLogClassifications);
Objects.requireNonNull(fingerprintKeySupplier);
}
}