All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vmware.photon.controller.model.adapters.azure.enumeration.AzureSecurityGroupEnumerationAdapterService Maven / Gradle / Ivy

/*
 * Copyright (c) 2015-2016 VMware, Inc. All Rights Reserved.
 *
 * Licensed 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 com.vmware.photon.controller.model.adapters.azure.enumeration;

import static com.vmware.photon.controller.model.adapters.azure.AzureUriPaths.AZURE_SECURITY_GROUP_ADAPTER;
import static com.vmware.photon.controller.model.adapters.azure.constants.AzureConstants.AUTH_HEADER_BEARER_PREFIX;
import static com.vmware.photon.controller.model.adapters.azure.constants.AzureConstants.AZURE_CORE_MANAGEMENT_URI;
import static com.vmware.photon.controller.model.adapters.azure.constants.AzureConstants.LIST_NETWORK_SECURITY_GROUP_URI;
import static com.vmware.photon.controller.model.adapters.azure.constants.AzureConstants.NETWORK_REST_API_VERSION;
import static com.vmware.photon.controller.model.adapters.azure.constants.AzureConstants.QUERY_PARAM_API_VERSION;
import static com.vmware.photon.controller.model.adapters.azure.utils.AzureUtils.getAzureConfig;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import com.microsoft.azure.credentials.ApplicationTokenCredentials;

import com.microsoft.azure.management.network.SecurityRuleDirection;
import org.apache.commons.net.util.SubnetUtils;

import com.vmware.photon.controller.model.ComputeProperties;
import com.vmware.photon.controller.model.adapterapi.EnumerationAction;
import com.vmware.photon.controller.model.adapters.azure.AzureUriPaths;
import com.vmware.photon.controller.model.adapters.azure.constants.AzureConstants;
import com.vmware.photon.controller.model.adapters.azure.constants.AzureConstants.ResourceGroupStateType;
import com.vmware.photon.controller.model.adapters.azure.model.network.NetworkSecurityGroup;
import com.vmware.photon.controller.model.adapters.azure.model.network.NetworkSecurityGroupListResult;
import com.vmware.photon.controller.model.adapters.azure.utils.AzureUtils;
import com.vmware.photon.controller.model.adapters.util.AdapterUriUtil;
import com.vmware.photon.controller.model.adapters.util.ComputeEnumerateAdapterRequest;
import com.vmware.photon.controller.model.adapters.util.enums.BaseComputeEnumerationAdapterContext;
import com.vmware.photon.controller.model.adapters.util.enums.EnumerationStages;
import com.vmware.photon.controller.model.query.QueryStrategy;
import com.vmware.photon.controller.model.query.QueryUtils.QueryByPages;
import com.vmware.photon.controller.model.resources.ResourceGroupService.ResourceGroupState;
import com.vmware.photon.controller.model.resources.ResourceState;
import com.vmware.photon.controller.model.resources.SecurityGroupService;
import com.vmware.photon.controller.model.resources.SecurityGroupService.SecurityGroupState;
import com.vmware.photon.controller.model.resources.SecurityGroupService.SecurityGroupState.Rule;
import com.vmware.photon.controller.model.resources.SecurityGroupService.SecurityGroupState.Rule.Access;
import com.vmware.xenon.common.DeferredResult;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.StatelessService;
import com.vmware.xenon.common.UriUtils;
import com.vmware.xenon.common.Utils;
import com.vmware.xenon.services.common.QueryTask.Query;
import com.vmware.xenon.services.common.QueryTask.Query.Builder;

/**
 * Enumeration adapter for data collection of Azure network security groups.
 */
public class AzureSecurityGroupEnumerationAdapterService extends StatelessService {

    public static final String SELF_LINK = AzureUriPaths.AZURE_FIREWALL_ENUMERATION_ADAPTER;

