blob: d2d510d5ff78c019511d9431a349e7756a55e5e8 [file] [log] [blame]
Changhoon Yoonb856b812015-08-10 03:47:19 +09001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Changhoon Yoonb856b812015-08-10 03:47:19 +09003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.onosproject.security.store;
18
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Sets;
Changhoon Yoonb856b812015-08-10 03:47:19 +090021import org.apache.karaf.features.BundleInfo;
22import org.apache.karaf.features.Feature;
23import org.apache.karaf.features.FeaturesService;
Changhoon Yoonb856b812015-08-10 03:47:19 +090024import org.onlab.util.KryoNamespace;
25import org.onosproject.app.ApplicationAdminService;
26import org.onosproject.core.Application;
27import org.onosproject.core.ApplicationId;
28import org.onosproject.security.Permission;
29import org.onosproject.store.AbstractStore;
30import org.onosproject.store.serializers.KryoNamespaces;
31import org.onosproject.store.service.ConsistentMap;
32import org.onosproject.store.service.EventuallyConsistentMap;
33import org.onosproject.store.service.LogicalClockService;
34import org.onosproject.store.service.MapEvent;
35import org.onosproject.store.service.MapEventListener;
36import org.onosproject.store.service.Serializer;
37import org.onosproject.store.service.StorageService;
Ray Milkeyd0f017f2018-09-21 12:52:34 -070038import org.onosproject.store.service.Versioned;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070039import org.osgi.service.component.annotations.Activate;
40import org.osgi.service.component.annotations.Component;
41import org.osgi.service.component.annotations.Deactivate;
42import org.osgi.service.component.annotations.Reference;
43import org.osgi.service.component.annotations.ReferenceCardinality;
Changhoon Yoonb856b812015-08-10 03:47:19 +090044import org.slf4j.Logger;
45
46import java.util.HashSet;
Sho SHIMIZU45906042016-01-13 23:05:54 -080047import java.util.Objects;
Changhoon Yoonb856b812015-08-10 03:47:19 +090048import java.util.Set;
49import java.util.concurrent.ConcurrentHashMap;
Madan Jampani67fd1e62016-07-01 10:26:23 -070050import java.util.concurrent.ExecutorService;
Changhoon Yoonb856b812015-08-10 03:47:19 +090051import java.util.stream.Collectors;
52
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070053import static java.util.concurrent.Executors.newSingleThreadExecutor;
54import static org.onlab.util.Tools.groupedThreads;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070055import static org.onosproject.security.store.SecurityModeState.INSTALLED;
56import static org.onosproject.security.store.SecurityModeState.POLICY_VIOLATED;
57import static org.onosproject.security.store.SecurityModeState.REVIEWED;
58import static org.onosproject.security.store.SecurityModeState.SECURED;
Changhoon Yoonb856b812015-08-10 03:47:19 +090059import static org.slf4j.LoggerFactory.getLogger;
60
61/**
62 * Manages application permissions granted/requested to applications.
63 * Uses both gossip-based and RAFT-based distributed data store.
64 */
Ray Milkeyd84f89b2018-08-17 14:54:17 -070065@Component(immediate = true, service = SecurityModeStore.class)
Changhoon Yoonb856b812015-08-10 03:47:19 +090066public class DistributedSecurityModeStore
67 extends AbstractStore<SecurityModeEvent, SecurityModeStoreDelegate>
68 implements SecurityModeStore {
69
70 private final Logger log = getLogger(getClass());
71
72 private ConsistentMap<ApplicationId, SecurityInfo> states;
73 private EventuallyConsistentMap<ApplicationId, Set<Permission>> violations;
74
75 private ConcurrentHashMap<String, Set<ApplicationId>> localBundleAppDirectory;
76 private ConcurrentHashMap<ApplicationId, Set<String>> localAppBundleDirectory;
77
Ray Milkeyd84f89b2018-08-17 14:54:17 -070078 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Changhoon Yoonb856b812015-08-10 03:47:19 +090079 protected StorageService storageService;
80
Ray Milkeyd84f89b2018-08-17 14:54:17 -070081 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Changhoon Yoonb856b812015-08-10 03:47:19 +090082 protected LogicalClockService clockService;
83
Ray Milkeyd84f89b2018-08-17 14:54:17 -070084 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Changhoon Yoonb856b812015-08-10 03:47:19 +090085 protected ApplicationAdminService applicationAdminService;
86
Ray Milkeyd84f89b2018-08-17 14:54:17 -070087 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Changhoon Yoonb856b812015-08-10 03:47:19 +090088 protected FeaturesService featuresService;
89
Madan Jampani67fd1e62016-07-01 10:26:23 -070090 private ExecutorService eventHandler;
91 private final SecurityStateListener statesListener = new SecurityStateListener();
92
Changhoon Yoonb856b812015-08-10 03:47:19 +090093 private static final Serializer STATE_SERIALIZER = Serializer.using(new KryoNamespace.Builder()
94 .register(KryoNamespaces.API)
95 .register(SecurityModeState.class)
96 .register(SecurityInfo.class)
97 .register(Permission.class)
98 .build());
99
100 private static final KryoNamespace.Builder VIOLATION_SERIALIZER = KryoNamespace.newBuilder()
101 .register(KryoNamespaces.API)
102 .register(Permission.class);
103
104 @Activate
105 public void activate() {
Yuta HIGUCHI1624df12016-07-21 16:54:33 -0700106 eventHandler = newSingleThreadExecutor(groupedThreads("onos/security/store", "event-handler", log));
Changhoon Yoonb856b812015-08-10 03:47:19 +0900107 states = storageService.<ApplicationId, SecurityInfo>consistentMapBuilder()
108 .withName("smonos-sdata")
109 .withSerializer(STATE_SERIALIZER)
110 .build();
111
Madan Jampani67fd1e62016-07-01 10:26:23 -0700112 states.addListener(statesListener, eventHandler);
Changhoon Yoonb856b812015-08-10 03:47:19 +0900113
114 violations = storageService.<ApplicationId, Set<Permission>>eventuallyConsistentMapBuilder()
115 .withName("smonos-rperms")
116 .withSerializer(VIOLATION_SERIALIZER)
117 .withTimestampProvider((k, v) -> clockService.getTimestamp())
118 .build();
119
120 localBundleAppDirectory = new ConcurrentHashMap<>();
121 localAppBundleDirectory = new ConcurrentHashMap<>();
122
123 log.info("Started");
124
125 }
126
127 @Deactivate
128 public void deactivate() {
Madan Jampani67fd1e62016-07-01 10:26:23 -0700129 states.removeListener(statesListener);
130 eventHandler.shutdown();
Changhoon Yoonb856b812015-08-10 03:47:19 +0900131 violations.destroy();
132 log.info("Stopped");
133 }
134
135
136 @Override
137 public Set<String> getBundleLocations(ApplicationId appId) {
138 Set<String> locations = localAppBundleDirectory.get(appId);
139 return locations != null ? locations : Sets.newHashSet();
140 }
141
142 @Override
143 public Set<ApplicationId> getApplicationIds(String location) {
144 Set<ApplicationId> appIds = localBundleAppDirectory.get(location);
145 return appIds != null ? appIds : Sets.newHashSet();
146 }
147
148 @Override
149 public Set<Permission> getRequestedPermissions(ApplicationId appId) {
150 Set<Permission> permissions = violations.get(appId);
151 return permissions != null ? permissions : ImmutableSet.of();
152 }
153
154 @Override
155 public Set<Permission> getGrantedPermissions(ApplicationId appId) {
156 return states.asJavaMap().getOrDefault(appId, new SecurityInfo(ImmutableSet.of(), null)).getPermissions();
157 }
158
159 @Override
160 public void requestPermission(ApplicationId appId, Permission permission) {
161
162 states.computeIf(appId, securityInfo -> (securityInfo == null || securityInfo.getState() != POLICY_VIOLATED),
163 (id, securityInfo) -> new SecurityInfo(securityInfo.getPermissions(), POLICY_VIOLATED));
164 violations.compute(appId, (k, v) -> v == null ? Sets.newHashSet(permission) : addAndGet(v, permission));
165 }
166
167 private Set<Permission> addAndGet(Set<Permission> oldSet, Permission newPerm) {
168 oldSet.add(newPerm);
169 return oldSet;
170 }
171
172 @Override
173 public boolean isSecured(ApplicationId appId) {
174 SecurityInfo info = states.get(appId).value();
175 return info == null ? false : info.getState().equals(SECURED);
176 }
177
178 @Override
179 public void reviewPolicy(ApplicationId appId) {
180 Application app = applicationAdminService.getApplication(appId);
181 if (app == null) {
182 log.warn("Unknown Application");
183 return;
184 }
185 states.computeIfPresent(appId, (applicationId, securityInfo) -> {
186 if (securityInfo.getState().equals(INSTALLED)) {
187 return new SecurityInfo(ImmutableSet.of(), REVIEWED);
188 }
189 return securityInfo;
190 });
191 }
192
193 @Override
194 public void acceptPolicy(ApplicationId appId, Set<Permission> permissionSet) {
195
196 Application app = applicationAdminService.getApplication(appId);
197 if (app == null) {
198 log.warn("Unknown Application");
199 return;
200 }
201
202 states.computeIf(appId,
Sho SHIMIZU45906042016-01-13 23:05:54 -0800203 Objects::nonNull,
Changhoon Yoonb856b812015-08-10 03:47:19 +0900204 (id, securityInfo) -> {
205 switch (securityInfo.getState()) {
206 case POLICY_VIOLATED:
207 System.out.println(
208 "This application has violated the security policy. Please uninstall.");
209 return securityInfo;
210 case SECURED:
211 System.out.println(
212 "The policy has been accepted already. To review policy, review [app.name]");
213 return securityInfo;
214 case INSTALLED:
215 System.out.println("Please review the security policy prior to accept them");
216 log.warn("Application has not been reviewed");
217 return securityInfo;
218 case REVIEWED:
219 return new SecurityInfo(permissionSet, SECURED);
220 default:
221 return securityInfo;
222 }
223 });
224 }
225
226 private final class SecurityStateListener
227 implements MapEventListener<ApplicationId, SecurityInfo> {
228
229 @Override
230 public void event(MapEvent<ApplicationId, SecurityInfo> event) {
231
232 if (delegate == null) {
233 return;
234 }
235 ApplicationId appId = event.key();
Ray Milkeyd0f017f2018-09-21 12:52:34 -0700236 Versioned<SecurityInfo> value = event.type() == MapEvent.Type.REMOVE ? event.oldValue() : event.newValue();
237 SecurityInfo info = value.value();
Changhoon Yoonb856b812015-08-10 03:47:19 +0900238
239 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
240 switch (info.getState()) {
241 case POLICY_VIOLATED:
242 notifyDelegate(new SecurityModeEvent(SecurityModeEvent.Type.POLICY_VIOLATED, appId));
243 break;
244 case SECURED:
245 notifyDelegate(new SecurityModeEvent(SecurityModeEvent.Type.POLICY_ACCEPTED, appId));
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800246 break;
Changhoon Yoonb856b812015-08-10 03:47:19 +0900247 default:
248 break;
249 }
250 } else if (event.type() == MapEvent.Type.REMOVE) {
251 removeAppFromDirectories(appId);
252 }
253 }
254 }
255
256 private void removeAppFromDirectories(ApplicationId appId) {
257 for (String location : localAppBundleDirectory.get(appId)) {
258 localBundleAppDirectory.get(location).remove(appId);
259 }
260 violations.remove(appId);
261 states.remove(appId);
262 localAppBundleDirectory.remove(appId);
263 }
264
265 @Override
266 public boolean registerApplication(ApplicationId appId) {
267 Application app = applicationAdminService.getApplication(appId);
268 if (app == null) {
269 log.warn("Unknown application.");
270 return false;
271 }
272 localAppBundleDirectory.put(appId, getBundleLocations(app));
273 for (String location : localAppBundleDirectory.get(appId)) {
274 if (!localBundleAppDirectory.containsKey(location)) {
275 localBundleAppDirectory.put(location, new HashSet<>());
276 }
277 if (!localBundleAppDirectory.get(location).contains(appId)) {
278 localBundleAppDirectory.get(location).add(appId);
279 }
280 }
281 states.put(appId, new SecurityInfo(Sets.newHashSet(), INSTALLED));
282 return true;
283 }
284
285 @Override
286 public void unregisterApplication(ApplicationId appId) {
287 if (localAppBundleDirectory.containsKey(appId)) {
288 for (String location : localAppBundleDirectory.get(appId)) {
289 if (localBundleAppDirectory.get(location).size() == 1) {
290 localBundleAppDirectory.remove(location);
291 } else {
292 localBundleAppDirectory.get(location).remove(appId);
293 }
294 }
295 localAppBundleDirectory.remove(appId);
296 }
297 }
298
299 @Override
300 public SecurityModeState getState(ApplicationId appId) {
301 return states.asJavaMap().getOrDefault(appId, new SecurityInfo(null, null)).getState();
302 }
303
304 private Set<String> getBundleLocations(Application app) {
305 Set<String> locations = new HashSet<>();
306 for (String name : app.features()) {
307 try {
308 Feature feature = featuresService.getFeature(name);
309 locations.addAll(
310 feature.getBundles().stream().map(BundleInfo::getLocation).collect(Collectors.toList()));
311 } catch (Exception e) {
312 return locations;
313 }
314 }
315 return locations;
316 }
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800317}