diff --git a/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java b/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java index 494d7d8d6e..cbcd8d4faa 100644 --- a/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java +++ b/cli/src/main/java/org/onosproject/cli/AbstractShellCommand.java @@ -29,6 +29,7 @@ import org.onosproject.net.Annotations; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.onosproject.net.DefaultAnnotations; +import org.onosproject.security.AuditService; import org.slf4j.Logger; import static org.slf4j.LoggerFactory.getLogger; @@ -43,63 +44,12 @@ public abstract class AbstractShellCommand implements Action, CodecContext { protected static final Logger log = getLogger(AbstractShellCommand.class); + private final ObjectMapper mapper = new ObjectMapper(); + @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. * @@ -109,7 +59,6 @@ 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); } @@ -204,8 +153,9 @@ public abstract class AbstractShellCommand implements Action, CodecContext { } @Override - public Object execute() throws Exception { + public final Object execute() throws Exception { try { + auditCommand(); doExecute(); } catch (ServiceNotFoundException e) { error(e.getMessage()); @@ -213,15 +163,23 @@ public abstract class AbstractShellCommand implements Action, CodecContext { return null; } - protected void doExecute() throws Exception { - try { - execute(); - } catch (ServiceNotFoundException e) { - error(e.getMessage()); + // Handles auditing + private void auditCommand() { + AuditService auditService = get(AuditService.class); + if (auditService != null && auditService.isAuditing()) { + // FIXME: Compose and log audit message here; this is a hack + String user = "foo"; // FIXME + String action = Thread.currentThread().getName().substring(5); // FIXME + auditService.logUserAction(user, action); } } - private final ObjectMapper mapper = new ObjectMapper(); + /** + * Body of the shell command. + * + * @throws Exception thrown when problem is encountered + */ + protected abstract void doExecute() throws Exception; @Override public ObjectMapper mapper() { diff --git a/core/api/src/main/java/org/onosproject/security/AuditService.java b/core/api/src/main/java/org/onosproject/security/AuditService.java new file mode 100644 index 0000000000..f98eda423d --- /dev/null +++ b/core/api/src/main/java/org/onosproject/security/AuditService.java @@ -0,0 +1,39 @@ +/* + * Copyright 2018-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.security; + +/** + * Service for enabling audit logging. + */ +public interface AuditService { + + /** + * Returns true if auditing is enabled. + * + * @return true if audit enabled; false otherwise + */ + boolean isAuditing(); + + /** + * Logs the specified user action. + * + * @param user user that initiated the action + * @param action action being logged + */ + void logUserAction(String user, String action); + +} diff --git a/core/net/BUILD b/core/net/BUILD index 50e0a3ffff..8047609e57 100644 --- a/core/net/BUILD +++ b/core/net/BUILD @@ -1,9 +1,7 @@ COMPILE_DEPS = CORE_DEPS + JACKSON + METRICS + KRYO + [ "//core/common:onos-core-common", - "//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/audit/impl/AuditManager.java b/core/net/src/main/java/org/onosproject/audit/impl/AuditManager.java new file mode 100644 index 0000000000..a871f3c018 --- /dev/null +++ b/core/net/src/main/java/org/onosproject/audit/impl/AuditManager.java @@ -0,0 +1,99 @@ +/* + * 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.audit.impl; + +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.security.AuditService; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Dictionary; + +import static org.onlab.util.Tools.get; +import static org.onosproject.net.OsgiPropertyConstants.AUDIT_LOGGER; +import static org.onosproject.net.OsgiPropertyConstants.AUDIT_LOGGER_DEFAULT; +import static org.onosproject.net.OsgiPropertyConstants.AUDIT_ENABLED; +import static org.onosproject.net.OsgiPropertyConstants.AUDIT_ENABLED_DEFAULT; + +/** + * Component to manage audit logging. + */ +@Component( + immediate = true, + service = { AuditService.class }, + property = { + AUDIT_ENABLED + ":Boolean=" + AUDIT_ENABLED_DEFAULT, + AUDIT_LOGGER + "=" + AUDIT_LOGGER_DEFAULT, + }) +public class AuditManager implements AuditService { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private Logger auditLog = log; + + /** Specifies whether or not audit logging is enabled. */ + private boolean auditEnabled = AUDIT_ENABLED_DEFAULT; + + /** Name of the audit logger. */ + private String auditFile = AUDIT_LOGGER_DEFAULT; + + @Reference(cardinality = ReferenceCardinality.MANDATORY) + protected ComponentConfigService cfgService; + + @Activate + protected void activate(ComponentContext ctx) { + cfgService.registerProperties(getClass()); + modified(ctx); + log.info("Started"); + } + + @Deactivate + protected void deactivate(ComponentContext ctx) { + log.info("Stopped"); + } + + @Modified + protected void modified(ComponentContext ctx) { + Dictionary properties = ctx.getProperties(); + if (properties != null) { + auditEnabled = Boolean.parseBoolean(get(properties, AUDIT_ENABLED)); + auditFile = get(properties, AUDIT_LOGGER); + auditLog = LoggerFactory.getLogger(auditFile); + log.info("Reconfigured; auditEnabled={}; auditFile={}", auditEnabled, auditFile); + } + } + + @Override + public boolean isAuditing() { + return auditEnabled; + } + + @Override + public void logUserAction(String user, String action) { + if (auditEnabled) { + auditLog.info("user={}; action={}", user, action); + } + } + +} diff --git a/core/net/src/main/java/org/onosproject/net/audit/impl/package-info.java b/core/net/src/main/java/org/onosproject/audit/impl/package-info.java similarity index 94% rename from core/net/src/main/java/org/onosproject/net/audit/impl/package-info.java rename to core/net/src/main/java/org/onosproject/audit/impl/package-info.java index 2845808da9..370c9de7a3 100644 --- a/core/net/src/main/java/org/onosproject/net/audit/impl/package-info.java +++ b/core/net/src/main/java/org/onosproject/audit/impl/package-info.java @@ -17,4 +17,4 @@ /** * Implementation of Audit Configuration. */ -package org.onosproject.net.audit.impl; \ No newline at end of file +package org.onosproject.audit.impl; \ No newline at end of file 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 db5c5e3cf7..3d6dec48e2 100644 --- a/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java +++ b/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java @@ -125,10 +125,10 @@ 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_ENABLED = "auditEnabled"; + public static final boolean AUDIT_ENABLED_DEFAULT = false; - public static final String AUDIT_FILE_TYPE_DESC = "auditFile"; - public static final String AUDIT_FILE_TYPE_DEFAULT = "all"; + public static final String AUDIT_LOGGER = "auditLogger"; + public static final String AUDIT_LOGGER_DEFAULT = "securityAudit"; } 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 deleted file mode 100644 index 31b6a18238..0000000000 --- a/core/net/src/main/java/org/onosproject/net/audit/impl/AuditManager.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java b/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java index f6c2739ebc..41839c75e9 100644 --- a/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java +++ b/utils/rest/src/main/java/org/onlab/rest/AbstractWebApplication.java @@ -54,7 +54,6 @@ 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/web/api/src/main/java/org/onosproject/rest/resources/AuditFilter.java similarity index 51% rename from utils/rest/src/main/java/org/onlab/rest/AuditFilter.java rename to web/api/src/main/java/org/onosproject/rest/resources/AuditFilter.java index 0f098ace1b..38471abcd6 100644 --- a/utils/rest/src/main/java/org/onlab/rest/AuditFilter.java +++ b/web/api/src/main/java/org/onosproject/rest/resources/AuditFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-present Open Networking Foundation + * Copyright 2018-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. @@ -14,11 +14,13 @@ * limitations under the License. */ -package org.onlab.rest; +package org.onosproject.rest.resources; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.io.IOUtils; -import org.slf4j.Logger; +import org.onlab.osgi.DefaultServiceDirectory; +import org.onlab.osgi.ServiceDirectory; +import org.onosproject.security.AuditService; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; @@ -27,34 +29,36 @@ 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. + * HTTP Filter for auditing REST API requests. */ - 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; + private static ServiceDirectory services = new DefaultServiceDirectory(); + + /** + * Disables functionality for unit tests. + */ + public static void disableForTests() { + disableForTests = true; + } @Override public void filter(ContainerRequestContext requestContext) throws IOException { - if (disableForTests) { - return; - } - if (isEnabled()) { + if (auditService() != null) { String requestBody = (requestContext.hasEntity() ? (readTreeFromStream(mapper, requestContext.getEntityStream()).toString()) : ""); requestContext.setProperty("requestBody", requestBody); - requestContext.setProperty("auditLog", "Path: " + requestContext.getUriInfo().getPath() + separator + // FIXME: audit message should be better structured + requestContext.setProperty("auditMessage", "Path: " + requestContext.getUriInfo().getPath() + separator + "Method: " + requestContext.getMethod() + separator + (requestContext.getMethod().equals("PUT") ? + // FIXME: is there really a need to differentiate based on method? ("Path_Parameters: " + requestContext.getUriInfo().getPathParameters().toString() + separator + "Query_Parameters: " + requestContext.getUriInfo().getQueryParameters().toString() + separator + "Request_Body: " + requestBody) : "")); @@ -65,72 +69,19 @@ public class AuditFilter implements ContainerRequestFilter, ContainerResponseFil @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()); + AuditService auditService = auditService(); + if (auditService != null) { + containerRequestContext.setProperty("auditMessage", containerRequestContext.getProperty("auditMessage") + + separator + "Status: " + containerResponseContext.getStatusInfo().toString()); + // FIXME: Audit record should indicate who did it, not just what was done and when + String user = containerRequestContext.getSecurityContext().getUserPrincipal().getName(); + String action = containerRequestContext.getProperty("auditMessage").toString(); + auditService.logUserAction(user, action); } } - /** - * To disable unit testing for this class. - */ - public static void disableForTests() { - disableForTests = true; + private AuditService auditService() { + AuditService auditService = disableForTests ? null : services.get(AuditService.class); + return auditService != null && auditService.isAuditing() ? auditService : null; } - - /** - * 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/main/java/org/onosproject/rest/resources/CoreWebApplication.java b/web/api/src/main/java/org/onosproject/rest/resources/CoreWebApplication.java index 37242a087b..a6227bc967 100644 --- a/web/api/src/main/java/org/onosproject/rest/resources/CoreWebApplication.java +++ b/web/api/src/main/java/org/onosproject/rest/resources/CoreWebApplication.java @@ -53,7 +53,8 @@ public class CoreWebApplication extends AbstractWebApplication { DiagnosticsWebResource.class, UiPreferencesWebResource.class, SystemInfoWebResource.class, - PacketProcessorsWebResource.class + PacketProcessorsWebResource.class, + AuditFilter.class ); } } 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 9ccf53f8c8..54203fd419 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,7 +24,6 @@ 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; /**