Skip to content

Commit

Permalink
Merge pull request #890 from ehsavoie/WFCORE-671
Browse files Browse the repository at this point in the history
WFCORE-671 : Configuration change reporting.
  • Loading branch information
bstansberry committed Jul 23, 2015
2 parents 72cf1eb + 43b4015 commit f6d4aba
Show file tree
Hide file tree
Showing 31 changed files with 3,303 additions and 909 deletions.
Expand Up @@ -69,6 +69,7 @@
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.jboss.as.controller.ConfigurationChangesCollector.ConfigurationChange;

import javax.security.auth.Subject;

Expand Down Expand Up @@ -121,6 +122,7 @@ abstract class AbstractOperationContext implements OperationContext {
private final ProcessType processType;
private final RunningMode runningMode;
private final Environment callEnvironment;
private final ConfigurationChangesCollector configurationChangesCollector = ConfigurationChangesCollector.INSTANCE;
// We only respect interruption on the way in; once we complete all steps
// and begin
// returning, any calls that can throw InterruptedException are converted to
Expand Down Expand Up @@ -502,6 +504,7 @@ boolean isControllingThread() {
* Log an audit record of this operation.
*/
void logAuditRecord() {
trackConfigurationChange();
if (!auditLogged) {
try {
AccessAuditContext accessContext = SecurityActions.currentAccessAuditContext();
Expand Down Expand Up @@ -535,7 +538,7 @@ private <T extends Principal> T getPrincipal(Subject subject, Class<T> clazz) {
}
Set<T> principals = subject.getPrincipals(clazz);
assert principals.size() <= 1;
if (principals.size() == 0) {
if (principals.isEmpty()) {
return null;
}
return principals.iterator().next();
Expand All @@ -549,6 +552,24 @@ private void recordControllerOperation(ModelNode operation) {
controllerOperations.add(operation.clone()); // clone so we don't log op nodes mutated during execution
}

void trackConfigurationChange() {
if (!isBooting() && !isReadOnly() && configurationChangesCollector.trackAllowed()) {
try {
AccessAuditContext accessContext = SecurityActions.currentAccessAuditContext();
Caller currentCaller = getCaller();
Subject subject = SecurityActions.getSubject(currentCaller);
configurationChangesCollector.addConfigurationChanges(new ConfigurationChange(resultAction,
currentCaller == null ? null : currentCaller.getName(),
accessContext == null ? null : accessContext.getDomainUuid(),
accessContext == null ? null : accessContext.getAccessMechanism(),
getSubjectInetAddress(subject),
controllerOperations));
} catch (Exception e) {
ControllerLogger.MGMT_OP_LOGGER.failedToUpdateAuditLog(e);
}
}
}

abstract Resource getModel();

/**
Expand Down
Expand Up @@ -43,7 +43,7 @@
import org.jboss.dmr.ModelType;

/**
*
* Collects boot errors.
* @author <a href="mailto:ehugonne@redhat.com">Emmanuel Hugonnet</a> (c) 2014 Red Hat, inc.
*/
public class BootErrorCollector {
Expand Down
@@ -0,0 +1,177 @@
/*
* Copyright (C) 2015 Red Hat, inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
package org.jboss.as.controller;

import static java.time.Instant.now;
import static org.jboss.as.controller.audit.JsonAuditLogItemFormatter.REMOTE_ADDRESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ACCESS_MECHANISM;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.DOMAIN_UUID;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FAILED;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATIONS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OPERATION_DATE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OUTCOME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SUCCESS;

import java.net.InetAddress;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.List;
import java.util.Locale;
import java.util.TreeSet;
import org.jboss.as.core.security.AccessMechanism;
import org.jboss.dmr.ModelNode;

/**
* Collects configuration changes.
* @author <a href="mailto:ehugonne@redhat.com">Emmanuel Hugonnet</a> (c) 2015 Red Hat, inc.
*/
public interface ConfigurationChangesCollector {

ConfigurationChangesCollectorImpl INSTANCE = new ConfigurationChangesCollectorImpl(0);

void addConfigurationChanges(ConfigurationChange change);

List<ModelNode> getChanges();

void setMaxHistory(int maxHistory);

boolean trackAllowed();

void deactivate();

static class ConfigurationChangesCollectorImpl implements ConfigurationChangesCollector {

private final TreeSet<ConfigurationChange> history = new TreeSet<>();
private int maxHistory;

private ConfigurationChangesCollectorImpl(final int maxHistory) {
this.maxHistory = maxHistory;
}

@Override
public void addConfigurationChanges(ConfigurationChange change) {
synchronized (history) {
if (history.size() == maxHistory) {
history.remove(history.last());
}
history.add(change);
}
}

@Override
public void setMaxHistory(int maxHistory) {
synchronized (history) {
this.maxHistory = maxHistory;
while(maxHistory < history.size()) {
history.remove(history.last());
}
}
}

@SuppressWarnings("unchecked")
@Override
public List<ModelNode> getChanges() {
TreeSet<ConfigurationChange> changes;
synchronized (history) {
changes = new TreeSet<>(history);
}
ModelNode result = new ModelNode().setEmptyList();
changes.stream().forEach((change) -> {
result.add(change.asModel());
});
return result.asList();
}

@Override
public boolean trackAllowed() {
synchronized (history) {
return this.maxHistory > 0;
}
}

@Override
public void deactivate() {
synchronized (history) {
this.maxHistory = 0;
this.history.clear();
}
}
}

static final class ConfigurationChange implements Comparable<ConfigurationChange> {

private static final DateTimeFormatter DATE_FORMAT = new DateTimeFormatterBuilder().appendInstant().toFormatter(Locale.ENGLISH);
private final OperationContext.ResultAction resultAction;
private final String userId;
private final String domainUuid;
private final AccessMechanism accessMecanism;
private final InetAddress inetAddress;
private final List<ModelNode> operations;
private final Instant date;

public ConfigurationChange(OperationContext.ResultAction resultAction, String userId, String domainUuid,
AccessMechanism accessMecanism, InetAddress inetAddress, List<ModelNode> operations) {
this.resultAction = resultAction;
this.userId = userId;
this.domainUuid = domainUuid;
this.accessMecanism = accessMecanism;
this.inetAddress = inetAddress;
this.operations = operations;
date = now();
}

private String getDate() {
return DATE_FORMAT.format(Instant.now());
}

public Instant getOperationInstant() {
return date;
}

public ModelNode asModel() {
ModelNode entry = new ModelNode().setEmptyObject();
entry.get(OPERATION_DATE).set(getDate());
if (domainUuid != null) {
entry.get(DOMAIN_UUID).set(domainUuid);
}
if (accessMecanism != null) {
entry.get(ACCESS_MECHANISM).set(accessMecanism.toString());
}
if (inetAddress != null) {
entry.get(REMOTE_ADDRESS).set(inetAddress.toString());
}
entry.get(OUTCOME).set(resultAction == OperationContext.ResultAction.KEEP ? SUCCESS : FAILED);
if (operations != null && !operations.isEmpty()) {
ModelNode changes = entry.get(OPERATIONS).setEmptyList();
operations.stream().forEach((op) -> {
changes.add(op);
});
}
return entry;
}

@Override
public int compareTo(ConfigurationChange change) {
return change.getOperationInstant().compareTo(date);
}
}
}

0 comments on commit f6d4aba

Please sign in to comment.