/* * 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 } } }