Files
qbit-secured-data/src/test/java/com/kingsrook/qbits/secureddata/LogCapture.java
T
james.maesandClaude Opus 5.5 470b0bd214 feat: retention and wipe with crypto-shredding; move to QQQ RC.6 (phase f)
Moves the pin to QQQ 4.1.0-RC.6 and removes the stand-ins RC.6 provides
(classification, client channels, table features, the UI API extension
point). Adds retention rules, wipe markers, legal holds, wipe runs and the
per-record-per-field audit key table; the wipe process (null with marker,
overwrite, keyed hash, delete, archive then wipe) runs in one transaction
per batch, destroying the audit keys with the field updates; the audit
protector and key source (AES-GCM wrapped DEKs, AAD-bound); the backups
warning; canary and wipe-proof tests.

Fixes a key-source bug that zeroed each DEK after returning it.

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

120 lines
4.4 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.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.config.Property;
/*******************************************************************************
** Captures every log event, at every level, from every logger while open.
** Used by the canary test to prove no protected value reaches a log line.
*******************************************************************************/
final class LogCapture implements AutoCloseable
{
private final List<String> lines = new ArrayList<>();
private final Map<String, Level> originalLevels = new HashMap<>();
private final LoggerContext context;
private final Appender appender;
/*******************************************************************************
** Attach to the root logger and force all configured loggers to ALL.
*******************************************************************************/
LogCapture()
{
context = (LoggerContext) LogManager.getContext(false);
Configuration configuration = context.getConfiguration();
appender = new AbstractAppender("secured-data-log-capture", null, null, true, Property.EMPTY_ARRAY)
{
@Override
public void append(LogEvent event)
{
StringBuilder line = new StringBuilder(event.getLoggerName()).append(' ').append(event.getMessage().getFormattedMessage());
for(Throwable t = event.getThrown(); t != null; t = t.getCause())
{
line.append(' ').append(t.getClass().getName()).append(' ').append(t.getMessage());
}
synchronized(lines)
{
lines.add(line.toString());
}
}
};
appender.start();
configuration.addAppender(appender);
List<LoggerConfig> all = new ArrayList<>(configuration.getLoggers().values());
all.add(configuration.getRootLogger());
for(LoggerConfig config : all)
{
originalLevels.put(config.getName(), config.getLevel());
config.setLevel(Level.ALL);
}
configuration.getRootLogger().addAppender(appender, Level.ALL, null);
context.updateLoggers();
}
/*******************************************************************************
** Every captured line so far.
*******************************************************************************/
List<String> lines()
{
synchronized(lines)
{
return (new ArrayList<>(lines));
}
}
/*******************************************************************************
** Detach and restore levels.
*******************************************************************************/
@Override
public void close()
{
Configuration configuration = context.getConfiguration();
configuration.getRootLogger().removeAppender(appender.getName());
for(LoggerConfig config : configuration.getLoggers().values())
{
config.setLevel(originalLevels.get(config.getName()));
}
configuration.getRootLogger().setLevel(originalLevels.get(configuration.getRootLogger().getName()));
context.updateLoggers();
appender.stop();
}
}