diff --git a/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java b/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java index d6d044efe7..494d7d8d6e 100644 --- a/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java +++ b/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java @@ -30,7 +30,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.onosproject.net.DefaultAnnotations; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; + +import static org.slf4j.LoggerFactory.getLogger; import java.util.Set; import java.util.TreeSet; @@ -39,12 +40,66 @@ import java.util.TreeSet; * Base abstraction of Karaf shell commands. */ public abstract class AbstractShellCommand implements Action, CodecContext { - protected final Logger log = LoggerFactory.getLogger(this.getClass()); + + protected static final Logger log = getLogger(AbstractShellCommand.class); @Option(name = "-j", aliases = "--json", description = "Output JSON", required = false, multiValued = false) private boolean json = false; + private static String auditFile = "all"; + private static boolean auditEnabled = false; + + /** + * To check if CLI Audit is enabled. + * + * @return true if the CLI Audit is enabled. + */ + private static boolean isEnabled() { + return auditEnabled; + } + + /** + * To enable CLI Audit. + */ + public static void enableAudit() { + auditEnabled = true; + } + + /** + * To disable CLI Audit. + */ + public static void disableAudit() { + auditEnabled = false; + } + + /** + * To set audit file type which CLI Audit logs must be saved. + * + * @param auditFile file that CLI Audit logs must be saved. + */ + public static void setAuditFile(String auditFile) { + AbstractShellCommand.auditFile = auditFile; + } + + /** + * To save audit logs into the log file. + * + * @param msg audit message. + */ + private static void saveAuditLog(String msg) { + if (isEnabled()) { + if (auditFile.equals("all")) { + log.info(msg); + log.info("AuditLog : " + msg); + } else if (auditFile.equals("karaf")) { + log.info(msg); + } else if (auditFile.equals("audit")) { + log.info("AuditLog : " + msg); + } + } + } + /** * Returns the reference to the implementation of the specified service. * @@ -54,6 +109,7 @@ public abstract class AbstractShellCommand implements Action, CodecContext { * @throws org.onlab.osgi.ServiceNotFoundException if service is unavailable */ public static T get(Class serviceClass) { + saveAuditLog("Audit "); return DefaultServiceDirectory.getService(serviceClass); } @@ -64,7 +120,7 @@ public abstract class AbstractShellCommand implements Action, CodecContext { */ protected ApplicationId appId() { return get(CoreService.class) - .registerApplication("org.onosproject.cli"); + .registerApplication("org.onosproject.cli"); } /** @@ -126,7 +182,7 @@ public abstract class AbstractShellCommand implements Action, CodecContext { /** * Produces a JSON object from the specified key/value annotations. * - * @param mapper ObjectMapper to use while converting to JSON + * @param mapper ObjectMapper to use while converting to JSON * @param annotations key/value annotations * @return JSON object */ @@ -186,9 +242,9 @@ public abstract class AbstractShellCommand implements Action, CodecContext { /** * Generates a Json representation of an object. * - * @param entity object to generate JSON for + * @param entity object to generate JSON for * @param entityClass class to format with - this chooses which codec to use - * @param Type of the object being formatted + * @param Type of the object being formatted * @return JSON object representation */ public ObjectNode jsonForEntity(T entity, Class entityClass) { diff --git a/core/net/BUILD b/core/net/BUILD index fc6c46dab3..50e0a3ffff 100644 --- a/core/net/BUILD +++ b/core/net/BUILD @@ -3,6 +3,7 @@ COMPILE_DEPS = CORE_DEPS + JACKSON + METRICS + KRYO + [ "//utils/rest:onlab-rest", "//core/store/serializers:onos-core-serializers", "//core/store/primitives:onos-core-primitives", + "//cli:onos-cli", "@org_osgi_service_cm//jar", ] diff --git a/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java b/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java index dd96525e32..db5c5e3cf7 100644 --- a/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java +++ b/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java @@ -124,4 +124,11 @@ public final class OsgiPropertyConstants { public static final String DTP_MAX_BATCH_MS = "maxBatchMs"; public static final int DTP_MAX_BATCH_MS_DEFAULT = 50; + + public static final String AUDIT_STATUS_DESC = "auditEnabled"; + public static final boolean AUDIT_STATUS_DEFAULT = false; + + public static final String AUDIT_FILE_TYPE_DESC = "auditFile"; + public static final String AUDIT_FILE_TYPE_DEFAULT = "all"; + } diff --git a/core/net/src/main/java/org/onosproject/net/audit/impl/AuditManager.java b/core/net/src/main/java/org/onosproject/net/audit/impl/AuditManager.java new file mode 100644 index 0000000000..31b6a18238 --- /dev/null +++ b/core/net/src/main/java/org/onosproject/net/audit/impl/AuditManager.java @@ -0,0 +1,92 @@ +/* + * Copyright 2016-present Open Networking Foundation + * + * 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 + * + * http://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 org.onosproject.net.audit.impl; + +import org.onlab.rest.AuditFilter; + +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.cli.AbstractShellCommand; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Modified; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; + +import java.util.Dictionary; + +import static org.onlab.util.Tools.get; +import static org.onosproject.net.OsgiPropertyConstants.AUDIT_FILE_TYPE_DESC; +import static org.onosproject.net.OsgiPropertyConstants.AUDIT_FILE_TYPE_DEFAULT; +import static org.onosproject.net.OsgiPropertyConstants.AUDIT_STATUS_DESC; +import static org.onosproject.net.OsgiPropertyConstants.AUDIT_STATUS_DEFAULT; + + +/** + * Component to manage REST API Audit. + */ +@Component( + immediate = true, + property = { + AUDIT_FILE_TYPE_DESC + "=" + AUDIT_FILE_TYPE_DEFAULT, + AUDIT_STATUS_DESC + ":Boolean=" + AUDIT_STATUS_DEFAULT + }) +public class AuditManager { + + public String auditFile = AUDIT_FILE_TYPE_DEFAULT; + public boolean auditEnabled = AUDIT_STATUS_DEFAULT; + + @Reference(cardinality = ReferenceCardinality.MANDATORY) + protected ComponentConfigService cfgService; + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + setAuditStatus(auditFile, auditEnabled); + } + + @Modified + protected void modifyFileType(ComponentContext context) { + Dictionary properties = context.getProperties(); + if (properties == null) { + return; + } + auditFile = get(properties, AUDIT_FILE_TYPE_DESC); + String enableAuditStr = get(properties, AUDIT_STATUS_DESC); + + auditEnabled = Boolean.parseBoolean(enableAuditStr); + setAuditStatus(auditFile, auditEnabled); + } + + /** + * To enable Audit and set file type for REST API and CLI as per the changes in configuration properties. + * + * @param auditFile file which audit logs are saved. + * @param auditEnabled status of REST API Audit and CLI Audit. + */ + public void setAuditStatus(String auditFile, boolean auditEnabled) { + if (auditEnabled) { + AuditFilter.enableAudit(); + AbstractShellCommand.enableAudit(); + } else { + AuditFilter.disableAudit(); + AbstractShellCommand.disableAudit(); + } + AuditFilter.setAuditFile(auditFile); + AbstractShellCommand.setAuditFile(auditFile); + } +} diff --git a/core/net/src/main/java/org/onosproject/net/audit/impl/package-info.java b/core/net/src/main/java/org/onosproject/net/audit/impl/package-info.java new file mode 100644 index 0000000000..2845808da9 --- /dev/null +++ b/core/net/src/main/java/org/onosproject/net/audit/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015-present Open Networking Foundation + * + * 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 + * + * http://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. + */ + +/** + * Implementation of Audit Configuration. + */ +package org.onosproject.net.audit.impl; \ No newline at end of file diff --git a/tools/package/etc/org.ops4j.pax.logging.cfg b/tools/package/etc/org.ops4j.pax.logging.cfg new file mode 100644 index 0000000000..88688aa0f2 --- /dev/null +++ b/tools/package/etc/org.ops4j.pax.logging.cfg @@ -0,0 +1,131 @@ +################################################################################ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You 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 +# +# http://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. +# +################################################################################ + +# Colors for log level rendering +color.fatal = bright red +color.error = bright red +color.warn = bright yellow +color.info = bright green +color.debug = cyan +color.trace = cyan + +# Common pattern layout for appenders +log4j2.pattern = %d{ISO8601} | %-5p | %-16t | %-32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n +log4j2.out.pattern = \u001b[90m%d{HH:mm:ss\.SSS}\u001b[0m %highlight{%-5level}{FATAL=${color.fatal}, ERROR=${color.error}, WARN=${color.warn}, INFO=${color.info}, DEBUG=${color.debug}, TRACE=${color.trace}} \u001b[90m[%t]\u001b[0m %msg%n%throwable + + +# Root logger +log4j2.rootLogger.level = INFO +# uncomment to use asynchronous loggers, which require mvn:com.lmax/disruptor/3.3.2 library +#log4j2.rootLogger.type = asyncRoot +#log4j2.rootLogger.includeLocation = false +log4j2.rootLogger.appenderRef.RollingFile.ref = RollingFile +log4j2.rootLogger.appenderRef.AuditFile.ref = AuditFile +log4j2.rootLogger.appenderRef.PaxOsgi.ref = PaxOsgi +log4j2.rootLogger.appenderRef.Console.ref = Console +log4j2.rootLogger.appenderRef.Console.filter.regex.type = RegexFilter +log4j2.rootLogger.appenderRef.Console.filter.regex.regex = .*Audit.* +log4j2.rootLogger.appenderRef.Console.filter.regex.onMatch = DENY +log4j2.rootLogger.appenderRef.Console.filter.regex.onMisMatch = ACCEPT +#log4j2.rootLogger.appenderRef.Console.filter.threshold.type = ThresholdFilter +#log4j2.rootLogger.appenderRef.Console.filter.threshold.level = ${karaf.log.console:-OFF} + +# Loggers configuration + +# SSHD logger +log4j2.logger.sshd.name = org.apache.sshd +log4j2.logger.sshd.level = INFO + +# Spifly logger +log4j2.logger.spifly.name = org.apache.aries.spifly +log4j2.logger.spifly.level = WARN + +# Security audit logger +log4j2.logger.audit.name = audit +log4j2.logger.audit.level = TRACE +log4j2.logger.audit.additivity = false +log4j2.logger.audit.appenderRef.AuditRollingFile.ref = AuditRollingFile + +# Appenders configuration + +# Console appender not used by default (see log4j2.rootLogger.appenderRefs) +log4j2.appender.console.type = Console +log4j2.appender.console.name = Console +log4j2.appender.console.layout.type = PatternLayout +log4j2.appender.console.layout.pattern = ${log4j2.out.pattern} + +# Rest Audit file appender +log4j2.appender.auditOnos.type = RollingRandomAccessFile +log4j2.appender.auditOnos.name = AuditFile +log4j2.appender.auditOnos.filter.regex.type = RegexFilter +log4j2.appender.auditOnos.filter.regex.regex = .*AuditLog.* +log4j2.appender.auditOnos.filter.regex.onMatch = ACCEPT +log4j2.appender.auditOnos.filter.regex.onMisMatch = DENY +log4j2.appender.auditOnos.fileName = ${karaf.data}/log/audit.log +log4j2.appender.auditOnos.filePattern = ${karaf.data}/log/audit-%i.log +log4j2.appender.auditOnos.append = true +log4j2.appender.auditOnos.layout.type = PatternLayout +log4j2.appender.auditOnos.layout.pattern = ${log4j2.pattern} +log4j2.appender.auditOnos.policies.type = Policies +log4j2.appender.auditOnos.policies.size.type = SizeBasedTriggeringPolicy +log4j2.appender.auditOnos.policies.size.size = 8MB + + +# Rolling file appender +log4j2.appender.rolling.type = RollingRandomAccessFile +log4j2.appender.rolling.name = RollingFile +log4j2.appender.rolling.filter.regex.type = RegexFilter +log4j2.appender.rolling.filter.regex.regex = .*AuditLog.* +log4j2.appender.rolling.filter.regex.onMatch = DENY +log4j2.appender.rolling.filter.regex.onMisMatch = ACCEPT +log4j2.appender.rolling.fileName = ${karaf.data}/log/karaf.log +log4j2.appender.rolling.filePattern = ${karaf.data}/log/karaf.log.%i +# uncomment to not force a disk flush +#log4j2.appender.rolling.immediateFlush = false +log4j2.appender.rolling.append = true +log4j2.appender.rolling.layout.type = PatternLayout +log4j2.appender.rolling.layout.pattern = ${log4j2.pattern} +log4j2.appender.rolling.policies.type = Policies +log4j2.appender.rolling.policies.size.type = SizeBasedTriggeringPolicy +log4j2.appender.rolling.policies.size.size = 16MB + +# Audit file appender +log4j2.appender.audit.type = RollingRandomAccessFile +log4j2.appender.audit.name = AuditRollingFile +log4j2.appender.audit.fileName = ${karaf.data}/log/security.log +log4j2.appender.audit.filePattern = ${karaf.data}/log/security-%i.log +log4j2.appender.audit.append = true +log4j2.appender.audit.layout.type = PatternLayout +log4j2.appender.audit.layout.pattern = %m%n +log4j2.appender.audit.policies.type = Policies +log4j2.appender.audit.policies.size.type = SizeBasedTriggeringPolicy +log4j2.appender.audit.policies.size.size = 8MB + +# OSGi appender +log4j2.appender.osgi.type = PaxOsgi +log4j2.appender.osgi.name = PaxOsgi +log4j2.appender.osgi.filter = * + +# help with identification of maven-related problems with pax-url-aether +#log4j2.logger.aether.name = shaded.org.eclipse.aether +#log4j2.logger.aether.level = TRACE +#log4j2.logger.http-headers.name = shaded.org.apache.http.headers +#log4j2.logger.http-headers.level = DEBUG +#log4j2.logger.maven.name = org.ops4j.pax.url.mvn +#log4j2.logger.maven.level = TRACE diff --git a/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java b/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java index 41839c75e9..f6c2739ebc 100644 --- a/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java +++ b/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java @@ -54,6 +54,7 @@ public abstract class AbstractWebApplication extends Application { WebApplicationExceptionMapper.class, IllegalArgumentExceptionMapper.class, IllegalStateExceptionMapper.class, + AuditFilter.class, JsonBodyWriter.class); builder.add(classes); return builder.build(); diff --git a/utils/rest/src/main/java/org/onlab/rest/AuditFilter.java b/utils/rest/src/main/java/org/onlab/rest/AuditFilter.java new file mode 100644 index 0000000000..0f098ace1b --- /dev/null +++ b/utils/rest/src/main/java/org/onlab/rest/AuditFilter.java @@ -0,0 +1,136 @@ +/* + * Copyright 2016-present Open Networking Foundation + * + * 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 + * + * http://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 org.onlab.rest; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.container.ContainerResponseContext; +import javax.ws.rs.container.ContainerResponseFilter; +import java.io.IOException; + +import static org.onlab.util.Tools.readTreeFromStream; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * FIlter for logging all REST Api http requests and details of request and response. + */ + +public class AuditFilter implements ContainerRequestFilter, ContainerResponseFilter { + + private static Logger log = getLogger(AuditFilter.class); + private ObjectMapper mapper = new ObjectMapper(); + private final String separator = " | "; + + private static boolean disableForTests = false; + private static String auditFile = "all"; + private static boolean auditEnabled = false; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + if (disableForTests) { + return; + } + if (isEnabled()) { + String requestBody = (requestContext.hasEntity() ? + (readTreeFromStream(mapper, requestContext.getEntityStream()).toString()) : ""); + requestContext.setProperty("requestBody", requestBody); + requestContext.setProperty("auditLog", "Path: " + requestContext.getUriInfo().getPath() + separator + + "Method: " + requestContext.getMethod() + separator + + (requestContext.getMethod().equals("PUT") ? + ("Path_Parameters: " + requestContext.getUriInfo().getPathParameters().toString() + separator + + "Query_Parameters: " + requestContext.getUriInfo().getQueryParameters().toString() + + separator + "Request_Body: " + requestBody) : "")); + requestContext.setEntityStream(IOUtils.toInputStream(requestBody)); + } + } + + @Override + public void filter(ContainerRequestContext containerRequestContext, + ContainerResponseContext containerResponseContext) throws IOException { + if (disableForTests) { + return; + } + if (isEnabled()) { + containerRequestContext.setProperty("auditLog", containerRequestContext.getProperty("auditLog") + separator + + "Status: " + containerResponseContext.getStatusInfo().toString()); + saveAuditLog(containerRequestContext.getProperty("auditLog").toString()); + } + } + + /** + * To disable unit testing for this class. + */ + public static void disableForTests() { + disableForTests = true; + } + + /** + * To save audit logs into the log file. + * + * @param msg audit message. + */ + private void saveAuditLog(String msg) { + if (isEnabled()) { + if (auditFile.equals("all")) { + log.info(msg); + log.info("AuditLog : " + msg); + } else if (auditFile.equals("karaf")) { + log.info(msg); + } else if (auditFile.equals("audit")) { + log.info("AuditLog : " + msg); + } + } + } + + /** + * To check if REST API Audit is enabled. + * + * @return true if the REST API Audit is enabled. + */ + private static boolean isEnabled() { + return auditEnabled; + } + + /** + * To enable REST API Audit. + */ + public static void enableAudit() { + auditEnabled = true; + } + + /** + * To disable REST API Audit. + */ + public static void disableAudit() { + auditEnabled = false; + } + + /** + * To set audit file type which REST API Audit logs must be saved. + * + * @param auditFile file that REST API Audit logs are saved. + */ + public static void setAuditFile(String auditFile) { + AuditFilter.auditFile = auditFile; + } + + +} diff --git a/web/api/src/test/java/org/onosproject/rest/resources/ResourceTest.java b/web/api/src/test/java/org/onosproject/rest/resources/ResourceTest.java index 9f809f35b4..9ccf53f8c8 100644 --- a/web/api/src/test/java/org/onosproject/rest/resources/ResourceTest.java +++ b/web/api/src/test/java/org/onosproject/rest/resources/ResourceTest.java @@ -24,6 +24,7 @@ import org.glassfish.jersey.test.spi.TestContainerFactory; import org.onlab.junit.TestUtils; import org.onlab.osgi.ServiceDirectory; import org.onlab.rest.AuthorizationFilter; +import org.onlab.rest.AuditFilter; import org.onlab.rest.BaseResource; /** @@ -51,6 +52,7 @@ public class ResourceTest extends JerseyTest { private void configureProperties() { set(TestProperties.CONTAINER_PORT, 0); AuthorizationFilter.disableForTests(); + AuditFilter.disableForTests(); } /**