/*
* JBoss, Home of Professional Open Source.
* Copyright 2013, 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 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 software 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 software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.domain.management.security.auditlog;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.COMPOSITE;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.HOST;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MAX_LENGTH;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.MESSAGE_TRANSFER;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PORT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.PROTOCOL;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.STEPS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SYSLOG_FORMAT;
import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.StringWriter;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.audit.ManagedAuditLogger;
import org.jboss.as.controller.audit.ManagedAuditLoggerImpl;
import org.jboss.as.controller.audit.SyslogAuditLogHandler;
import org.jboss.as.controller.audit.SyslogAuditLogHandler.Transport;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.interfaces.InetAddressUtil;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.domain.management.CoreManagementResourceDefinition;
import org.jboss.as.domain.management.audit.AccessAuditResourceDefinition;
import org.jboss.as.domain.management.audit.AuditLogHandlerResourceDefinition;
import org.jboss.as.domain.management.audit.AuditLogLoggerResourceDefinition;
import org.jboss.as.domain.management.audit.FileAuditLogHandlerResourceDefinition;
import org.jboss.as.domain.management.audit.SyslogAuditLogProtocolResourceDefinition;
import org.jboss.as.domain.management.security.util.ManagementControllerTestBase;
import org.jboss.dmr.ModelNode;
import org.jboss.logmanager.handlers.SyslogHandler;
import org.junit.After;
import org.junit.Assert;
/**
* Don't use core-model test for this. It does not support runtime, and more importantly for backwards compatibility the audit logger cannot be used
*
* @author: Kabir Khan
*/
public class AbstractAuditLogHandlerTestCase extends ManagementControllerTestBase {
protected static final PathAddress AUDIT_ADDR = PathAddress.pathAddress(CoreManagementResourceDefinition.PATH_ELEMENT, AccessAuditResourceDefinition.PATH_ELEMENT);
protected static final int SYSLOG_PORT = 6666;
protected static final int SYSLOG_PORT2 = 6667;
protected final List<ModelNode> bootOperations = new ArrayList<ModelNode>();
public AbstractAuditLogHandlerTestCase(boolean enabled, boolean addFile) {
bootOperations.add(Util.createAddOperation(AUDIT_ADDR));
ModelNode add = Util.createAddOperation(createJsonFormatterAddress("test-formatter"));
bootOperations.add(add);
if (addFile) {
add = createAddFileHandlerOperation("test-file", "test-formatter", "test-file.log");
add.get(FileAuditLogHandlerResourceDefinition.MAX_FAILURE_COUNT.getName()).set(3);
bootOperations.add(add);
}
add = Util.createAddOperation(
AUDIT_ADDR.append(AuditLogLoggerResourceDefinition.PATH_ELEMENT));
add.get(ModelDescriptionConstants.ENABLED).set(enabled);
add.get(ModelDescriptionConstants.LOG_READ_ONLY).set(true);
bootOperations.add(add);
if (addFile) {
bootOperations.add(createAddHandlerReferenceOperation("test-file"));
}
}
@After
public void clearDependencies(){
auditLogger = null;
logDir = null;
}
/**
* Override base method to clone the operation so any mutation of it by the controller
* does not get noticed in comparisons of log output to op input.
* {@inheritDoc}
*/
@Override
public ModelNode executeForResult(ModelNode operation) throws OperationFailedException {
return super.executeForResult(operation.clone());
}
protected ManagedAuditLogger getAuditLogger(){
if (auditLogger == null){
auditLogger = new ManagedAuditLoggerImpl("8.0.0", true);
}
return auditLogger;
}
protected void checkHandlerRuntimeFailureMetrics(ModelNode handler, int maxFailureCount, int failureCount, boolean disabled) {
Assert.assertEquals(maxFailureCount, handler.get(AuditLogHandlerResourceDefinition.MAX_FAILURE_COUNT.getName()).asInt());
Assert.assertEquals(failureCount, handler.get(AuditLogHandlerResourceDefinition.FAILURE_COUNT.getName()).asInt());
Assert.assertEquals(disabled, handler.get(AuditLogHandlerResourceDefinition.DISABLED_DUE_TO_FAILURE.getName()).asBoolean());
}
protected String stripSyslogHeader(byte[] bytes) {
String s = new String(bytes, StandardCharsets.UTF_8);
int i = s.indexOf(" - - ");
return s.substring(i + 6);
}
protected ModelNode getSyslogRecord(byte[] bytes) {
Assert.assertNotNull("bytes to create syslog record cannot be null.", bytes);
String msg = new String(bytes, StandardCharsets.UTF_8);
return getSyslogRecord(msg);
}
protected ModelNode getSyslogRecord(String msg) {
msg = msg.substring(msg.indexOf('{')).replace("#012", "\n");
return ModelNode.fromJSONString(msg);
}
protected void checkOpsEqual(ModelNode rawDmr, ModelNode fromLog) {
ModelNode expected = ModelNode.fromJSONString(rawDmr.toJSONString(true));
Assert.assertEquals(expected, fromLog);
}
private final Pattern DATE_STAMP_PATTERN = Pattern.compile("\\d\\d\\d\\d-\\d\\d-\\d\\d \\d\\d:\\d\\d:\\d\\d - \\{");
protected List<ModelNode> readFile(File file, int expectedRecords) throws IOException {
List<ModelNode> list = new ArrayList<ModelNode>();
try (final BufferedReader reader = Files.newBufferedReader(file.toPath(),StandardCharsets.UTF_8)){
StringWriter writer = null;
String line = reader.readLine();
while (line != null) {
if (DATE_STAMP_PATTERN.matcher(line).matches()) {
if (writer != null) {
list.add(ModelNode.fromJSONString(writer.getBuffer().toString()));
}
writer = new StringWriter();
writer.append("{");
} else {
writer.append("\n" + line);
}
line = reader.readLine();
}
if (writer != null) {
list.add(ModelNode.fromJSONString(writer.getBuffer().toString()));
}
}
Assert.assertEquals(list.toString(), expectedRecords, list.size());
return list;
}
protected String readFullFileRecord(File file) throws IOException {
try (final BufferedReader reader = Files.newBufferedReader(file.toPath(),StandardCharsets.UTF_8)){
boolean firstLine = true;
StringWriter writer = new StringWriter();
String line = reader.readLine();
while (line != null) {
if (!firstLine) {
writer.append("\n");
} else {
firstLine = false;
}
writer.append(line);
line = reader.readLine();
}
return writer.toString();
}
}
protected ModelNode createAuditLogWriteAttributeOperation(String attr, boolean value) {
return Util.getWriteAttributeOperation(AUDIT_ADDR.append(AuditLogLoggerResourceDefinition.PATH_ELEMENT), attr, new ModelNode(value));
}
protected ModelNode createAddFileHandlerOperation(String handlerName, String formatterName, String fileName) {
ModelNode op = Util.createAddOperation(createFileHandlerAddress(handlerName));
op.get(ModelDescriptionConstants.RELATIVE_TO).set("log.dir");
op.get(ModelDescriptionConstants.PATH).set(fileName);
op.get(ModelDescriptionConstants.FORMATTER).set(formatterName);
return op;
}
protected ModelNode createAddPeriodicRotatingFileHandlerOperation(String handlerName, String formatterName, String fileName, String suffix) {
ModelNode op = Util.createAddOperation(createPeriodicRotatingFileHandlerAddress(handlerName));
op.get(ModelDescriptionConstants.RELATIVE_TO).set("log.dir");
op.get(ModelDescriptionConstants.PATH).set(fileName);
op.get(ModelDescriptionConstants.FORMATTER).set(formatterName);
op.get(ModelDescriptionConstants.SUFFIX).set(suffix);
return op;
}
protected ModelNode createAddSizeRotatingFileHandlerOperation(String handlerName, String formatterName, String fileName, String rotateSize, int maxBackupIndex) {
ModelNode op = Util.createAddOperation(createSizeRotatingFileHandlerAddress(handlerName));
op.get(ModelDescriptionConstants.RELATIVE_TO).set("log.dir");
op.get(ModelDescriptionConstants.PATH).set(fileName);
op.get(ModelDescriptionConstants.FORMATTER).set(formatterName);
if (rotateSize != null) {
op.get(ModelDescriptionConstants.ROTATE_SIZE).set(rotateSize);
}
op.get(ModelDescriptionConstants.MAX_BACKUP_INDEX).set(maxBackupIndex);
return op;
}
protected ModelNode createRemoveFileHandlerOperation(String handlerName) {
return Util.createRemoveOperation(createFileHandlerAddress(handlerName));
}
protected ModelNode createRemovePeriodicRotatingFileHandlerOperation(String handlerName) {
return Util.createRemoveOperation(createPeriodicRotatingFileHandlerAddress(handlerName));
}
protected ModelNode createRemoveSizeRotatingFileHandlerOperation(String handlerName) {
return Util.createRemoveOperation(createSizeRotatingFileHandlerAddress(handlerName));
}
protected PathAddress createFileHandlerAddress(String handlerName){
return createHandlerAddress(ModelDescriptionConstants.FILE_HANDLER, handlerName);
}
protected PathAddress createPeriodicRotatingFileHandlerAddress(String handlerName){
return createHandlerAddress(ModelDescriptionConstants.PERIODIC_ROTATING_FILE_HANDLER, handlerName);
}
protected PathAddress createSizeRotatingFileHandlerAddress(String handlerName){
return createHandlerAddress(ModelDescriptionConstants.SIZE_ROTATING_FILE_HANDLER, handlerName);
}
protected PathAddress createHandlerAddress(String handlerType, String handlerName){
return AUDIT_ADDR.append(PathElement.pathElement(handlerType, handlerName));
}
protected ModelNode createRemoveJsonFormatterOperation(String formatterName) {
return Util.createRemoveOperation(createJsonFormatterAddress(formatterName));
}
protected PathAddress createJsonFormatterAddress(String formatterName) {
return AUDIT_ADDR.append(
PathElement.pathElement(ModelDescriptionConstants.JSON_FORMATTER, formatterName));
}
protected ModelNode createAddSyslogHandlerUdpOperation(String handlerName, String formatterName, InetAddress addr, int port, SyslogHandler.SyslogType syslogFormat, int maxLength){
ModelNode composite = new ModelNode();
composite.get(OP).set(COMPOSITE);
composite.get(OP_ADDR).setEmptyList();
composite.get(STEPS).setEmptyList();
ModelNode handler = Util.createAddOperation(createSyslogHandlerAddress(handlerName));
if (syslogFormat != null){
handler.get(SYSLOG_FORMAT).set(syslogFormat.toString());
}
if (maxLength > 0) {
handler.get(MAX_LENGTH).set(maxLength);
}
handler.get(ModelDescriptionConstants.FORMATTER).set(formatterName);
composite.get(STEPS).add(handler);
ModelNode protocol = Util.createAddOperation(createSyslogHandlerProtocolAddress(handlerName, SyslogAuditLogHandler.Transport.UDP));
protocol.get(HOST).set(InetAddressUtil.canonize(addr.getHostName()));
protocol.get(PORT).set(port);
composite.get(STEPS).add(protocol);
return composite;
}
protected ModelNode createAddSyslogHandlerTcpOperation(String handlerName, String formatterName, InetAddress addr, int port, SyslogHandler.SyslogType syslogFormat, SyslogAuditLogHandler.MessageTransfer transfer){
ModelNode composite = new ModelNode();
composite.get(OP).set(COMPOSITE);
composite.get(OP_ADDR).setEmptyList();
composite.get(STEPS).setEmptyList();
ModelNode handler = Util.createAddOperation(createSyslogHandlerAddress(handlerName));
if (syslogFormat != null){
handler.get(SYSLOG_FORMAT).set(syslogFormat.toString());
}
handler.get(ModelDescriptionConstants.FORMATTER).set(formatterName);
composite.get(STEPS).add(handler);
ModelNode protocol = Util.createAddOperation(createSyslogHandlerProtocolAddress(handlerName, SyslogAuditLogHandler.Transport.TCP));
protocol.get(HOST).set(InetAddressUtil.canonize(addr.getHostName()));
protocol.get(PORT).set(port);
if (transfer != null) {
protocol.get(MESSAGE_TRANSFER).set(transfer.name());
}
composite.get(STEPS).add(protocol);
return composite;
}
protected ModelNode createAddSyslogHandlerTlsOperation(String handlerName, String formatterName, InetAddress addr, int port, SyslogHandler.SyslogType syslogFormat,
SyslogAuditLogHandler.MessageTransfer transfer, File truststorePath, String trustPwd, File clientCertPath, String clientCertPwd){
ModelNode composite = new ModelNode();
composite.get(OP).set(COMPOSITE);
composite.get(OP_ADDR).setEmptyList();
composite.get(STEPS).setEmptyList();
ModelNode handler = Util.createAddOperation(createSyslogHandlerAddress(handlerName));
if (syslogFormat != null){
handler.get(SYSLOG_FORMAT).set(syslogFormat.toString());
}
handler.get(ModelDescriptionConstants.FORMATTER).set(formatterName);
composite.get(STEPS).add(handler);
ModelNode protocol = Util.createAddOperation(createSyslogHandlerProtocolAddress(handlerName, SyslogAuditLogHandler.Transport.TLS));
protocol.get(HOST).set(InetAddressUtil.canonize(addr.getHostName()));
protocol.get(PORT).set(port);
if (transfer != null) {
protocol.get(MESSAGE_TRANSFER).set(transfer.name());
}
composite.get(STEPS).add(protocol);
ModelNode truststore = Util.createAddOperation(
createSyslogHandlerProtocolAddress("syslog-test", Transport.TLS).append(
PathElement.pathElement(ModelDescriptionConstants.AUTHENTICATION, ModelDescriptionConstants.TRUSTSTORE)));
truststore.get(SyslogAuditLogProtocolResourceDefinition.TlsKeyStore.KEYSTORE_PATH.getName()).set(truststorePath.getAbsolutePath());
truststore.get(SyslogAuditLogProtocolResourceDefinition.TlsKeyStore.KEYSTORE_PASSWORD.getName()).set(trustPwd);
composite.get(STEPS).add(truststore);
if (clientCertPath != null) {
ModelNode clientCert = Util.createAddOperation(createSyslogHandlerProtocolAddress("syslog-test", Transport.TLS).append(
PathElement.pathElement(ModelDescriptionConstants.AUTHENTICATION, ModelDescriptionConstants.CLIENT_CERT_STORE)));
clientCert.get(SyslogAuditLogProtocolResourceDefinition.TlsKeyStore.KEYSTORE_PATH.getName()).set(clientCertPath.getAbsolutePath());
clientCert.get(SyslogAuditLogProtocolResourceDefinition.TlsKeyStore.KEYSTORE_PASSWORD.getName()).set(clientCertPwd);
composite.get(STEPS).add(clientCert);
}
return composite;
}
protected PathAddress createSyslogHandlerAddress(String handlerName){
return AUDIT_ADDR.append(PathElement.pathElement(ModelDescriptionConstants.SYSLOG_HANDLER, handlerName));
}
protected PathAddress createSyslogHandlerProtocolAddress(String handlerName, SyslogAuditLogHandler.Transport transport){
return AUDIT_ADDR.append(
PathElement.pathElement(ModelDescriptionConstants.SYSLOG_HANDLER, handlerName),
PathElement.pathElement(PROTOCOL, transport.name().toLowerCase()));
}
protected ModelNode createAddHandlerReferenceOperation(String name){
return Util.createAddOperation(createHandlerReferenceAddress(name));
}
protected ModelNode createRemoveHandlerReferenceOperation(String name){
return Util.createRemoveOperation(createHandlerReferenceAddress(name));
}
protected PathAddress createHandlerReferenceAddress(String name){
return AUDIT_ADDR.append(
AuditLogLoggerResourceDefinition.PATH_ELEMENT,
PathElement.pathElement(ModelDescriptionConstants.HANDLER, name));
}
protected List<ModelNode> checkBootRecordHeader(ModelNode bootRecord, int ops, String type, boolean readOnly, boolean booting, boolean success) {
Assert.assertEquals(type, bootRecord.get("type").asString());
Assert.assertEquals(readOnly, bootRecord.get("r/o").asBoolean());
Assert.assertEquals(booting, bootRecord.get("booting").asBoolean());
Assert.assertEquals("anonymous", bootRecord.get("user").asString());
Assert.assertFalse(bootRecord.get("domainUUID").isDefined());
Assert.assertFalse(bootRecord.get("access").isDefined());
Assert.assertFalse(bootRecord.get("remote-address").isDefined());
Assert.assertEquals(success, bootRecord.get("success").asBoolean());
List<ModelNode> operations = bootRecord.get("ops").asList();
Assert.assertEquals(ops, operations.size());
return operations;
}
protected FilenameFilter createLogFilenameFilter(final String filenamePrefix) {
return new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.startsWith(filenamePrefix);
}
};
}
@Override
protected void addBootOperations(List<ModelNode> bootOperations) {
for (ModelNode bootOp : this.bootOperations) {
bootOperations.add(bootOp.clone()); // clone so we don't have to worry about mutated ops when we compare
}
}
}