    public static class SecurityGroupEnumContext extends
            BaseComputeEnumerationAdapterContext {

        // Azure credentials.
        ApplicationTokenCredentials credentials;

        // Stores the map of resource groups state ids to document self links.
        // key -> resource group id; value - link to the local ResourceGroupState object.
        Map securityGroupRGStates = new HashMap<>();

        SecurityGroupEnumContext(StatelessService service, ComputeEnumerateAdapterRequest request,
                Operation op) {

            super(service, request, op, SecurityGroupState.class,
                    SecurityGroupService.FACTORY_LINK);
        }

        @Override
        public DeferredResult getExternalResources(String nextPageLink) {

            this.securityGroupRGStates.clear();

            URI uri;
            if (nextPageLink == null) {
                // First request to fetch Network Security Groups from Azure.
                String uriStr = AdapterUriUtil
                        .expandUriPathTemplate(LIST_NETWORK_SECURITY_GROUP_URI,
                                this.request.parentAuth.userLink);
                uri = UriUtils.extendUriWithQuery(
                        UriUtils.buildUri(uriStr),
                        QUERY_PARAM_API_VERSION, NETWORK_REST_API_VERSION);
            } else {
                // Request to fetch next page of Network Security Groups from Azure.
                uri = UriUtils.buildUri(nextPageLink);
            }

            final Operation operation = Operation.createGet(uri);
            operation.addRequestHeader(Operation.ACCEPT_HEADER,
                    Operation.MEDIA_TYPE_APPLICATION_JSON);
            operation.addRequestHeader(Operation.CONTENT_TYPE_HEADER,
                    Operation.MEDIA_TYPE_APPLICATION_JSON);
            try {
                operation.addRequestHeader(Operation.AUTHORIZATION_HEADER,
                        AUTH_HEADER_BEARER_PREFIX + this.credentials.getToken(AZURE_CORE_MANAGEMENT_URI));
            } catch (Exception ex) {
                return DeferredResult.failed(ex);
            }

            return this.service
                    .sendWithDeferredResult(operation, NetworkSecurityGroupListResult.class)
                    .thenApply(results -> {
                        RemoteResourcesPage page = new RemoteResourcesPage();

                        if (results.value != null) {
                            results.value.forEach(securityGroup -> {
                                page.resourcesPage.put(securityGroup.id, securityGroup);
                            });
                        }
                        page.nextPageLink = results.nextLink;

                        return page;
                    });
        }

        /**
         * Load local Security Group states and in addition load their related resource groups. They
         * will be used when building the updated local Security Group states.
         */
        @Override
        protected DeferredResult queryLocalStates(
                SecurityGroupEnumContext context) {
            return super.queryLocalStates(context)
                    .thenCompose(this::getSecurityGroupRGStates);
        }

        @Override
        protected void customizeLocalStatesQuery(Query.Builder qBuilder) {

            qBuilder.addCompositeFieldClause(
                    ResourceState.FIELD_NAME_CUSTOM_PROPERTIES,
                    ComputeProperties.COMPUTE_HOST_LINK_PROP_NAME,
                    this.request.parentCompute.documentSelfLink);
        }

        @Override
        protected DeferredResult buildLocalResourceState(
                NetworkSecurityGroup networkSecurityGroup,
                SecurityGroupState localSecurityGroupState) {
            LocalStateHolder holder = new LocalStateHolder();

            String resourceGroupId = AzureUtils.getResourceGroupId(networkSecurityGroup.id);
            String rgDocumentSelfLink = this.securityGroupRGStates.get(resourceGroupId);
            if (rgDocumentSelfLink == null) {
                // The Resource Group of Security Group is still not enumerated.
                // TODO: add log.
                holder.localState = this.SKIP;
                return DeferredResult.completed(holder);
            }

            holder.localState = new SecurityGroupState();

            if (localSecurityGroupState != null) {
                // Update: no need to touch Photon fields which are not affected by Azure
                holder.localState.documentSelfLink = localSecurityGroupState.documentSelfLink;
            } else {
                // Create: need to set Photon fields
                holder.localState.customProperties = new HashMap<>();
                holder.localState.customProperties.put(
                        ComputeProperties.COMPUTE_HOST_LINK_PROP_NAME,
                        this.request.parentCompute.documentSelfLink);

                holder.localState.authCredentialsLink = this.request.parentAuth.documentSelfLink;
                holder.localState.resourcePoolLink = this.request.original.resourcePoolLink;

                holder.localState.instanceAdapterReference = UriUtils
                        .buildUri(this.service.getHost(), AZURE_SECURITY_GROUP_ADAPTER);
            }

            // Fields explicitly affected by Azure

            holder.localState.groupLinks = Collections.singleton(rgDocumentSelfLink);
            holder.localState.name = networkSecurityGroup.name;
            holder.localState.regionId = networkSecurityGroup.location;

            // Add tags
            if (networkSecurityGroup.tags != null) {
                holder.remoteTags.putAll(networkSecurityGroup.tags);
            }

            if (networkSecurityGroup.properties == null
                    || networkSecurityGroup.properties.securityRules == null) {
                // No rules.
                return DeferredResult.completed(holder);
            }

            holder.localState.ingress = new ArrayList<>();
            holder.localState.egress = new ArrayList<>();

            networkSecurityGroup.properties.securityRules.forEach(securityRule -> {

                Rule rule = new Rule();
                rule.name = securityRule.name;
                rule.access = AzureConstants.AZURE_SECURITY_GROUP_ACCESS
                        .equalsIgnoreCase(securityRule.properties.access)
                                ? Access.Allow
                                : Access.Deny;
                rule.protocol = securityRule.properties.protocol;

                final List rulesList;
                String ports;

                if (SecurityRuleDirection.INBOUND.toString().equalsIgnoreCase(
                        securityRule.properties.direction)) {
                    // ingress rule.
                    rule.ipRangeCidr = securityRule.properties.sourceAddressPrefix;
                    ports = securityRule.properties.sourcePortRange;
                    rulesList = holder.localState.ingress;
                } else {
                    // egress rule.
                    rule.ipRangeCidr = securityRule.properties.destinationAddressPrefix;
                    ports = securityRule.properties.destinationPortRange;
                    rulesList = holder.localState.egress;
                }

                if (SecurityGroupService.ANY.equals(ports)) {
                    ports = SecurityGroupService.ALL_PORTS;
                }

                rule.ports = ports;
                try {
                    if (!SecurityGroupService.ANY.equals(rule.ipRangeCidr)) {
                        new SubnetUtils(rule.ipRangeCidr);
                    }
                    rulesList.add(rule);
                } catch (IllegalArgumentException e) {
                    // Ignore this rule as not supported by the system.
                    this.service.logWarning(() -> String.format("Network Security Rule is ignored."
                            + " Rule ip range: %s.", rule.ipRangeCidr));
                }
            });

            return DeferredResult.completed(holder);
        }

        /**
         * For each loaded Security Group load its corresponding resource group state based on the
         * resource group id.
         */
        private DeferredResult getSecurityGroupRGStates(
                SecurityGroupEnumContext context) {

            if (context.remoteResources.keySet().isEmpty()) {
                return DeferredResult.completed(context);
            }

            List resourceGroupIds = context.remoteResources.keySet().stream()
                    .map(AzureUtils::getResourceGroupId)
                    .collect(Collectors.toList());

            Query.Builder qBuilder = Builder.create()
                    .addKindFieldClause(ResourceGroupState.class)
                    .addInClause(ResourceState.FIELD_NAME_ID, resourceGroupIds)
                    .addCompositeFieldClause(
                            ResourceState.FIELD_NAME_CUSTOM_PROPERTIES,
                            ComputeProperties.COMPUTE_HOST_LINK_PROP_NAME,
                            this.request.parentCompute.documentSelfLink)
                    .addCompositeFieldClause(
                            ResourceState.FIELD_NAME_CUSTOM_PROPERTIES,
                            ComputeProperties.RESOURCE_TYPE_KEY,
                            ResourceGroupStateType.AzureResourceGroup.name());

            QueryStrategy queryByPages = new QueryByPages(
                    this.service.getHost(),
                    qBuilder.build(),
                    ResourceGroupState.class,
                    context.request.parentCompute.tenantLinks,
                    context.request.original.endpointLink)
                            .setMaxPageSize(resourceGroupIds.size());

            return queryByPages.queryDocuments(
                    rgState -> this.securityGroupRGStates.put(rgState.id, rgState.documentSelfLink))
                    .thenApply(ignore -> context);
        }
    }

