blob: b31e2a0d844af6ee7f199e4971884cb4fc0a4edf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jclouds.azurecompute.arm.compute.extensions;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.notNull;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
import static org.jclouds.azurecompute.arm.config.AzureComputeProperties.TIMEOUT_RESOURCE_DELETED;
import static org.jclouds.azurecompute.arm.domain.IdReference.extractName;
import static org.jclouds.azurecompute.arm.domain.IdReference.extractResourceGroup;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.inject.Named;
import org.jclouds.azurecompute.arm.AzureComputeApi;
import org.jclouds.azurecompute.arm.compute.config.AzurePredicatesModule.SecurityGroupAvailablePredicateFactory;
import org.jclouds.azurecompute.arm.compute.domain.ResourceGroupAndName;
import org.jclouds.azurecompute.arm.domain.NetworkInterfaceCard;
import org.jclouds.azurecompute.arm.domain.NetworkProfile.NetworkInterface;
import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroup;
import org.jclouds.azurecompute.arm.domain.NetworkSecurityGroupProperties;
import org.jclouds.azurecompute.arm.domain.NetworkSecurityRule;
import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties;
import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Access;
import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Direction;
import org.jclouds.azurecompute.arm.domain.NetworkSecurityRuleProperties.Protocol;
import org.jclouds.azurecompute.arm.domain.ResourceGroup;
import org.jclouds.azurecompute.arm.domain.VirtualMachine;
import org.jclouds.azurecompute.arm.features.NetworkSecurityGroupApi;
import org.jclouds.azurecompute.arm.features.NetworkSecurityRuleApi;
import org.jclouds.compute.domain.SecurityGroup;
import org.jclouds.compute.domain.SecurityGroupBuilder;
import org.jclouds.compute.extensions.SecurityGroupExtension;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.domain.Location;
import org.jclouds.location.Region;
import org.jclouds.logging.Logger;
import org.jclouds.net.domain.IpPermission;
import org.jclouds.net.domain.IpProtocol;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.base.Supplier;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import com.google.common.collect.Ordering;
public class AzureComputeSecurityGroupExtension implements SecurityGroupExtension {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
protected Logger logger = Logger.NULL;
private final AzureComputeApi api;
private final Function<NetworkSecurityGroup, SecurityGroup> securityGroupConverter;
private final SecurityGroupAvailablePredicateFactory securityGroupAvailable;
private final Predicate<URI> resourceDeleted;
private final LoadingCache<String, ResourceGroup> defaultResourceGroup;
private final Supplier<Set<String>> regionIds;
@Inject
AzureComputeSecurityGroupExtension(AzureComputeApi api,
Function<NetworkSecurityGroup, SecurityGroup> groupConverter,
SecurityGroupAvailablePredicateFactory securityRuleAvailable,
@Named(TIMEOUT_RESOURCE_DELETED) Predicate<URI> resourceDeleted,
LoadingCache<String, ResourceGroup> defaultResourceGroup,
@Region Supplier<Set<String>> regionIds) {
this.api = api;
this.securityGroupConverter = groupConverter;
this.securityGroupAvailable = securityRuleAvailable;
this.resourceDeleted = resourceDeleted;
this.defaultResourceGroup = defaultResourceGroup;
this.regionIds = regionIds;
}
@Override
public Set<SecurityGroup> listSecurityGroupsInLocation(Location location) {
return securityGroupsInLocations(ImmutableSet.of(location.getId()));
}
@Override
public Set<SecurityGroup> listSecurityGroups() {
return securityGroupsInLocations(regionIds.get());
}
private Set<SecurityGroup> securityGroupsInLocations(final Set<String> locations) {
List<SecurityGroup> securityGroups = new ArrayList<SecurityGroup>();
for (ResourceGroup rg : api.getResourceGroupApi().list()) {
securityGroups.addAll(securityGroupsInResourceGroup(rg.name()));
}
return ImmutableSet.copyOf(filter(securityGroups, new Predicate<SecurityGroup>() {
@Override
public boolean apply(SecurityGroup input) {
return locations.contains(input.getLocation().getId());
}
}));
}
private Set<SecurityGroup> securityGroupsInResourceGroup(String resourceGroup) {
List<NetworkSecurityGroup> networkGroups = api.getNetworkSecurityGroupApi(resourceGroup).list();
return ImmutableSet.copyOf(transform(filter(networkGroups, notNull()), securityGroupConverter));
}
@Override
public Set<SecurityGroup> listSecurityGroupsForNode(String nodeId) {
logger.debug(">> getting security groups for node %s...", nodeId);
final ResourceGroupAndName resourceGroupAndName = ResourceGroupAndName.fromSlashEncoded(nodeId);
VirtualMachine vm = api.getVirtualMachineApi(resourceGroupAndName.resourceGroup()).get(
resourceGroupAndName.name());
if (vm == null) {
throw new IllegalArgumentException("Node " + nodeId + " was not found");
}
List<NetworkInterface> networkInterfaces = vm.properties().networkProfile().networkInterfaces();
List<NetworkSecurityGroup> networkGroups = new ArrayList<NetworkSecurityGroup>();
for (NetworkInterface networkInterfaceCardIdReference : networkInterfaces) {
String nicName = extractName(networkInterfaceCardIdReference.id());
String nicResourceGroup = extractResourceGroup(networkInterfaceCardIdReference.id());
NetworkInterfaceCard card = api.getNetworkInterfaceCardApi(nicResourceGroup).get(nicName);
if (card != null && card.properties().networkSecurityGroup() != null) {
String secGroupName = card.properties().networkSecurityGroup().name();
String sgResourceGroup = card.properties().networkSecurityGroup().resourceGroup();
NetworkSecurityGroup group = api.getNetworkSecurityGroupApi(sgResourceGroup).get(secGroupName);
networkGroups.add(group);
}
}
return ImmutableSet.copyOf(transform(filter(networkGroups, notNull()), securityGroupConverter));
}
@Override
public SecurityGroup getSecurityGroupById(String id) {
logger.debug(">> getting security group %s...", id);
final ResourceGroupAndName resourceGroupAndName = ResourceGroupAndName.fromSlashEncoded(id);
NetworkSecurityGroup securityGroup = api.getNetworkSecurityGroupApi(resourceGroupAndName.resourceGroup()).get(
resourceGroupAndName.name());
return securityGroup == null ? null : securityGroupConverter.apply(securityGroup);
}
@Override
public SecurityGroup createSecurityGroup(String name, Location location) {
ResourceGroup resourceGroup = defaultResourceGroup.getUnchecked(location.getId());
logger.debug(">> creating security group %s in %s...", name, location);
SecurityGroupBuilder builder = new SecurityGroupBuilder();
builder.name(name);
builder.location(location);
NetworkSecurityGroup sg = api.getNetworkSecurityGroupApi(resourceGroup.name()).createOrUpdate(name,
location.getId(), null, NetworkSecurityGroupProperties.builder().build());
checkState(securityGroupAvailable.create(resourceGroup.name()).apply(name),
"Security group was not created in the configured timeout");
return securityGroupConverter.apply(sg);
}
@Override
public boolean removeSecurityGroup(String id) {
logger.debug(">> deleting security group %s...", id);
final ResourceGroupAndName resourceGroupAndName = ResourceGroupAndName.fromSlashEncoded(id);
URI uri = api.getNetworkSecurityGroupApi(resourceGroupAndName.resourceGroup())
.delete(resourceGroupAndName.name());
// https://docs.microsoft.com/en-us/rest/api/network/virtualnetwork/delete-a-network-security-group
if (uri != null) {
// 202-Accepted if resource exists and the request is accepted.
return resourceDeleted.apply(uri);
} else {
// 204-No Content if resource does not exist.
return false;
}
}
@Override
public SecurityGroup addIpPermission(IpPermission ipPermission, SecurityGroup group) {
return addIpPermission(ipPermission.getIpProtocol(), ipPermission.getFromPort(), ipPermission.getToPort(),
ipPermission.getTenantIdGroupNamePairs(), ipPermission.getCidrBlocks(), ipPermission.getGroupIds(), group);
}
@Override
public SecurityGroup removeIpPermission(IpPermission ipPermission, SecurityGroup group) {
return removeIpPermission(ipPermission.getIpProtocol(), ipPermission.getFromPort(), ipPermission.getToPort(),
ipPermission.getTenantIdGroupNamePairs(), ipPermission.getCidrBlocks(), ipPermission.getGroupIds(), group);
}
@Override
public SecurityGroup addIpPermission(IpProtocol protocol, int startPort, int endPort,
Multimap<String, String> tenantIdGroupNamePairs, Iterable<String> ipRanges, Iterable<String> groupIds,
SecurityGroup group) {
String portRange = startPort + "-" + endPort;
String ruleName = "ingress-" + protocol.name().toLowerCase() + "-" + portRange;
logger.debug(">> adding ip permission [%s] to %s...", ruleName, group.getName());
// TODO: Support Azure network tags somehow?
final ResourceGroupAndName resourceGroupAndName = ResourceGroupAndName.fromSlashEncoded(group.getId());
NetworkSecurityGroupApi groupApi = api.getNetworkSecurityGroupApi(resourceGroupAndName.resourceGroup());
NetworkSecurityGroup networkSecurityGroup = groupApi.get(resourceGroupAndName.name());
if (networkSecurityGroup == null) {
throw new IllegalArgumentException("Security group " + group.getName() + " was not found");
}
NetworkSecurityRuleApi ruleApi = api.getNetworkSecurityRuleApi(resourceGroupAndName.resourceGroup(), networkSecurityGroup.name());
int nextPriority = getRuleStartingPriority(networkSecurityGroup);
for (String ipRange : ipRanges) {
NetworkSecurityRuleProperties properties = NetworkSecurityRuleProperties.builder()
.protocol(Protocol.fromValue(protocol.name()))
.sourceAddressPrefix(ipRange)
.sourcePortRange("*")
.destinationAddressPrefix("*")
.destinationPortRange(portRange)
.direction(Direction.Inbound)
.access(Access.Allow)
.priority(nextPriority++)
.build();
logger.debug(">> creating network security rule %s for %s...", ruleName, ipRange);
ruleApi.createOrUpdate(ruleName, properties);
checkState(
securityGroupAvailable.create(resourceGroupAndName.resourceGroup()).apply(networkSecurityGroup.name()),
"Security group was not updated in the configured timeout");
}
return getSecurityGroupById(group.getId());
}
@Override
public SecurityGroup removeIpPermission(final IpProtocol protocol, int startPort, int endPort,
Multimap<String, String> tenantIdGroupNamePairs, final Iterable<String> ipRanges, Iterable<String> groupIds,
SecurityGroup group) {
final String portRange = startPort + "-" + endPort;
String ruleName = "ingress-" + protocol.name().toLowerCase() + "-" + portRange;
logger.debug(">> deleting ip permissions matching [%s] from %s...", ruleName, group.getName());
final ResourceGroupAndName resourceGroupAndName = ResourceGroupAndName.fromSlashEncoded(group.getId());
NetworkSecurityGroupApi groupApi = api.getNetworkSecurityGroupApi(resourceGroupAndName.resourceGroup());
NetworkSecurityGroup networkSecurityGroup = groupApi.get(resourceGroupAndName.name());
if (networkSecurityGroup == null) {
throw new IllegalArgumentException("Security group " + group.getName() + " was not found");
}
NetworkSecurityRuleApi ruleApi = api.getNetworkSecurityRuleApi(resourceGroupAndName.resourceGroup(),
networkSecurityGroup.name());
Iterable<NetworkSecurityRule> rules = filter(ruleApi.list(), new Predicate<NetworkSecurityRule>() {
@Override
public boolean apply(NetworkSecurityRule input) {
NetworkSecurityRuleProperties props = input.properties();
return Objects.equal(portRange, props.destinationPortRange())
&& Objects.equal(Protocol.fromValue(protocol.name()), props.protocol())
&& Objects.equal(Direction.Inbound, props.direction()) //
&& Objects.equal(Access.Allow, props.access())
&& any(ipRanges, equalTo(props.sourceAddressPrefix().replace("*", "0.0.0.0/0")));
}
});
for (NetworkSecurityRule matchingRule : rules) {
logger.debug(">> deleting network security rule %s from %s...", matchingRule.name(), group.getName());
ruleApi.delete(matchingRule.name());
checkState(
securityGroupAvailable.create(resourceGroupAndName.resourceGroup()).apply(networkSecurityGroup.name()),
"Security group was not updated in the configured timeout");
}
return getSecurityGroupById(group.getId());
}
@Override
public boolean supportsTenantIdGroupNamePairs() {
return false;
}
@Override
public boolean supportsTenantIdGroupIdPairs() {
return false;
}
@Override
public boolean supportsGroupIds() {
return false;
}
@Override
public boolean supportsPortRangesForGroups() {
return false;
}
@Override
public boolean supportsExclusionCidrBlocks() {
return false;
}
private int getRuleStartingPriority(NetworkSecurityGroup securityGroup) {
List<NetworkSecurityRule> existingRules = securityGroup.properties().securityRules();
return existingRules.isEmpty() ? 100 : rulesByPriority().max(existingRules).properties().priority() + 1;
}
private static Ordering<NetworkSecurityRule> rulesByPriority() {
return new Ordering<NetworkSecurityRule>() {
@Override
public int compare(NetworkSecurityRule left, NetworkSecurityRule right) {
return left.properties().priority() - right.properties().priority();
}
};
}
}