6e15411df4d84ce5a26d0f3438cd50abf50ee943
[groupbasedpolicy.git] / renderers / vpp / src / main / java / org / opendaylight / groupbasedpolicy / renderer / vpp / policy / acl / AccessListUtil.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.groupbasedpolicy.renderer.vpp.policy.acl;
10
11 import java.net.Inet4Address;
12 import java.net.Inet6Address;
13 import java.net.InetAddress;
14 import java.net.UnknownHostException;
15 import java.util.ArrayList;
16 import java.util.Arrays;
17 import java.util.HashMap;
18 import java.util.List;
19 import java.util.Map;
20
21 import javax.annotation.Nonnull;
22
23 import org.opendaylight.groupbasedpolicy.api.sf.AllowActionDefinition;
24 import org.opendaylight.groupbasedpolicy.renderer.util.AddressEndpointUtils;
25 import org.opendaylight.groupbasedpolicy.renderer.vpp.policy.PolicyContext;
26 import org.opendaylight.groupbasedpolicy.renderer.vpp.policy.RendererResolvedPolicy;
27 import org.opendaylight.groupbasedpolicy.renderer.vpp.sf.SubjectFeatures;
28 import org.opendaylight.groupbasedpolicy.util.EndpointUtils;
29 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.Actions;
30 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.ActionsBuilder;
31 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.packet.handling.PermitBuilder;
32 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpPrefix;
33 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix;
34 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Prefix;
35 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.base_endpoint.rev160427.parent.child.endpoints.parent.endpoint.choice.parent.endpoint._case.ParentEndpoint;
36 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.forwarding.l2_l3.rev160427.L2BridgeDomain;
37 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.forwarding.l2_l3.rev160427.SubnetAugmentRenderer;
38 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.forwarding.l2_l3.rev160427.has.subnet.Subnet;
39 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.HasDirection.Direction;
40 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.instance.ParameterValue;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.EndpointPolicyParticipation;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.endpoints.AddressEndpointWithLocation;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.endpoints.RendererEndpointKey;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.endpoints.renderer.endpoint.PeerEndpointKey;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.forwarding.RendererForwardingByTenant;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.has.actions.Action;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.has.classifiers.Classifier;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.has.resolved.rules.ResolvedRule;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 import com.google.common.base.Optional;
53
54  /*
55   * Transforms Renderer policy into access-list configuration for Honeycomb.
56   *
57   * */
58
59 public class AccessListUtil {
60
61     private static final Logger LOG = LoggerFactory.getLogger(AccessListUtil.class);
62     private static final String UNDERSCORE = "_";
63     private static final String PERMIT_EXTERNAL_INGRESS = "permit_external_ingress";
64     private static final String PERMIT_EXTERNAL_EGRESS = "permit_external_egress";
65     private static final String DENY_INGRESS_IPV4 = "deny_ingress_ipv4";
66     private static final String DENY_INGRESS_IPV6 = "deny_ingress_ipv6";
67     private static final String DENY_EGRESS_IPV4 = "deny_egress_ipv4";
68     private static final String DENY_EGRESS_IPV6 = "deny_egress_ipv6";
69
70     public enum ACE_DIRECTION {
71         INGRESS, EGRESS
72     }
73
74     // hiding default public constructor
75     private AccessListUtil() {}
76
77     public static List<AccessListWrapper> resolveAclsOnInterface(RendererEndpointKey rEpKey, PolicyContext ctx) {
78         List<AccessListWrapper> aclWrappers = new ArrayList<>();
79         for (ACE_DIRECTION dir : new ACE_DIRECTION[] {ACE_DIRECTION.INGRESS, ACE_DIRECTION.EGRESS}) {
80             aclWrappers.add(buildAccessListWrappers(dir, ctx, rEpKey));
81         }
82         return aclWrappers;
83     }
84
85     /**
86      * @param policyDirection direction for which policy should be resolved. EP -> VPP = OUTBOUND, EP <- VPP = INBOUND 
87      * @param ctx with cached data
88      * @param rEpKey key of EP for which to create ACLs.
89      * @return synchronization futures, so that INGRESS and EGRESS ACLS can be resolved in parallel.
90      */
91     private static AccessListWrapper buildAccessListWrappers(ACE_DIRECTION policyDirection, PolicyContext ctx,
92             RendererEndpointKey rEpKey) {
93         LOG.trace("Resolving policy for VPP renderer endpoint {} in a separate thread in {} direction.", rEpKey,
94                 policyDirection);
95         AccessListWrapper aclWrapper = AccessListUtil.ACE_DIRECTION.INGRESS
96             .equals(policyDirection) ? new IngressAccessListWrapper() : new EgressAccessListWrapper();
97         ctx.getPolicyTable()
98             .row(rEpKey)
99             .keySet()
100             .stream()
101             .filter(peerEpKey -> peerHasLocation(ctx, peerEpKey))
102             .forEach(peerEpKey -> {
103             ctx.getPolicyTable().get(rEpKey, peerEpKey).forEach(resolvedRules -> {
104                 List<GbpAceBuilder> rules = new ArrayList<>();
105                 LOG.debug("Resolving policy for {} and peer endpoint {}", rEpKey, peerEpKey);
106                 Direction classifDir =
107                         calculateClassifDirection(resolvedRules.getRendererEndpointParticipation(), policyDirection);
108                 rules.addAll(resolveAclRulesFromPolicy(resolvedRules, classifDir,
109                         rEpKey.getAddress() + UNDERSCORE + peerEpKey.getAddress()));
110                 updateAddressesInRules(rules, rEpKey, peerEpKey, ctx, policyDirection, true);
111                 aclWrapper.writeRules(rules);
112             });
113         });
114         // resolve peers with no location
115         aclWrapper.writeRules(denyDomainSubnets(ctx, policyDirection));
116         // TODO currently any traffic heading to/from outside of managed domain is
117         // permitted for demonstration purposes
118         if (rEpKey.getContextType().isAssignableFrom(L2BridgeDomain.class) && findAddrEp(ctx, rEpKey) != null) {
119             Optional<GbpAceBuilder> allowExtAccess =
120                     allowExternalNetworksForEp(findAddrEp(ctx, rEpKey), policyDirection);
121             if (allowExtAccess.isPresent()) {
122                 aclWrapper.writeRule(allowExtAccess.get());
123             }
124         }
125         return aclWrapper;
126     }
127
128     /**
129      * Resolves direction for classifiers that will be applied to INBOUND or OUTBOUND direction.
130      * </p>
131      * Rule is applied in INGRESS direction when participation is PROVIDER and classifier direction is OUT
132      * </p>
133      * Rule is applied in INGRESS direction when participation is CONSUMER and classifier direction is IN
134      * </p>
135      * INBOUND direction is applied otherwise.
136      * </p>
137      * Based on this
138      * </p>
139      * OUT classifier direction is resolved for INGRESS traffic when participation is PROVIDER
140      * </p>
141      * OUT classifier direction is resolved for EGRESS traffic when participation is CONSUMER
142      * </p>
143      * IN is resolved otherwise.
144      * </p>
145      * @param participation provider or consumer
146      * @param direction EGRESS or INGRESS
147      * @return Direction that classifiers should match for given policy direction.
148      */
149     private static Direction calculateClassifDirection(EndpointPolicyParticipation participation, ACE_DIRECTION direction) {
150         if (EndpointPolicyParticipation.PROVIDER.equals(participation) && ACE_DIRECTION.INGRESS.equals(direction)) {
151             return Direction.Out;
152         }
153         if (EndpointPolicyParticipation.CONSUMER.equals(participation) && ACE_DIRECTION.EGRESS.equals(direction)) {
154             return Direction.Out;
155         }
156         return Direction.In;
157     }
158
159     private static void updateAddressesInRules(List<GbpAceBuilder> rules, RendererEndpointKey rEpKey,
160             PeerEndpointKey peerEpKey, PolicyContext ctx, ACE_DIRECTION policyDirection,
161             boolean resolveForLocationPeers) {
162         for (AddressMapper addrMapper : Arrays.asList(new SourceMapper(policyDirection),
163                 new DestinationMapper(policyDirection))) {
164             if (peerHasLocation(ctx, peerEpKey) && resolveForLocationPeers) {
165                 addrMapper.updateRules(rules, findAddrEp(ctx, rEpKey), findAddrEp(ctx, peerEpKey));
166             } else if (!peerHasLocation(ctx, peerEpKey) && !resolveForLocationPeers) {
167                 addrMapper.updateExtRules(rules, findAddrEp(ctx, rEpKey), null);
168             }
169         }
170     }
171
172     private static boolean peerHasLocation(PolicyContext ctx, PeerEndpointKey peerEpKey) {
173         return ctx.getAddrEpByKey().get(
174                 AddressEndpointUtils.fromPeerEpKey(peerEpKey)) != null;
175     }
176
177     private static AddressEndpointWithLocation findAddrEp(PolicyContext ctx, RendererEndpointKey rEpKey) {
178         return ctx.getAddrEpByKey().get(
179                 AddressEndpointUtils.fromRendererEpKey(rEpKey));
180     }
181
182     private static AddressEndpointWithLocation findAddrEp(PolicyContext ctx, PeerEndpointKey rEpKey) {
183         return ctx.getAddrEpByKey().get(
184                 AddressEndpointUtils.fromPeerEpKey(rEpKey));
185     }
186
187     /** Transform a resolved rule to ACE with corresponding classification and action fields
188      *
189      * @param resolvedPolicy resolved rules, with the same participation - provider or consumer
190      * @param direction rules matching corresponding direction will be collected
191      * @return resolved ACE entries
192      */
193     private static List<GbpAceBuilder> resolveAclRulesFromPolicy(RendererResolvedPolicy resolvedPolicy,
194             Direction direction, String namePasphrase) {
195         List<GbpAceBuilder> aclRules = new ArrayList<>();
196         for (ResolvedRule resolvedRule : resolvedPolicy.getRuleGroup().getRules()) {
197             Map<String, ParameterValue> params = resolveClassifParamsForDir(direction, resolvedRule.getClassifier());
198             if (params.isEmpty()) {
199                 continue;
200             }
201             LOG.debug("Processing classifification params {} in resolved rule {}.", params,
202                     resolvedRule.getName() + UNDERSCORE + namePasphrase);
203             org.opendaylight.groupbasedpolicy.renderer.vpp.sf.Classifier classif =
204                     resolveImplementedClassifForDir(direction, resolvedRule.getClassifier());
205             GbpAceBuilder aclRuleBuilder =
206                     new GbpAceBuilder(resolvedRule.getName().getValue() + UNDERSCORE + namePasphrase);
207             boolean updated = classif != null && classif.updateMatch(aclRuleBuilder, params);
208             Optional<Actions> optAction = resolveActions(resolvedRule.getAction());
209             if (!optAction.isPresent() || !updated) {
210                 LOG.error("Failed to process rule {}. Resolved parameters {}, resolved classifier. Actions resolved: {}"
211                         + "{}.", resolvedRule.getName().getValue(), params, classif, optAction.isPresent());
212                 continue;
213             }
214             aclRuleBuilder.setAction(optAction.get());
215             aclRules.add(aclRuleBuilder);
216         }
217         return aclRules;
218     }
219
220     private static org.opendaylight.groupbasedpolicy.renderer.vpp.sf.Classifier resolveImplementedClassifForDir(
221             @Nonnull Direction direction, @Nonnull List<Classifier> classifiers) {
222         org.opendaylight.groupbasedpolicy.renderer.vpp.sf.Classifier feasibleClassifier = null;
223         for (Classifier cl : classifiers) {
224             if (direction.equals(cl.getDirection()) || direction.equals(Direction.Bidirectional)) {
225                 org.opendaylight.groupbasedpolicy.renderer.vpp.sf.Classifier classif =
226                         SubjectFeatures.getClassifier(cl.getClassifierDefinitionId());
227                 if (feasibleClassifier == null) {
228                     feasibleClassifier = classif;
229                 }
230                 if (classif.getParent() != null && classif.getParent().equals(feasibleClassifier)) {
231                     feasibleClassifier = classif;
232                 }
233             }
234         }
235         return feasibleClassifier;
236     }
237
238     private static Map<String, ParameterValue> resolveClassifParamsForDir(Direction direction,
239             List<Classifier> classifier) {
240         Map<String, ParameterValue> params = new HashMap<>();
241         classifier.stream()
242             .filter(classif -> direction.equals(classif.getDirection()) || direction.equals(Direction.Bidirectional))
243             .forEach(classif -> {
244                 LOG.trace("Resolving parameters for classiifier: {} with direction", classif, direction);
245                 classif.getParameterValue()
246                     .stream()
247                     .filter(v -> params.get(v.getName().getValue()) == null) // not unique
248                     .filter(v -> v.getIntValue() != null || v.getStringValue() != null || v.getRangeValue() != null)
249                     .forEach(v -> params.put(v.getName().getValue(), v));
250                 LOG.trace("Resolved parameters {} for classiifier: {} with direction {}", params, classif, direction);
251             });
252         return params;
253     }
254
255     private static Optional<Actions> resolveActions(List<Action> actions) {
256         for (Action action : actions) {
257             if (AllowActionDefinition.ID
258                 .equals(action.getActionDefinitionId())) {
259                 LOG.trace("Applying supported action: {}", action);
260                 return Optional
261                     .of(new ActionsBuilder().setPacketHandling(new PermitBuilder().setPermit(true).build()).build());
262             }
263         }
264         LOG.warn("No supported action found among actions: {}", actions);
265         return Optional.absent();
266     }
267
268     /*
269      * so far any traffic heading to/from outside of managed domain is permitted for demonstration
270      * purposes
271      * TODO initial workaround for external networking
272      */
273    private static Optional<GbpAceBuilder> allowExternalNetworksForEp(@Nonnull AddressEndpointWithLocation addrEp,
274             AccessListUtil.ACE_DIRECTION dir) {
275         List<ParentEndpoint> parentEndpoints = EndpointUtils.getParentEndpoints(addrEp.getParentEndpointChoice());
276         if (parentEndpoints.isEmpty()) {
277             return Optional.absent();
278         }
279         for (ParentEndpoint parentEp : parentEndpoints) {
280             InetAddress byName;
281             try {
282                 byName = InetAddress.getByName(substringBeforeSlash(parentEp.getAddress()));
283             } catch (UnknownHostException e) {
284                 LOG.error("Failed to parse IP address {}", e);
285                 return Optional.absent();
286             }
287             if (byName instanceof Inet4Address) {
288                 if (AccessListUtil.ACE_DIRECTION.INGRESS.equals(dir)) {
289                     return Optional.of(new GbpAceBuilder(PERMIT_EXTERNAL_INGRESS).setIpAddresses(
290                             new Ipv4Prefix(parentEp.getAddress()), null).setPermit());
291                 } else {
292                     return Optional.of(new GbpAceBuilder(PERMIT_EXTERNAL_EGRESS).setIpAddresses(null,
293                             new Ipv4Prefix(parentEp.getAddress())).setPermit());
294                 }
295             } else if (byName instanceof Inet6Address) {
296                 if (AccessListUtil.ACE_DIRECTION.INGRESS.equals(dir)) {
297                     return Optional.of(new GbpAceBuilder(PERMIT_EXTERNAL_INGRESS).setIpAddresses(
298                             new Ipv6Prefix(parentEp.getAddress()), null).setPermit());
299                 } else {
300                     return Optional.of(new GbpAceBuilder(PERMIT_EXTERNAL_EGRESS).setIpAddresses(null,
301                             new Ipv6Prefix(parentEp.getAddress())).setPermit());
302                 }
303             }
304         }
305         return Optional.absent();
306     }
307
308     /**
309      * Helps stripping address part of a CIDR
310      */
311     private static String substringBeforeSlash(String address) {
312         return (address.contains("/") && address.split("/").length > 0) ? address.split("/")[0] : address;
313     }
314
315     private static List<GbpAceBuilder> denyDomainSubnets(@Nonnull PolicyContext ctx, @Nonnull ACE_DIRECTION policyDirection) {
316         List<GbpAceBuilder> aclRuleBuilders = new ArrayList<>();
317         for (RendererForwardingByTenant rf : ctx.getPolicy()
318             .getConfiguration()
319             .getRendererForwarding()
320             .getRendererForwardingByTenant()) {
321             rf.getRendererNetworkDomain()
322                 .stream()
323                 .filter(rnd -> org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.forwarding.l2_l3.rev160427.Subnet.class
324                     .equals(rnd.getNetworkDomainType()))
325                 .forEach(rnd -> {
326                     SubnetAugmentRenderer subnetAug = rnd.getAugmentation(SubnetAugmentRenderer.class);
327                     // subnetAug should not be null
328                     subnetAug.getSubnet();
329                     if (policyDirection.equals(ACE_DIRECTION.INGRESS) && subnetAug.getSubnet().isIsTenant()) {
330                         aclRuleBuilders.add(denyIngressTrafficForPrefix(subnetAug.getSubnet()));
331                     }
332                     else if(subnetAug.getSubnet().isIsTenant()) {
333                         aclRuleBuilders.add(denyEgressTrafficForPrefix(subnetAug.getSubnet()));
334                     }
335                 });
336         }
337         return aclRuleBuilders;
338     }
339
340     private static GbpAceBuilder denyEgressTrafficForPrefix(Subnet subnet) {
341         IpPrefix ipPrefix = subnet.getIpPrefix();
342         if (ipPrefix.getIpv4Prefix() != null) {
343             return new GbpAceBuilder(DENY_EGRESS_IPV4 + UNDERSCORE + String.valueOf(ipPrefix.getValue()))
344                 .setIpAddresses(ipPrefix.getIpv4Prefix(), null).setDeny();
345         } else if (ipPrefix.getIpv6Prefix() != null) {
346             return new GbpAceBuilder(DENY_EGRESS_IPV6 + UNDERSCORE + String.valueOf(ipPrefix.getValue()))
347                 .setIpAddresses(ipPrefix.getIpv6Prefix(), null).setDeny();
348         }
349         throw new IllegalStateException("Unknown prefix type " + subnet.getIpPrefix());
350     }
351
352     static void setSourceL3Address(GbpAceBuilder rule, String address) throws UnknownHostException {
353         InetAddress addr = InetAddress.getByName(substringBeforeSlash(address));
354         if (addr instanceof Inet6Address) {
355             rule.setIpAddresses(new Ipv6Prefix(address), null);
356         } else {
357             rule.setIpAddresses(new Ipv4Prefix(address), null);
358         }
359     }
360
361     static void setDestinationL3Address(GbpAceBuilder rule, String address) throws UnknownHostException {
362         InetAddress addr = InetAddress.getByName(substringBeforeSlash(address));
363         if (addr instanceof Inet6Address) {
364             rule.setIpAddresses(null, new Ipv6Prefix(address));
365         } else {
366             rule.setIpAddresses(null, new Ipv4Prefix(address));
367         }
368     }
369
370     static GbpAceBuilder denyIngressTrafficForPrefix(Subnet subnet) {
371         IpPrefix ipPrefix = subnet.getIpPrefix();
372         if (ipPrefix.getIpv4Prefix() != null) {
373             return new GbpAceBuilder(DENY_INGRESS_IPV4 + UNDERSCORE + String.valueOf(ipPrefix.getValue()))
374                 .setIpAddresses(null, ipPrefix.getIpv4Prefix()).setDeny();
375         } else if (ipPrefix.getIpv6Prefix() != null) {
376             return new GbpAceBuilder(DENY_INGRESS_IPV6 + UNDERSCORE + String.valueOf(ipPrefix.getValue()))
377                 .setIpAddresses(null, ipPrefix.getIpv6Prefix()).setDeny();
378         }
379         throw new IllegalStateException("Unknown prefix type " + subnet.getIpPrefix());
380     }
381 }