Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package org.jboss.as.controller;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADDRESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ALLOW_RESOURCE_SERVICE_RESTART;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ATTRIBUTE_VALUE_WRITTEN_NOTIFICATION;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.CANCELLED;
Expand Down Expand Up @@ -39,6 +40,9 @@

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -47,6 +51,7 @@
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.HexFormat;
import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
Expand Down Expand Up @@ -159,7 +164,7 @@ abstract class AbstractOperationContext implements OperationContext, AutoCloseab
ModelNode initialOperation;

/** Operations that were added by the controller, before execution started */
private final List<ModelNode> controllerOperations = new ArrayList<ModelNode>(2);
private final List<RecordedOperation> controllerOperations = new ArrayList<>(2);
private boolean auditLogged;
private final AuditLogger auditLogger;
private final ModelControllerImpl controller;
Expand Down Expand Up @@ -453,6 +458,7 @@ public void close() {
if (modifiedResourcesForModelValidation != null) {
modifiedResourcesForModelValidation.clear();
}
this.controllerOperations.clear();
}

/**
Expand All @@ -475,7 +481,7 @@ ResultAction executeOperation() {
}
} catch (RuntimeException e) {
handleUncaughtException(e);
ControllerLogger.MGMT_OP_LOGGER.unexpectedOperationExecutionException(e, controllerOperations);
ControllerLogger.MGMT_OP_LOGGER.unexpectedOperationExecutionException(e, filterControllerOperations(true));
} finally {
// On failure close any attached response streams
if (resultAction != ResultAction.KEEP && !isBooting()) {
Expand Down Expand Up @@ -667,7 +673,8 @@ void logAuditRecord() {
accessContext == null ? null : accessContext.getAccessMechanism(),
accessContext == null ? null : accessContext.getRemoteAddress(),
getModel(),
controllerOperations);
filterControllerOperations(false),
filterControllerOperations(true));
auditLogged = true;
} catch (Exception e) {
ControllerLogger.MGMT_OP_LOGGER.failedToUpdateAuditLog(e);
Expand All @@ -677,10 +684,54 @@ void logAuditRecord() {

/**
* Record an operation added before execution began (i.e. added by the controller and not by a step)
*
* @param operation the operation
*/
private void recordControllerOperation(ModelNode operation) {
controllerOperations.add(operation.clone()); // clone so we don't log op nodes mutated during execution
ModelNode op = operation.clone();
ModelNode redactedOp = operation.clone();

ImmutableManagementResourceRegistration mrr = getRootResourceRegistration();
OperationEntry operationEntry = mrr.getOperationEntry(PathAddress.pathAddress(op.get(ADDRESS)), op.get(OP).asString());
if (operationEntry != null) {
Set<String> redactableNames = operationEntry.getOperationDefinition().getRedactableAttributeNames();
if (!redactableNames.isEmpty()) {
for (String redactableName : redactableNames) {
redactValues(redactedOp, redactableName);
}
}
}
controllerOperations.add(new RecordedOperation(op, redactedOp));
}

private void redactValues(ModelNode node, String redactableName) {
if (!node.isDefined()) {
return;
}
switch (node.getType()) {
case OBJECT -> {
if (node.hasDefined(redactableName)) {
node.get(redactableName).set(sha256(node.get(redactableName).asString()));
}
for (String key : node.keys()) {
redactValues(node.get(key), redactableName);
}
}
case LIST -> {
for (ModelNode element : node.asList()) {
redactValues(element, redactableName);
}
}
case PROPERTY -> {
Property prop = node.asProperty();
if (prop.getName().equals(redactableName)) {
node.set(prop.getName(), new ModelNode(sha256(prop.getValue().asString())));
} else {
redactValues(prop.getValue(), redactableName);
}
}
default -> { /* leaf node */ }
}
}

void recordWriteLock() {
Expand All @@ -697,7 +748,7 @@ void trackConfigurationChange() {
accessContext == null ? null : accessContext.getDomainUuid(),
accessContext == null ? null : accessContext.getAccessMechanism(),
accessContext == null ? null : accessContext.getRemoteAddress(),
controllerOperations));
filterControllerOperations(configurationChangesCollector.isRedacted())));
} catch (Exception e) {
ControllerLogger.MGMT_OP_LOGGER.failedToUpdateAuditLog(e);
}
Expand Down Expand Up @@ -839,7 +890,7 @@ private boolean tryStageCompleted(Stage currentStage) {
if (!initialResponse.hasDefined(FAILURE_DESCRIPTION)) {
initialResponse.get(FAILURE_DESCRIPTION).set(ControllerLogger.MGMT_OP_LOGGER.unexpectedOperationExecutionFailureDescription(e));
}
ControllerLogger.MGMT_OP_LOGGER.unexpectedOperationExecutionException(e, controllerOperations);
ControllerLogger.MGMT_OP_LOGGER.unexpectedOperationExecutionException(e, filterControllerOperations(true));
}
return result;
}
Expand Down Expand Up @@ -1821,4 +1872,29 @@ public void close() throws IOException {
StreamUtils.safeClose(stream);
}
}

private record RecordedOperation(ModelNode op, ModelNode redactedOp) {
@Override
public String toString() {
return redactedOp.toString();
}
}

private List<ModelNode> filterControllerOperations(boolean redacted) {
return controllerOperations.stream()
.map(ro -> redacted ? ro.redactedOp : ro.op)
.toList();
}

