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