Changhoon Yoon
Committed by Gerrit Code Review

ONOS-1858, ONOS-1857, ONOS-1860, ONOS-1862, ONOS-1898 : SM-ONOS

Change-Id: I206e72521cf663466bfcc612e1896bb22d87da06
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.cli.security;
import org.apache.karaf.shell.console.completer.StringsCompleter;
import org.onosproject.app.ApplicationService;
import org.onosproject.cli.AbstractCompleter;
import org.onosproject.core.Application;
import java.util.Iterator;
import java.util.List;
import java.util.SortedSet;
import static org.onosproject.cli.AbstractShellCommand.get;
/**
* Application name completer for permission command.
*/
public class PermissionApplicationNameCompleter extends AbstractCompleter {
@Override
public int complete(String buffer, int cursor, List<String> candidates) {
// Delegate string completer
StringsCompleter delegate = new StringsCompleter();
// Fetch our service and feed it's offerings to the string completer
ApplicationService service = get(ApplicationService.class);
Iterator<Application> it = service.getApplications().iterator();
SortedSet<String> strings = delegate.getStrings();
while (it.hasNext()) {
Application app = it.next();
strings.add(app.id().name());
}
// Now let the completer do the work for figuring out what to offer.
return delegate.complete(buffer, cursor, candidates);
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.cli.security;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.app.ApplicationAdminService;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.core.Application;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.Permission;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Manages application permissions.
*/
@Command(scope = "onos", name = "perm",
description = "Manages application permissions")
public class PermissionCommand extends AbstractShellCommand {
static final String ADD = "add";
static final String REMOVE = "remove";
static final String LIST = "list";
static final String CLEAR = "clear";
@Argument(index = 0, name = "command",
description = "Command name (add|remove)",
required = true, multiValued = false)
String command = null;
@Argument(index = 1, name = "name", description = "Application name",
required = true, multiValued = false)
String name = null;
@Argument(index = 2, name = "permissions", description = "List of permissions",
required = false, multiValued = true)
String[] permissions = null;
@Override
protected void execute() {
ApplicationAdminService applicationAdminService = get(ApplicationAdminService.class);
Set<Permission> newPermSet = Sets.newHashSet();
if (command.equals(ADD)) {
ApplicationId appId = applicationAdminService.getId(name);
if (appId == null) {
print("No such application: %s", name);
return;
}
Application app = applicationAdminService.getApplication(appId);
for (String perm : permissions) {
try {
Permission permission = Permission.valueOf(perm);
newPermSet.add(permission);
} catch (IllegalArgumentException e) {
print("%s is not a valid permission.", perm);
return;
}
}
Set<Permission> oldPermSet = applicationAdminService.getPermissions(appId);
if (oldPermSet != null) {
newPermSet.addAll(oldPermSet);
} else {
newPermSet.addAll(app.permissions());
}
applicationAdminService.setPermissions(appId, ImmutableSet.copyOf(newPermSet));
} else if (command.equals(REMOVE)) {
ApplicationId appId = applicationAdminService.getId(name);
Application app = applicationAdminService.getApplication(appId);
if (appId == null) {
print("No such application: %s", name);
return;
}
Set<Permission> oldPermSet = applicationAdminService.getPermissions(appId);
if (oldPermSet == null) {
oldPermSet = app.permissions();
}
Set<String> clearPermSet = Sets.newHashSet(permissions);
newPermSet.addAll(oldPermSet.stream().filter(
perm -> !clearPermSet.contains(perm.name().toUpperCase())).collect(Collectors.toList()));
applicationAdminService.setPermissions(appId, ImmutableSet.copyOf(newPermSet));
} else if (command.equals(CLEAR)) {
ApplicationId appId = applicationAdminService.getId(name);
if (appId == null) {
print("No such application: %s", name);
return;
}
applicationAdminService.setPermissions(appId, ImmutableSet.of());
print("Cleared the permission list of %s.", appId.name());
} else if (command.equals(LIST)) {
ApplicationId appId = applicationAdminService.getId(name);
if (appId == null) {
print("No such application: %s", name);
return;
}
Application app = applicationAdminService.getApplication(appId);
Set<Permission> userPermissions = applicationAdminService.getPermissions(appId);
Set<Permission> defaultPermissions = app.permissions();
print("Application Role");
print("\trole=%s", app.role().name());
if (defaultPermissions != null) {
if (!defaultPermissions.isEmpty()) {
print("Default permissions (specified in app.xml)");
for (Permission perm : defaultPermissions) {
print("\tpermission=%s", perm.name());
}
} else {
print("(No default permissions specified in app.xml)");
}
}
if (userPermissions != null) {
if (!userPermissions.isEmpty()) {
print("User permissions");
for (Permission perm : userPermissions) {
print("\tpermission=%s", perm.name());
}
} else {
print("(User has removed all the permissions");
}
}
}
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.cli.security;
import com.google.common.collect.ImmutableList;
import org.onosproject.cli.AbstractChoicesCompleter;
import java.util.List;
import static org.onosproject.cli.security.PermissionCommand.*;
/**
* Permission command completer.
*/
public class PermissionCommandCompleter extends AbstractChoicesCompleter {
@Override
protected List<String> choices() {
return ImmutableList.of(ADD, REMOVE, CLEAR, LIST);
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.cli.security;
import org.apache.karaf.shell.console.completer.ArgumentCompleter;
import org.onosproject.cli.AbstractChoicesCompleter;
import org.onosproject.core.Permission;
import java.util.ArrayList;
import java.util.List;
/**
* Permission Name Completer.
*/
public class PermissionNameCompleter extends AbstractChoicesCompleter {
@Override
protected List<String> choices() {
List<String> permNames = new ArrayList<>();
ArgumentCompleter.ArgumentList list = getArgumentList();
String cmd = list.getArguments()[1];
if (cmd.equals("add") || cmd.equals("remove")) {
for (Permission perm : Permission.values()) {
permNames.add(perm.name());
}
}
return permNames;
}
}
......@@ -21,6 +21,15 @@
</command>
<command>
<action class="org.onosproject.cli.security.PermissionCommand"/>
<completers>
<ref component-id="permCommandCompleter"/>
<ref component-id="permAppNameCompleter"/>
<ref component-id="permNameCompleter"/>
</completers>
</command>
<command>
<action class="org.onosproject.cli.app.ApplicationsListCommand"/>
</command>
......@@ -360,6 +369,9 @@
</command>
</command-bundle>
<bean id="permAppNameCompleter" class="org.onosproject.cli.security.PermissionApplicationNameCompleter"/>
<bean id="permCommandCompleter" class="org.onosproject.cli.security.PermissionCommandCompleter"/>
<bean id="permNameCompleter" class="org.onosproject.cli.security.PermissionNameCompleter"/>
<bean id="appCommandCompleter" class="org.onosproject.cli.app.ApplicationCommandCompleter"/>
<bean id="appNameCompleter" class="org.onosproject.cli.app.ApplicationNameCompleter"/>
<bean id="allAppNameCompleter" class="org.onosproject.cli.app.AllApplicationNamesCompleter"/>
......
......@@ -36,6 +36,7 @@
<module>common</module>
<module>net</module>
<module>store</module>
<module>security</module>
</modules>
<dependencies>
......
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2015 Open Networking Laboratory
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>onos-security</artifactId>
<groupId>org.onosproject</groupId>
<version>1.2.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>bundle</packaging>
<artifactId>onos-security-impl</artifactId>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.scr.annotations</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-api</artifactId>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-security-util</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.karaf.features</groupId>
<artifactId>org.apache.karaf.features.core</artifactId>
</dependency>
</dependencies>
</project>
\ No newline at end of file
package org.onosproject.security.impl;
import org.apache.commons.collections.FastHashMap;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.karaf.features.BundleInfo;
import org.apache.karaf.features.Feature;
import org.apache.karaf.features.FeaturesService;
import org.onosproject.app.ApplicationAdminService;
import org.onosproject.app.ApplicationEvent;
import org.onosproject.app.ApplicationListener;
import org.onosproject.app.ApplicationState;
import org.onosproject.core.Application;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.Permission;
import org.onosproject.security.util.AppPermission;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.PackagePermission;
import org.osgi.framework.ServicePermission;
import org.osgi.service.log.LogEntry;
import org.osgi.service.log.LogListener;
import org.osgi.service.log.LogReaderService;
import org.osgi.service.permissionadmin.PermissionInfo;
import java.security.AccessControlException;
import java.security.AllPermission;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.osgi.service.permissionadmin.PermissionAdmin;
import org.slf4j.Logger;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Security-Mode ONOS management implementation.
*/
//TODO : implement a dedicated distributed store for SM-ONOS
@Component(immediate = true)
public class SecurityModeManager {
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ApplicationAdminService appAdminService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected FeaturesService featuresService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected LogReaderService logReaderService;
private final Logger log = getLogger(getClass());
private SecurityBundleListener securityBundleListener = new SecurityBundleListener();
private SecurityApplicationListener securityApplicationListener = new SecurityApplicationListener();
private SecurityLogListener securityLogListener = new SecurityLogListener();
private Bundle bundle = null;
private BundleContext bundleContext = null;
private PermissionAdmin permissionAdmin = null;
private HashMap<String, ApplicationId> appTracker = null;
private HashMap<Permission, Set<String>> serviceDirectory = null;
@Activate
public void activate() {
if (System.getSecurityManager() == null) {
log.warn("J2EE security manager is disabled.");
deactivate();
return;
}
bundle = FrameworkUtil.getBundle(this.getClass());
bundleContext = bundle.getBundleContext();
bundleContext.addBundleListener(securityBundleListener);
appAdminService.addListener(securityApplicationListener);
logReaderService.addLogListener(securityLogListener);
appTracker = new FastHashMap();
permissionAdmin = getPermissionAdmin(bundleContext);
if (permissionAdmin == null) {
log.warn("Permission Admin not found.");
this.deactivate();
return;
}
serviceDirectory = PolicyBuilder.getServiceDirectory();
PermissionInfo[] allPerm = {
new PermissionInfo(AllPermission.class.getName(), "", ""), };
permissionAdmin.setPermissions(bundle.getLocation(), allPerm);
log.warn("Security-Mode Started");
}
@Deactivate
public void deactivate() {
bundleContext.removeBundleListener(securityBundleListener);
appAdminService.removeListener(securityApplicationListener);
logReaderService.removeLogListener(securityLogListener);
log.info("Stopped");
}
private class SecurityApplicationListener implements ApplicationListener {
@Override
public void event(ApplicationEvent event) {
//App needs to be restarted
if (event.type() == ApplicationEvent.Type.APP_PERMISSIONS_CHANGED) {
if (appAdminService.getState(event.subject().id()) == ApplicationState.ACTIVE) {
appAdminService.deactivate(event.subject().id());
print("Permissions updated (%s). Deactivating...",
event.subject().id().name());
}
}
}
}
private class SecurityBundleListener implements BundleListener {
@Override
public void bundleChanged(BundleEvent event) {
switch (event.getType()) {
case BundleEvent.INSTALLED:
setPermissions(event);
break;
case BundleEvent.UNINSTALLED:
clearPermissions(event);
break;
default:
break;
}
}
}
private void clearPermissions(BundleEvent bundleEvent) {
if (appTracker.containsKey(bundleEvent.getBundle().getLocation())) {
permissionAdmin.setPermissions(bundleEvent.getBundle().getLocation(), new PermissionInfo[]{});
appTracker.remove(bundleEvent.getBundle().getLocation());
}
}
// find the location of the installed bundle and enforce policy
private void setPermissions(BundleEvent bundleEvent) {
for (Application app : appAdminService.getApplications()) {
if (getBundleLocations(app).contains(bundleEvent.getBundle().getLocation())) {
String location = bundleEvent.getBundle().getLocation();
Set<org.onosproject.core.Permission> permissions =
appAdminService.getPermissions(app.id());
//Permissions granted by user overrides the permissions specified in App.Xml file
if (permissions == null) {
permissions = app.permissions();
}
if (permissions.isEmpty()) {
print("Application %s has not been granted any permission.", app.id().name());
}
PermissionInfo[] perms = null;
switch (app.role()) {
case ADMIN:
perms = PolicyBuilder.getAdminApplicationPermissions(serviceDirectory);
break;
case REGULAR:
perms = PolicyBuilder.getApplicationPermissions(serviceDirectory, permissions);
break;
case UNSPECIFIED:
default:
//no role has been assigned.
perms = PolicyBuilder.getDefaultPerms();
log.warn("Application %s has no role assigned.", app.id().name());
break;
}
permissionAdmin.setPermissions(location, perms);
appTracker.put(location, app.id());
break;
}
}
}
//TODO: dispatch security policy violation event via distributed store
//immediately notify and deactivate the application upon policy violation
private class SecurityLogListener implements LogListener {
@Override
public void logged(LogEntry entry) {
if (entry != null) {
if (entry.getException() != null) {
ApplicationId applicationId = appTracker.get(entry.getBundle().getLocation());
if (applicationId != null) {
if (appAdminService.getState(applicationId).equals(ApplicationState.ACTIVE)) {
if (entry.getException() instanceof AccessControlException) {
java.security.Permission permission =
((AccessControlException) entry.getException()).getPermission();
handleException(applicationId.name(), permission);
appAdminService.deactivate(applicationId);
}
}
}
}
}
}
}
private void handleException(String name, java.security.Permission perm) {
if (perm instanceof ServicePermission || perm instanceof PackagePermission) {
print("%s has attempted to %s %s.", name, perm.getActions(), perm.getName());
} else if (perm instanceof AppPermission) {
print("%s has attempted to call an NB API that requires %s permission.",
name, perm.getName().toUpperCase());
} else {
print("%s has attempted to perform an action that requires %s", name, perm.toString());
}
print("POLICY VIOLATION: Deactivating %s.", name);
}
private void print(String format, Object... args) {
System.out.println(String.format("SM-ONOS: " + format, args));
log.warn(String.format(format, args));
}
private List<String> getBundleLocations(Application app) {
List<String> locations = new ArrayList();
for (String name : app.features()) {
try {
Feature feature = featuresService.getFeature(name);
locations.addAll(
feature.getBundles().stream().map(BundleInfo::getLocation).collect(Collectors.toList()));
} catch (Exception e) {
return locations;
}
}
return locations;
}
private PermissionAdmin getPermissionAdmin(BundleContext context) {
return (PermissionAdmin) context.getService(context.getServiceReference(PermissionAdmin.class.getName()));
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>onos-core</artifactId>
<groupId>org.onosproject</groupId>
<version>1.2.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>onos-security</artifactId>
<packaging>pom</packaging>
<modules>
<module>util</module>
<module>impl</module>
</modules>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>onos-security</artifactId>
<groupId>org.onosproject</groupId>
<version>1.2.0-SNAPSHOT</version>
</parent>
<artifactId>onos-security-util</artifactId>
<packaging>bundle</packaging>
</project>
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.util;
/**
* Checks if the caller has the required permission to call each API.
*/
public final class AppGuard {
private AppGuard() {
}
public static boolean check(String perm) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
System.getSecurityManager().checkPermission(new AppPermission(perm));
}
return true;
}
}
/*
* Copyright 2015 Open Networking Laboratory
*
* 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.util;
import java.security.BasicPermission;
/**
* Implementation of API access permission.
*/
public class AppPermission extends BasicPermission {
public AppPermission(String name) {
super(name.toUpperCase(), "");
}
public AppPermission(String name, String actions) {
super(name.toUpperCase(), actions);
}
}
......@@ -123,6 +123,13 @@
<bundle>mvn:org.onosproject/onos-cli/@ONOS-VERSION</bundle>
</feature>
<feature name="onos-security" version="@FEATURE-VERSION"
description="Security-Mode ONOS">
<!--<bundle>mvn:org.onosproject/onos-security-felix/2.2.0-ONOS</bundle>-->
<bundle>mvn:org.onosproject/onos-security-impl/@ONOS-VERSION</bundle>
<bundle>mvn:org.onosproject/onos-security-util/@ONOS-VERSION</bundle>
</feature>
<!-- Deprecated! For standalone testing only. -->
<feature name="onos-core-trivial" version="@FEATURE-VERSION"
description="ONOS trivial core components">
......