private String sha256(String input) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] encodedHash = digest.digest(input.getBytes(StandardCharsets.UTF_8));
return HexFormat.of().formatHex(encodedHash);
} catch (NoSuchAlgorithmException e) {
// SHA-256 should be guaranteed to be available in Java environments,
// but in case of an error, provides a fallback
return "*".repeat(Math.min(input.length(), 6));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,15 @@ public interface ConfigurationChangesCollector {

void deactivate();

static class ConfigurationChangesCollectorImpl implements ConfigurationChangesCollector {
boolean isRedacted();

void setRedacted(boolean redacted);

class ConfigurationChangesCollectorImpl implements ConfigurationChangesCollector {

private final Deque<ConfigurationChange> history = new ArrayDeque<>();
private int maxHistory;
private boolean redacted;

private ConfigurationChangesCollectorImpl(final int maxHistory) {
this.maxHistory = maxHistory;
Expand Down Expand Up @@ -102,9 +107,19 @@ public void deactivate() {
this.history.clear();
}
}

@Override
public void setRedacted(boolean redacted) {
this.redacted = redacted;
}

@Override
public boolean isRedacted() {
return this.redacted;
}
}

static final class ConfigurationChange {
final class ConfigurationChange {

private static final DateTimeFormatter DATE_FORMAT = new DateTimeFormatterBuilder().appendInstant(3).toFormatter(Locale.ENGLISH);
private final OperationContext.ResultAction resultAction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.jboss.as.controller.access.management.AccessConstraintDefinition;
import org.jboss.as.controller.registry.AttributeAccess;
import org.jboss.as.controller.descriptions.DescriptionProvider;
import org.jboss.as.controller.registry.OperationEntry;
import org.jboss.as.version.Stability;
Expand Down Expand Up @@ -115,4 +117,39 @@ public AttributeDefinition[] getReplyParameters() {
return replyParameters;
}

/**
* Returns the names of the attributes that are susceptible to be redacted. This method
* recursively inspects all parameters, including nested attributes within complex types
* ({@link ObjectTypeAttributeDefinition}, {@link ObjectListAttributeDefinition},
* {@link ObjectMapAttributeDefinition}), collecting the names of those that have the
* {@link AttributeAccess.Flag#REDACTABLE} flag set.
*
* @return a set of attribute names requiring redaction, or an empty set if none
*/
public Set<String> getRedactableAttributeNames() {
Set<String> result = new HashSet<>();
for (AttributeDefinition parameter : parameters) {
collectRedactableNames(parameter, result);
}
return Collections.unmodifiableSet(result);
}

private void collectRedactableNames(AttributeDefinition attr, Set<String> result) {
if (attr instanceof ObjectTypeAttributeDefinition objectType) {
for (AttributeDefinition nested : objectType.getValueTypes()) {
collectRedactableNames(nested, result);
}
} else if (attr instanceof ObjectListAttributeDefinition listType) {
for (AttributeDefinition nested : listType.getValueType().getValueTypes()) {
collectRedactableNames(nested, result);
}
} else if (attr instanceof ObjectMapAttributeDefinition mapType) {
for (AttributeDefinition nested : mapType.getValueType().getValueTypes()) {
collectRedactableNames(nested, result);
}
} else if (attr.getFlags().contains(AttributeAccess.Flag.REDACTABLE)) {
result.add(attr.getName());
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ enum Status {
}

void log(boolean readOnly, OperationContext.ResultAction resultAction, String userId, String domainUUID,
AccessMechanism accessMechanism, InetAddress remoteAddress, final Resource resultantModel, List<ModelNode> operations);
AccessMechanism accessMechanism, InetAddress remoteAddress, final Resource resultantModel, List<ModelNode> operations, List<ModelNode> redactedOperations);

void logJmxMethodAccess(final boolean readOnly, final String userId, final String domainUUID, final AccessMechanism accessMechanism, InetAddress remoteAddress,
final String methodName, final String[] methodSignature, final Object[] methodParams, final Throwable error);
Expand All @@ -54,6 +54,15 @@ public boolean isLogReadOnly() {
public void setLogReadOnly(boolean logReadOnly) {
}

@Override
public boolean isRedacted() {
return false;
}

@Override
public void setRedacted(boolean maskOperationParameters) {
}

@Override
public boolean isLogBoot() {
return false;
Expand All @@ -68,7 +77,7 @@ public Status getLoggerStatus() {
}

@Override
public void log(boolean readOnly, OperationContext.ResultAction resultAction, String userId, String domainUUID, AccessMechanism accessMechanism, InetAddress remoteAddress, Resource resultantModel, List<ModelNode> operations) {
public void log(boolean readOnly, OperationContext.ResultAction resultAction, String userId, String domainUUID, AccessMechanism accessMechanism, InetAddress remoteAddress, Resource resultantModel, List<ModelNode> operations, List<ModelNode> redactedOperations) {
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,19 @@ public interface ManagedAuditLogger extends AuditLogger {
/**
* Set whether to log read-only operations
*
* @param logReadOnly wheter to log read-only operations
* @param logReadOnly whether to log read-only operations
*/
void setLogReadOnly(boolean logReadOnly);

boolean isRedacted();

/**
* Set whether to redact the operation parameters that has been flagged as redactable
*
* @param redacted whether to log redact operation parameters
*/
void setRedacted(boolean redacted);

/**
* Get whether this audit logger logs operations on boot
*
Expand Down
Loading
Loading