    public AzureSecurityGroupEnumerationAdapterService() {
        super.toggleOption(ServiceOption.INSTRUMENTATION, true);
    }

    @Override
    public void handlePatch(Operation op) {
        if (!op.hasBody()) {
            op.fail(new IllegalArgumentException("body is required"));
            return;
        }

        SecurityGroupEnumContext ctx = new SecurityGroupEnumContext(
                this, op.getBody(ComputeEnumerateAdapterRequest.class), op);

        if (ctx.request.original.isMockRequest) {
            op.complete();
            return;
        }

        handleEnumeration(ctx);
    }

    /**
     * Creates the firewall states in the local document store based on the network security groups
     * received from the remote endpoint.
     *
     * @param context
     *            The local service context that has all the information needed to create the
     *            additional description states in the local system.
     */
    private void handleEnumeration(SecurityGroupEnumContext context) {
        switch (context.stage) {

        case CLIENT:
            if (context.credentials == null) {
                try {
                    context.credentials = getAzureConfig(context.request.parentAuth);
                } catch (Throwable e) {
                    handleError(context, e);
                    return;
                }
            }
            context.stage = EnumerationStages.ENUMERATE;
            handleEnumeration(context);
            break;
        case ENUMERATE:
            String enumKey = getEnumKey(context);
            switch (context.request.original.enumerationAction) {
            case START:
                logInfo(() -> String.format("Launching Azure SecurityGroup enumeration for %s",
                        enumKey));
                context.request.original.enumerationAction = EnumerationAction.REFRESH;
                handleEnumeration(context);
                break;
            case REFRESH:
                // Allow base context class to enumerate the resources.
                context.enumerate()
                        .whenComplete((ignoreCtx, t) -> {
                            // NOTE: In case of error 'ignoreCtx' is null so use passed context!
                            if (t != null) {
                                handleError(context, t);
                                return;
                            }
                            context.stage = EnumerationStages.FINISHED;
                            handleEnumeration(context);
                        });
                break;
            case STOP:
                logInfo(() -> String.format(
                        "Azure SecurityGroup enumeration will be stopped for %s",
                        enumKey));
                context.stage = EnumerationStages.FINISHED;
                handleEnumeration(context);
                break;
            default:
                handleError(context, new RuntimeException("Unknown enumeration action"
                        + context.request.original.enumerationAction));
                break;
            }
            break;
        case FINISHED:
            logInfo(() -> String.format("Azure SecurityGroup enumeration finished for %s",
                    getEnumKey(context)));
            context.operation.complete();
            break;
        case ERROR:
            logWarning(() -> String.format("Azure SecurityGroup enumeration error for %s",
                    getEnumKey(context)));
            context.operation.fail(context.error);
            break;
        default:
            String msg = String.format("Unknown Azure enumeration stage %s ",
                    context.stage.toString());
            logSevere(() -> msg);
            context.error = new IllegalStateException(msg);
            context.operation.fail(context.error);
        }
    }

    /**
     * Return a key to uniquely identify enumeration for compute host instance.
     */
    private String getEnumKey(SecurityGroupEnumContext ctx) {
        return ctx.request.original.getEnumKey();
    }

    private void handleError(SecurityGroupEnumContext ctx, Throwable e) {
        logSevere(() -> String.format("Failed at stage %s with exception: %s", ctx.stage,
                Utils.toString(e)));
        ctx.error = e;
        ctx.stage = EnumerationStages.ERROR;
        handleEnumeration(ctx);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy