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