blob: a0ce69b8c92d0a3307e307c63266001b37b08b27 [file] [log] [blame]
Changhoon Yoonb856b812015-08-10 03:47:19 +09001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
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;
21
22import org.apache.felix.scr.annotations.Activate;
23import org.apache.felix.scr.annotations.Component;
24import org.apache.felix.scr.annotations.Deactivate;
25import org.apache.felix.scr.annotations.Reference;
26import org.apache.felix.scr.annotations.ReferenceCardinality;
27import org.apache.felix.scr.annotations.Service;
28import org.apache.karaf.features.BundleInfo;
29import org.apache.karaf.features.Feature;
30import org.apache.karaf.features.FeaturesService;
Changhoon Yoonb856b812015-08-10 03:47:19 +090031import org.onlab.util.KryoNamespace;
32import org.onosproject.app.ApplicationAdminService;
33import org.onosproject.core.Application;
34import org.onosproject.core.ApplicationId;
35import org.onosproject.security.Permission;
36import org.onosproject.store.AbstractStore;
37import org.onosproject.store.serializers.KryoNamespaces;
38import org.onosproject.store.service.ConsistentMap;
39import org.onosproject.store.service.EventuallyConsistentMap;
40import org.onosproject.store.service.LogicalClockService;
41import org.onosproject.store.service.MapEvent;
42import org.onosproject.store.service.MapEventListener;
43import org.onosproject.store.service.Serializer;
44import org.onosproject.store.service.StorageService;
45import org.slf4j.Logger;
46
47import java.util.HashSet;
Sho SHIMIZU45906042016-01-13 23:05:54 -080048import java.util.Objects;
Changhoon Yoonb856b812015-08-10 03:47:19 +090049import java.util.Set;
50import java.util.concurrent.ConcurrentHashMap;
Madan Jampani67fd1e62016-07-01 10:26:23 -070051import java.util.concurrent.ExecutorService;
Changhoon Yoonb856b812015-08-10 03:47:19 +090052import java.util.stream.Collectors;
53
Yuta HIGUCHI1624df12016-07-21 16:54:33 -070054import static java.util.concurrent.Executors.newSingleThreadExecutor;
55import static org.onlab.util.Tools.groupedThreads;
Changhoon Yoonb856b812015-08-10 03:47:19 +090056import static org.onosproject.security.store.SecurityModeState.*;
57import static org.slf4j.LoggerFactory.getLogger;
58
59/**
60 * Manages application permissions granted/requested to applications.
61 * Uses both gossip-based and RAFT-based distributed data store.
62 */
63@Component(immediate = true)
64@Service
65public class DistributedSecurityModeStore
66 extends AbstractStore<SecurityModeEvent, SecurityModeStoreDelegate>
67 implements SecurityModeStore {
68
69 private final Logger log = getLogger(getClass());
70
71 private ConsistentMap<ApplicationId, SecurityInfo> states;
72 private EventuallyConsistentMap<ApplicationId, Set<Permission>> violations;
73
74 private ConcurrentHashMap<String, Set<ApplicationId>> localBundleAppDirectory;
75 private ConcurrentHashMap<ApplicationId, Set<String>> localAppBundleDirectory;
76
77 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
78 protected StorageService storageService;
79
80 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
81 protected LogicalClockService clockService;
82
83 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84 protected ApplicationAdminService applicationAdminService;
85
86 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87 protected FeaturesService featuresService;
88
Madan Jampani67fd1e62016-07-01 10:26:23 -070089 private ExecutorService eventHandler;
90 private final SecurityStateListener statesListener = new SecurityStateListener();
91
Changhoon Yoonb856b812015-08-10 03:47:19 +090092 private static final Serializer STATE_SERIALIZER = Serializer.using(new KryoNamespace.Builder()
93 .register(KryoNamespaces.API)
94 .register(SecurityModeState.class)
95 .register(SecurityInfo.class)
96 .register(Permission.class)
97 .build());
98
99 private static final KryoNamespace.Builder VIOLATION_SERIALIZER = KryoNamespace.newBuilder()
100 .register(KryoNamespaces.API)
101 .register(Permission.class);
102
103 @Activate
104 public void activate() {
Yuta HIGUCHI1624df12016-07-21 16:54:33 -0700105 eventHandler = newSingleThreadExecutor(groupedThreads("onos/security/store", "event-handler", log));
Changhoon Yoonb856b812015-08-10 03:47:19 +0900106 states = storageService.<ApplicationId, SecurityInfo>consistentMapBuilder()
107 .withName("smonos-sdata")
108 .withSerializer(STATE_SERIALIZER)
109 .build();
110
Madan Jampani67fd1e62016-07-01 10:26:23 -0700111 states.addListener(statesListener, eventHandler);
Changhoon Yoonb856b812015-08-10 03:47:19 +0900112
113 violations = storageService.<ApplicationId, Set<Permission>>eventuallyConsistentMapBuilder()
114 .withName("smonos-rperms")
115 .withSerializer(VIOLATION_SERIALIZER)
116 .withTimestampProvider((k, v) -> clockService.getTimestamp())
117 .build();
118
119 localBundleAppDirectory = new ConcurrentHashMap<>();
120 localAppBundleDirectory = new ConcurrentHashMap<>();
121
122 log.info("Started");
123
124 }
125
126 @Deactivate
127 public void deactivate() {
Madan Jampani67fd1e62016-07-01 10:26:23 -0700128 states.removeListener(statesListener);
129 eventHandler.shutdown();
Changhoon Yoonb856b812015-08-10 03:47:19 +0900130 violations.destroy();
131 log.info("Stopped");
132 }
133
134
135 @Override
136 public Set<String> getBundleLocations(ApplicationId appId) {
137 Set<String> locations = localAppBundleDirectory.get(appId);
138 return locations != null ? locations : Sets.newHashSet();
139 }
140
141 @Override
142 public Set<ApplicationId> getApplicationIds(String location) {
143 Set<ApplicationId> appIds = localBundleAppDirectory.get(location);
144 return appIds != null ? appIds : Sets.newHashSet();
145 }
146
147 @Override
148 public Set<Permission> getRequestedPermissions(ApplicationId appId) {
149 Set<Permission> permissions = violations.get(appId);
150 return permissions != null ? permissions : ImmutableSet.of();
151 }
152
153 @Override
154 public Set<Permission> getGrantedPermissions(ApplicationId appId) {
155 return states.asJavaMap().getOrDefault(appId, new SecurityInfo(ImmutableSet.of(), null)).getPermissions();
156 }
157
158 @Override
159 public void requestPermission(ApplicationId appId, Permission permission) {
160
161 states.computeIf(appId, securityInfo -> (securityInfo == null || securityInfo.getState() != POLICY_VIOLATED),
162 (id, securityInfo) -> new SecurityInfo(securityInfo.getPermissions(), POLICY_VIOLATED));
163 violations.compute(appId, (k, v) -> v == null ? Sets.newHashSet(permission) : addAndGet(v, permission));
164 }
165
166 private Set<Permission> addAndGet(Set<Permission> oldSet, Permission newPerm) {
167 oldSet.add(newPerm);
168 return oldSet;
169 }
170
171 @Override
172 public boolean isSecured(ApplicationId appId) {
173 SecurityInfo info = states.get(appId).value();
174 return info == null ? false : info.getState().equals(SECURED);
175 }
176
177 @Override
178 public void reviewPolicy(ApplicationId appId) {
179 Application app = applicationAdminService.getApplication(appId);
180 if (app == null) {
181 log.warn("Unknown Application");
182 return;
183 }
184 states.computeIfPresent(appId, (applicationId, securityInfo) -> {
185 if (securityInfo.getState().equals(INSTALLED)) {
186 return new SecurityInfo(ImmutableSet.of(), REVIEWED);
187 }
188 return securityInfo;
189 });
190 }
191
192 @Override
193 public void acceptPolicy(ApplicationId appId, Set<Permission> permissionSet) {
194
195 Application app = applicationAdminService.getApplication(appId);
196 if (app == null) {
197 log.warn("Unknown Application");
198 return;
199 }
200
201 states.computeIf(appId,
Sho SHIMIZU45906042016-01-13 23:05:54 -0800202 Objects::nonNull,
Changhoon Yoonb856b812015-08-10 03:47:19 +0900203 (id, securityInfo) -> {
204 switch (securityInfo.getState()) {
205 case POLICY_VIOLATED:
206 System.out.println(
207 "This application has violated the security policy. Please uninstall.");
208 return securityInfo;
209 case SECURED:
210 System.out.println(
211 "The policy has been accepted already. To review policy, review [app.name]");
212 return securityInfo;
213 case INSTALLED:
214 System.out.println("Please review the security policy prior to accept them");
215 log.warn("Application has not been reviewed");
216 return securityInfo;
217 case REVIEWED:
218 return new SecurityInfo(permissionSet, SECURED);
219 default:
220 return securityInfo;
221 }
222 });
223 }
224
225 private final class SecurityStateListener
226 implements MapEventListener<ApplicationId, SecurityInfo> {
227
228 @Override
229 public void event(MapEvent<ApplicationId, SecurityInfo> event) {
230
231 if (delegate == null) {
232 return;
233 }
234 ApplicationId appId = event.key();
235 SecurityInfo info = event.value().value();
236
237 if (event.type() == MapEvent.Type.INSERT || event.type() == MapEvent.Type.UPDATE) {
238 switch (info.getState()) {
239 case POLICY_VIOLATED:
240 notifyDelegate(new SecurityModeEvent(SecurityModeEvent.Type.POLICY_VIOLATED, appId));
241 break;
242 case SECURED:
243 notifyDelegate(new SecurityModeEvent(SecurityModeEvent.Type.POLICY_ACCEPTED, appId));
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800244 break;
Changhoon Yoonb856b812015-08-10 03:47:19 +0900245 default:
246 break;
247 }
248 } else if (event.type() == MapEvent.Type.REMOVE) {
249 removeAppFromDirectories(appId);
250 }
251 }
252 }
253
254 private void removeAppFromDirectories(ApplicationId appId) {
255 for (String location : localAppBundleDirectory.get(appId)) {
256 localBundleAppDirectory.get(location).remove(appId);
257 }
258 violations.remove(appId);
259 states.remove(appId);
260 localAppBundleDirectory.remove(appId);
261 }
262
263 @Override
264 public boolean registerApplication(ApplicationId appId) {
265 Application app = applicationAdminService.getApplication(appId);
266 if (app == null) {
267 log.warn("Unknown application.");
268 return false;
269 }
270 localAppBundleDirectory.put(appId, getBundleLocations(app));
271 for (String location : localAppBundleDirectory.get(appId)) {
272 if (!localBundleAppDirectory.containsKey(location)) {
273 localBundleAppDirectory.put(location, new HashSet<>());
274 }
275 if (!localBundleAppDirectory.get(location).contains(appId)) {
276 localBundleAppDirectory.get(location).add(appId);
277 }
278 }
279 states.put(appId, new SecurityInfo(Sets.newHashSet(), INSTALLED));
280 return true;
281 }
282
283 @Override
284 public void unregisterApplication(ApplicationId appId) {
285 if (localAppBundleDirectory.containsKey(appId)) {
286 for (String location : localAppBundleDirectory.get(appId)) {
287 if (localBundleAppDirectory.get(location).size() == 1) {
288 localBundleAppDirectory.remove(location);
289 } else {
290 localBundleAppDirectory.get(location).remove(appId);
291 }
292 }
293 localAppBundleDirectory.remove(appId);
294 }
295 }
296
297 @Override
298 public SecurityModeState getState(ApplicationId appId) {
299 return states.asJavaMap().getOrDefault(appId, new SecurityInfo(null, null)).getState();
300 }
301
302 private Set<String> getBundleLocations(Application app) {
303 Set<String> locations = new HashSet<>();
304 for (String name : app.features()) {
305 try {
306 Feature feature = featuresService.getFeature(name);
307 locations.addAll(
308 feature.getBundles().stream().map(BundleInfo::getLocation).collect(Collectors.toList()));
309 } catch (Exception e) {
310 return locations;
311 }
312 }
313 return locations;
314 }
Ray Milkey4fd3ceb2015-12-10 14:43:08 -0800315}