/* * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.groupbasedpolicy.renderer.vpp.policy.acl; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import org.opendaylight.groupbasedpolicy.api.sf.AllowActionDefinition; import org.opendaylight.groupbasedpolicy.renderer.util.AddressEndpointUtils; import org.opendaylight.groupbasedpolicy.renderer.vpp.policy.PolicyContext; import org.opendaylight.groupbasedpolicy.renderer.vpp.policy.RendererResolvedPolicy; import org.opendaylight.groupbasedpolicy.renderer.vpp.sf.SubjectFeatures; 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; 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; 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; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IpPrefix; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv6Prefix; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.common.rev140421.RuleName; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.forwarding.l2_l3.rev170511.SubnetAugmentRenderer; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.forwarding.l2_l3.rev170511.has.subnet.Subnet; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.HasDirection.Direction; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.policy.rev140421.subject.feature.instance.ParameterValue; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.EndpointPolicyParticipation; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.endpoints.AddressEndpointWithLocation; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.endpoints.RendererEndpointKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.endpoints.renderer.endpoint.PeerEndpointKey; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.renderer.rev151103.renderers.renderer.renderer.policy.configuration.renderer.forwarding.RendererForwardingByTenant; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.has.actions.Action; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.has.classifiers.Classifier; import org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.resolved.policy.rev150828.has.resolved.rules.ResolvedRule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Optional; /* * Transforms Renderer policy into access-list configuration for Honeycomb. * */ public class AccessListUtil { private static final Logger LOG = LoggerFactory.getLogger(AccessListUtil.class); static final String UNDERSCORE = "_"; private static final String PERMIT_EXTERNAL_INGRESS = "permit_external_ingress"; private static final String PERMIT_EXTERNAL_EGRESS = "permit_external_egress"; private static final String DENY_INGRESS_IPV4 = "deny_ingress_ipv4"; private static final String DENY_INGRESS_IPV6 = "deny_ingress_ipv6"; private static final String DENY_EGRESS_IPV4 = "deny_egress_ipv4"; private static final String DENY_EGRESS_IPV6 = "deny_egress_ipv6"; public enum ACE_DIRECTION { INGRESS, EGRESS } // hiding default public constructor private AccessListUtil() {} static void configureLocalRules(PolicyContext ctx, RendererEndpointKey rEpKey, ACE_DIRECTION policyDirection, AccessListWrapper aclWrapper) { ctx.getPolicyTable() .row(rEpKey) .keySet() .stream() .filter(peerEpKey -> peerHasLocation(ctx, peerEpKey)) .forEach(peerEpKey -> { List rules = new ArrayList<>(); ctx.getPolicyTable().get(rEpKey, peerEpKey).forEach(resolvedRules -> { Direction classifDir = calculateClassifDirection(resolvedRules.getRendererEndpointParticipation(), policyDirection); rules.addAll(resolveAclRulesFromPolicy(resolvedRules, classifDir, rEpKey, peerEpKey)); }); if (validateAndUpdateAddressesInRules(rules, rEpKey, peerEpKey, ctx, policyDirection, true)) { aclWrapper.writeRules(rules); } }); } /** * Resolves direction for classifiers that will be applied to INBOUND or OUTBOUND direction. *

* Rule is applied in INGRESS direction when participation is PROVIDER and classifier direction * is OUT *

* Rule is applied in INGRESS direction when participation is CONSUMER and classifier direction * is IN *

* INBOUND direction is applied otherwise. *

* Based on this *

* OUT classifier direction is resolved for INGRESS traffic when participation is PROVIDER *

* OUT classifier direction is resolved for EGRESS traffic when participation is CONSUMER *

* IN is resolved otherwise. *

* * @param participation provider or consumer * @param direction EGRESS or INGRESS * @return Direction that classifiers should match for given policy direction. */ static Direction calculateClassifDirection(EndpointPolicyParticipation participation, ACE_DIRECTION direction) { if (EndpointPolicyParticipation.PROVIDER.equals(participation) && ACE_DIRECTION.INGRESS.equals(direction)) { return Direction.Out; } if (EndpointPolicyParticipation.CONSUMER.equals(participation) && ACE_DIRECTION.EGRESS.equals(direction)) { return Direction.Out; } return Direction.In; } static boolean validateAndUpdateAddressesInRules(List rules, RendererEndpointKey rEpKey, PeerEndpointKey peerEpKey, PolicyContext ctx, ACE_DIRECTION policyDirection, boolean resolveForLocationPeers) { for (AddressMapper addrMapper : Arrays.asList(new SourceMapper(policyDirection), new DestinationMapper(policyDirection))) { if (peerHasLocation(ctx, peerEpKey) && resolveForLocationPeers) { if (!addrMapper.updateRules(rules, findAddrEp(ctx, rEpKey), findAddrEp(ctx, peerEpKey))) { return false; } } else if (!peerHasLocation(ctx, peerEpKey) && !resolveForLocationPeers) { addrMapper.updateExtRules(rules, findAddrEp(ctx, rEpKey), null); } } return true; } private static boolean peerHasLocation(PolicyContext ctx, PeerEndpointKey peerEpKey) { return ctx.getAddrEpByKey().get(AddressEndpointUtils.fromPeerEpKey(peerEpKey)) != null; } static AddressEndpointWithLocation findAddrEp(PolicyContext ctx, RendererEndpointKey rEpKey) { return ctx.getAddrEpByKey().get(AddressEndpointUtils.fromRendererEpKey(rEpKey)); } private static AddressEndpointWithLocation findAddrEp(PolicyContext ctx, PeerEndpointKey rEpKey) { return ctx.getAddrEpByKey().get(AddressEndpointUtils.fromPeerEpKey(rEpKey)); } /** * Transform a resolved rule to ACE with corresponding classification and action fields * * @param resolvedPolicy resolved rules, with the same participation - provider or consumer * @param direction rules matching corresponding direction will be collected * @return resolved ACE entries */ static @Nonnull String resolveAceName(@Nonnull RuleName ruleName, @Nonnull RendererEndpointKey key, @Nonnull PeerEndpointKey peer) { return ruleName.getValue() + "_" + key.getAddress() + "_" + peer.getAddress(); } private static List resolveAclRulesFromPolicy(RendererResolvedPolicy resolvedPolicy, Direction direction, RendererEndpointKey r, PeerEndpointKey p) { List aclRules = new ArrayList<>(); for (ResolvedRule resolvedRule : resolvedPolicy.getRuleGroup().getRules()) { Optional resolveAce = resolveAceClassifersAndAction(resolvedRule, direction, resolveAceName(resolvedRule.getName(), r, p)); if (resolveAce.isPresent()) { aclRules.add(resolveAce.get()); } } return aclRules; } public static Optional resolveAceClassifersAndAction(ResolvedRule resolvedRule, Direction direction, String ruleName) { Map params = resolveClassifParamsForDir(direction, resolvedRule.getClassifier()); if (params.isEmpty()) { return Optional.absent(); } org.opendaylight.groupbasedpolicy.renderer.vpp.sf.Classifier classif = resolveImplementedClassifForDir(direction, resolvedRule.getClassifier()); GbpAceBuilder aclRuleBuilder = new GbpAceBuilder(ruleName); // new GbpAceBuilder(resolvedRule.getName().getValue() + UNDERSCORE + namePasphrase); boolean updated = classif != null && classif.updateMatch(aclRuleBuilder, params); Optional optAction = resolveActions(resolvedRule.getAction()); if (!optAction.isPresent() || !updated) { LOG.error("Failed to process rule {}. Resolved parameters {}, resolved classifier. Actions resolved: {}" + "{}.", resolvedRule.getName().getValue(), params, classif, optAction.isPresent()); return Optional.absent(); } aclRuleBuilder.setAction(optAction.get()); return Optional.of(aclRuleBuilder); } private static org.opendaylight.groupbasedpolicy.renderer.vpp.sf.Classifier resolveImplementedClassifForDir( @Nonnull Direction direction, @Nonnull List classifiers) { org.opendaylight.groupbasedpolicy.renderer.vpp.sf.Classifier feasibleClassifier = null; for (Classifier cl : classifiers) { if (direction.equals(cl.getDirection()) || direction.equals(Direction.Bidirectional)) { org.opendaylight.groupbasedpolicy.renderer.vpp.sf.Classifier classif = SubjectFeatures.getClassifier(cl.getClassifierDefinitionId()); if (feasibleClassifier == null) { feasibleClassifier = classif; } if (classif.getParent() != null && classif.getParent().equals(feasibleClassifier)) { feasibleClassifier = classif; } } } return feasibleClassifier; } private static Map resolveClassifParamsForDir(Direction direction, List classifier) { Map params = new HashMap<>(); classifier.stream() .filter(classif -> direction.equals(classif.getDirection()) || direction.equals(Direction.Bidirectional)) .forEach(classif -> { classif.getParameterValue() .stream() .filter(v -> params.get(v.getName().getValue()) == null) // not unique .filter(v -> v.getIntValue() != null || v.getStringValue() != null || v.getRangeValue() != null) .forEach(v -> params.put(v.getName().getValue(), v)); }); return params; } private static Optional resolveActions(List actions) { for (Action action : actions) { if (AllowActionDefinition.ID.equals(action.getActionDefinitionId())) { return Optional .of(new ActionsBuilder().setPacketHandling(new PermitBuilder().setPermit(true).build()).build()); } } return Optional.absent(); } /* * so far any traffic heading to/from outside of managed domain is permitted for demonstration * purposes * TODO initial workaround for external networking */ static GbpAceBuilder allowExternalNetworksForEp(@Nonnull RendererEndpointKey rendEp, AccessListUtil.ACE_DIRECTION dir) { InetAddress byName; try { byName = InetAddress.getByName(substringBeforeSlash(rendEp.getAddress())); } catch (UnknownHostException e) { LOG.error("Failed to parse IP address {}", e); return null; } if (byName instanceof Inet4Address) { if (AccessListUtil.ACE_DIRECTION.INGRESS.equals(dir)) { return new GbpAceBuilder(PERMIT_EXTERNAL_INGRESS) .setIpAddresses(new Ipv4Prefix(rendEp.getAddress()), null).setPermit(); } else { return new GbpAceBuilder(PERMIT_EXTERNAL_EGRESS) .setIpAddresses(null, new Ipv4Prefix(rendEp.getAddress())).setPermit(); } } else if (byName instanceof Inet6Address) { if (AccessListUtil.ACE_DIRECTION.INGRESS.equals(dir)) { new GbpAceBuilder(PERMIT_EXTERNAL_INGRESS).setIpAddresses(new Ipv6Prefix(rendEp.getAddress()), null) .setPermit(); } else { new GbpAceBuilder(PERMIT_EXTERNAL_EGRESS).setIpAddresses(null, new Ipv6Prefix(rendEp.getAddress())) .setPermit(); } } return null; } /** * Helps stripping address part of a CIDR */ private static String substringBeforeSlash(String address) { return (address.contains("/") && address.split("/").length > 0) ? address.split("/")[0] : address; } static List denyDomainSubnets(@Nonnull PolicyContext ctx, @Nonnull ACE_DIRECTION policyDirection) { List aclRuleBuilders = new ArrayList<>(); for (RendererForwardingByTenant rf : ctx.getPolicy() .getConfiguration() .getRendererForwarding() .getRendererForwardingByTenant()) { rf.getRendererNetworkDomain() .stream() .filter(rnd -> org.opendaylight.yang.gen.v1.urn.opendaylight.groupbasedpolicy.forwarding.l2_l3.rev170511.Subnet.class .equals(rnd.getNetworkDomainType())) .forEach(rnd -> { SubnetAugmentRenderer subnetAug = rnd.getAugmentation(SubnetAugmentRenderer.class); // subnetAug should not be null subnetAug.getSubnet(); if (policyDirection.equals(ACE_DIRECTION.INGRESS) && subnetAug.getSubnet().isIsTenant()) { aclRuleBuilders.add(denyIngressTrafficForPrefix(subnetAug.getSubnet())); } else if (subnetAug.getSubnet().isIsTenant()) { aclRuleBuilders.add(denyEgressTrafficForPrefix(subnetAug.getSubnet())); } }); } return aclRuleBuilders; } private static GbpAceBuilder denyEgressTrafficForPrefix(Subnet subnet) { IpPrefix ipPrefix = subnet.getIpPrefix(); if (ipPrefix.getIpv4Prefix() != null) { return new GbpAceBuilder(DENY_EGRESS_IPV4 + UNDERSCORE + String.valueOf(ipPrefix.getValue())) .setIpAddresses(ipPrefix.getIpv4Prefix(), null).setDeny(); } else if (ipPrefix.getIpv6Prefix() != null) { return new GbpAceBuilder(DENY_EGRESS_IPV6 + UNDERSCORE + String.valueOf(ipPrefix.getValue())) .setIpAddresses(ipPrefix.getIpv6Prefix(), null).setDeny(); } throw new IllegalStateException("Unknown prefix type " + subnet.getIpPrefix()); } static void setSourceL3Address(GbpAceBuilder rule, String address) throws UnknownHostException { if (isIpv6Address(address)) { rule.setIpAddresses(new Ipv6Prefix(address), null); } else { rule.setIpAddresses(new Ipv4Prefix(address), null); } } static void setDestinationL3Address(GbpAceBuilder rule, String address) throws UnknownHostException { if (isIpv6Address(address)) { rule.setIpAddresses(null, new Ipv6Prefix(address)); } else { rule.setIpAddresses(null, new Ipv4Prefix(address)); } } public static boolean isIpv4Address(String address) throws UnknownHostException { InetAddress addr = InetAddress.getByName(substringBeforeSlash(address)); return addr instanceof Inet4Address; } public static boolean isIpv6Address(String address) throws UnknownHostException { InetAddress addr = InetAddress.getByName(substringBeforeSlash(address)); return addr instanceof Inet6Address; } static GbpAceBuilder denyIngressTrafficForPrefix(Subnet subnet) { IpPrefix ipPrefix = subnet.getIpPrefix(); if (ipPrefix.getIpv4Prefix() != null) { return new GbpAceBuilder(DENY_INGRESS_IPV4 + UNDERSCORE + String.valueOf(ipPrefix.getValue())) .setIpAddresses(null, ipPrefix.getIpv4Prefix()).setDeny(); } else if (ipPrefix.getIpv6Prefix() != null) { return new GbpAceBuilder(DENY_INGRESS_IPV6 + UNDERSCORE + String.valueOf(ipPrefix.getValue())) .setIpAddresses(null, ipPrefix.getIpv6Prefix()).setDeny(); } throw new IllegalStateException("Unknown prefix type " + subnet.getIpPrefix()); } }