Partilhar via


Implementar uma aplicação web Amazon Web Services (AWS) para o Azure

Neste artigo, implementa a aplicação Yelb para o cluster Azure Kubernetes Service (AKS) que criou no artigo anterior.

Verifique o ambiente

Antes de implementar a aplicação, certifique-se de que o seu cluster AKS está devidamente configurado utilizando os seguintes comandos:

  1. Liste os namespaces no seu cluster usando o comando kubectl get namespace.

    kubectl get namespace
    

    Se instalou o controlador de ingress NGINX usando o complemento de encaminhamento de aplicações, deverá ver o namespace app-routing-system no resultado:

    NAME                 STATUS   AGE
    app-routing-system   Active   4h28m
    cert-manager         Active   109s
    dapr-system          Active   4h18m
    default              Active   4h29m
    gatekeeper-system    Active   4h28m
    kube-node-lease      Active   4h29m
    kube-public          Active   4h29m
    kube-system          Active   4h29m
    

    Se instalaste o controlador de entrada NGINX via Helm, deves ver o ingress-basic namespace na saída:

    NAME                STATUS   AGE
    cert-manager        Active   7m42s
    dapr-system         Active   11m
    default             Active   21m
    gatekeeper-system   Active   20m
    ingress-basic       Active   7m19s
    kube-node-lease     Active   21m
    kube-public         Active   21m
    kube-system         Active   21m
    prometheus          Active   8m9s
    
  2. Obtenha os detalhes do serviço do app-routing-system ou do ingress-basic namespace usando o kubectl get service command.

    kubectl get service --namespace <namespace-name> -o wide
    

    Se utilizou o suplemento de encaminhamento de aplicações, deverá ver que o EXTERNAL-IP do serviço nginx é um endereço IP privado. Este endereço é o IP privado de uma configuração de IP de frontend no balanceador de carga privado kubernetes-internal do seu cluster AKS.

    NAME    TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                                      AGE     SELECTOR
    nginx   LoadBalancer   172.16.55.104   10.240.0.7    80:31447/TCP,443:31772/TCP,10254:30459/TCP   4h28m   app=nginx
    

    Se utilizou o Helm, deverá ver que o EXTERNAL-IP do serviço nginx-ingress-ingress-nginx-controller é um endereço IP privado. Este endereço é o IP privado de uma configuração de IP frontend no kubernetes-internal balanceador de carga privado do seu cluster AKS.

    NAME                                               TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
    nginx-ingress-ingress-nginx-controller             LoadBalancer   172.16.42.152    10.240.0.7    80:32117/TCP,443:32513/TCP   7m31s
    nginx-ingress-ingress-nginx-controller-admission   ClusterIP      172.16.78.85     <none>        443/TCP                      7m31s
    nginx-ingress-ingress-nginx-controller-metrics     ClusterIP      172.16.109.138   <none>        10254/TCP                    7m31s
    

Prepare-se para implementar a aplicação Yelb

Se quiser implementar o exemplo usando a abordagem rescisão TLS no Gateway de Aplicação e invocação Yelb via HTTP, pode encontrar os scripts Bash e os modelos YAML para implementar a aplicação Yelb na pasta http.

Se quiseres implementar o exemplo usando a arquitetura Implementing end-to-end TLS usando Gateway de Aplicação do Azure, podes encontrar os scripts Bash e templates YAML para implementar a aplicação web na pasta https.

Nas secções restantes deste artigo, orientamo-lo através do processo de implementação da aplicação de exemplo utilizando a abordagem de TLS de ponta a ponta.

Personalizar variáveis

  1. Antes de executar qualquer script, necessita de personalizar os valores das variáveis no ficheiro 00-variables.sh. Este ficheiro está incluído em todos os scripts e contém as seguintes variáveis:

    # Azure subscription and tenant
    RESOURCE_GROUP_NAME="<aks-resource-group>"
    SUBSCRIPTION_ID="$(az account show --query id --output tsv)"
    SUBSCRIPTION_NAME="$(az account show --query name --output tsv)"
    TENANT_ID="$(az account show --query tenantId --output tsv)"
    AKS_CLUSTER_NAME="<aks-name>"
    AGW_NAME="<application-gateway-name>"
    AGW_PUBLIC_IP_NAME="<application-gateway-public-ip-name>"
    DNS_ZONE_NAME="<your-azure-dns-zone-name-eg-contoso.com>"
    DNS_ZONE_RESOURCE_GROUP_NAME="<your-azure-dns-zone-resource-group-name>"
    DNS_ZONE_SUBSCRIPTION_ID="<your-azure-dns-zone-subscription-id>"
    
    # NGINX ingress controller installed via Helm
    NGINX_NAMESPACE="ingress-basic"
    NGINX_REPO_NAME="ingress-nginx"
    NGINX_REPO_URL="https://kubernetes.github.io/ingress-nginx"
    NGINX_CHART_NAME="ingress-nginx"
    NGINX_RELEASE_NAME="ingress-nginx"
    NGINX_REPLICA_COUNT=3
    
    # Specify the ingress class name for the ingress controller
    # - nginx: Unmanaged NGINX ingress controller installed via Helm
    # - webapprouting.kubernetes.azure.com: Managed NGINX ingress controller installed via AKS application routing add-on
    INGRESS_CLASS_NAME="webapprouting.kubernetes.azure.com"
    
    # Subdomain of the Yelb UI service
    SUBDOMAIN="<yelb-application-subdomain>"
    
    # URL of the Yelb UI service
    URL="https://$SUBDOMAIN.$DNS_ZONE_NAME"
    
    # Secret provider class
    KEY_VAULT_NAME="<key-vault-name>"
    KEY_VAULT_CERTIFICATE_NAME="<key-vault-resource-group-name>"
    KEY_VAULT_SECRET_PROVIDER_IDENTITY_CLIENT_ID="<key-vault-secret-provider-identity-client-id>"
    TLS_SECRET_NAME="yelb-tls-secret"
    NAMESPACE="yelb"
    
  2. Pode executar o seguinte comando az aks show para recuperar o clientId da identidade gerida atribuída pelo utilizador usada pelo Azure Key Vault Provider for Secrets Store CSI Driver. O módulo keyVault.bicepKey Vault Administrador função para a identidade gerida atribuída pelo utilizador do addon para permitir que este recupere o certificado usado pelo Kubernetes Ingress usado para expor o serviço yelb-ui via o controlador de entrada NGINX.

    az aks show \
      --name <aks-name> \
      --resource-group <aks-resource-group-name> \
      --query addonProfiles.azureKeyvaultSecretsProvider.identity.clientId \
      --output tsv \
      --only-show-errors
    
  3. Se implantou a infraestrutura Azure usando os módulos Bicep fornecidos com este exemplo, pode proceder à implementação da aplicação Yelb. Se quiser implementar a aplicação no seu cluster AKS, pode usar os seguintes scripts para configurar o seu ambiente. Pode usar o 02-create-nginx-ingress-controller.sh para instalar o controlador de entrada NGINX com o firewall de aplicações web open-source ModSecurity (WAF) ativado.

    #!/bin/bash
    
    # Variables
    source ./00-variables.sh
    
    # Check if the NGINX ingress controller Helm chart is already installed
    result=$(helm list -n $NGINX_NAMESPACE | grep $NGINX_RELEASE_NAME | awk '{print $1}')
    
    if [[ -n $result ]]; then
      echo "[$NGINX_RELEASE_NAME] NGINX ingress controller release already exists in the [$NGINX_NAMESPACE] namespace"
    else
      # Check if the NGINX ingress controller repository is not already added
      result=$(helm repo list | grep $NGINX_REPO_NAME | awk '{print $1}')
    
      if [[ -n $result ]]; then
        echo "[$NGINX_REPO_NAME] Helm repo already exists"
      else
        # Add the NGINX ingress controller repository
        echo "Adding [$NGINX_REPO_NAME] Helm repo..."
        helm repo add $NGINX_REPO_NAME $NGINX_REPO_URL
      fi
    
      # Update your local Helm chart repository cache
      echo 'Updating Helm repos...'
      helm repo update
    
      # Deploy NGINX ingress controller
      echo "Deploying [$NGINX_RELEASE_NAME] NGINX ingress controller to the [$NGINX_NAMESPACE] namespace..."
      helm install $NGINX_RELEASE_NAME $NGINX_REPO_NAME/$nginxChartName \
        --create-namespace \
        --namespace $NGINX_NAMESPACE \
        --set controller.nodeSelector."kubernetes\.io/os"=linux \
        --set controller.replicaCount=$NGINX_REPLICA_COUNT \
        --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \
        --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz
    fi
    
    # Get values
    helm get values $NGINX_RELEASE_NAME --namespace $NGINX_NAMESPACE
    

Implantar o aplicativo

  1. Execute o seguinte script 03-deploy-yelb.sh para implementar a aplicação Yelb e um objeto Ingresso do Kubernetes para tornar o serviço yelb-ui acessível à internet pública.

    #!/bin/bash
    
    # Variables
    source ./00-variables.sh
    
    # Check if namespace exists in the cluster
    result=$(kubectl get namespace -o jsonpath="{.items[?(@.metadata.name=='$NAMESPACE')].metadata.name}")
    
    if [[ -n $result ]]; then
      echo "$NAMESPACE namespace already exists in the cluster"
    else
      echo "$NAMESPACE namespace does not exist in the cluster"
      echo "creating $NAMESPACE namespace in the cluster..."
      kubectl create namespace $NAMESPACE
    fi
    
    # Create the Secret Provider Class object
    echo "Creating the secret provider class object..."
    cat <<EOF | kubectl apply -f -
    apiVersion: secrets-store.csi.x-k8s.io/v1
    kind: SecretProviderClass
    metadata:
      namespace: $NAMESPACE
      name: yelb
    spec:
      provider: azure
      secretObjects:
        - secretName: $TLS_SECRET_NAME
          type: kubernetes.io/tls
          data: 
            - objectName: $KEY_VAULT_CERTIFICATE_NAME
              key: tls.key
            - objectName: $KEY_VAULT_CERTIFICATE_NAME
              key: tls.crt
      parameters:
        usePodIdentity: "false"
        useVMManagedIdentity: "true"
        userAssignedIdentityID: $KEY_VAULT_SECRET_PROVIDER_IDENTITY_CLIENT_ID
        keyvaultName: $KEY_VAULT_NAME
        objects: |
          array:
            - |
              objectName: $KEY_VAULT_CERTIFICATE_NAME
              objectType: secret
        tenantId: $TENANT_ID
    EOF
    
    # Apply the YAML configuration
    kubectl apply -f yelb.yml
    
    echo "waiting for secret $TLS_SECRET_NAME in namespace $namespace..."
    
    while true; do
      if kubectl get secret -n $NAMESPACE $TLS_SECRET_NAME >/dev/null 2>&1; then
        echo "secret $TLS_SECRET_NAME found!"
        break
      else
        printf "."
        sleep 3
      fi
    done
    
    # Create chat-ingress
    cat ingress.yml |
      yq "(.spec.ingressClassName)|="\""$INGRESS_CLASS_NAME"\" |
      yq "(.spec.tls[0].hosts[0])|="\""$SUBDOMAIN.$DNS_ZONE_NAME"\" |
      yq "(.spec.tls[0].secretName)|="\""$TLS_SECRET_NAME"\" |
      yq "(.spec.rules[0].host)|="\""$SUBDOMAIN.$DNS_ZONE_NAME"\" |
      kubectl apply -f -
    
    # Check the deployed resources within the yelb namespace:
    kubectl get all -n yelb
    
  2. Atualize o manifesto YAML yelb-ui para incluir a definição csi volume e volume mount para ler o certificado como um segredo de Azure Key Vault.

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: yelb
  name: yelb-ui
spec:
  replicas: 1
  selector:
    matchLabels:
      app: yelb-ui
      tier: frontend
  template:
    metadata:
      labels:
        app: yelb-ui
        tier: frontend
    spec:
      containers:
        - name: yelb-ui
          image: mreferre/yelb-ui:0.7
          ports:
            - containerPort: 80
          volumeMounts:
            - name: secrets-store-inline
              mountPath: "/mnt/secrets-store"
              readOnly: true
      volumes:
        - name: secrets-store-inline
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: yelb
  1. Agora pode implementar a aplicação. O script utiliza o yelb.yml manifesto YAML para implementar a aplicação e o ingress.yml para criar o objeto ingress. Se usar uma Zona DNS Pública Azure para resolução de nomes de domínio, pode empregar o script 04-configure-dns.sh. Este script associa o endereço IP público do controlador de entrada NGINX com o domínio usado pelo objeto de entrada, que expõe o serviço yelb-ui. O script executa as seguintes etapas:

    1. Recupera o endereço público do IP público do Azure usado pela configuração de IP front-end do Application Gateway.
    2. Verifica se existe um registo A para o subdomínio utilizado pelo serviço yelb-ui.
    3. Se o registo A não existir, o script irá criá-lo.
source ./00-variables.sh

# Get the address of the Application Gateway Public IP
echo "Retrieving the address of the [$AGW_PUBLIC_IP_NAME] public IP address of the [$AGW_NAME] Application Gateway..."
PUBLIC_IP_ADDRESS=$(az network public-ip show \
    --resource-group $RESOURCE_GROUP_NAME \
    --name $AGW_PUBLIC_IP_NAME \
    --query ipAddress \
    --output tsv \
    --only-show-errors)
if [[ -n $PUBLIC_IP_ADDRESS ]]; then
    echo "[$PUBLIC_IP_ADDRESS] public IP address successfully retrieved for the [$AGW_NAME] Application Gateway"
else
    echo "Failed to retrieve the public IP address of the [$AGW_NAME] Application Gateway"
    exit
fi
# Check if an A record for todolist subdomain exists in the DNS Zone
echo "Retrieving the A record for the [$SUBDOMAIN] subdomain from the [$DNS_ZONE_NAME] DNS zone..."
IPV4_ADDRESS=$(az network dns record-set a list \
    --zone-name $DNS_ZONE_NAME \
    --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \
    --subscription $DNS_ZONE_SUBSCRIPTION_ID \
    --query "[?name=='$SUBDOMAIN'].ARecords[].IPV4_ADDRESS" \
    --output tsv \
    --only-show-errors)
if [[ -n $IPV4_ADDRESS ]]; then
    echo "An A record already exists in [$DNS_ZONE_NAME] DNS zone for the [$SUBDOMAIN] subdomain with [$IPV4_ADDRESS] IP address"
    if [[ $IPV4_ADDRESS == $PUBLIC_IP_ADDRESS ]]; then
        echo "The [$IPV4_ADDRESS] ip address of the existing A record is equal to the ip address of the ingress"
        echo "No additional step is required"
        continue
    else
        echo "The [$IPV4_ADDRESS] ip address of the existing A record is different than the ip address of the ingress"
    fi
    # Retrieving name of the record set relative to the zone
    echo "Retrieving the name of the record set relative to the [$DNS_ZONE_NAME] zone..."
    RECORDSET_NAME=$(az network dns record-set a list \
        --zone-name $DNS_ZONE_NAME \
        --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \
        --subscription $DNS_ZONE_SUBSCRIPTION_ID \
        --query "[?name=='$SUBDOMAIN'].name" \
        --output tsv \
        --only-show-errors 2>/dev/null)
    if [[ -n $RECORDSET_NAME ]]; then
        echo "[$RECORDSET_NAME] record set name successfully retrieved"
    else
        echo "Failed to retrieve the name of the record set relative to the [$DNS_ZONE_NAME] zone"
        exit
    fi
    # Remove the A record
    echo "Removing the A record from the record set relative to the [$DNS_ZONE_NAME] zone..."
    az network dns record-set a remove-record \
        --ipv4-address $IPV4_ADDRESS \
        --record-set-name $RECORDSET_NAME \
        --zone-name $DNS_ZONE_NAME \
        --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \
        --subscription $DNS_ZONE_SUBSCRIPTION_ID \
        --only-show-errors 1>/dev/null
    if [[ $? == 0 ]]; then
        echo "[$IPV4_ADDRESS] ip address successfully removed from the [$RECORDSET_NAME] record set"
    else
        echo "Failed to remove the [$IPV4_ADDRESS] ip address from the [$RECORDSET_NAME] record set"
        exit
    fi
fi
# Create the A record
echo "Creating an A record in [$DNS_ZONE_NAME] DNS zone for the [$SUBDOMAIN] subdomain with [$PUBLIC_IP_ADDRESS] IP address..."
az network dns record-set a add-record \
    --zone-name $DNS_ZONE_NAME \
    --resource-group $DNS_ZONE_RESOURCE_GROUP_NAME \
    --subscription $DNS_ZONE_SUBSCRIPTION_ID \
    --record-set-name $SUBDOMAIN \
    --ipv4-address $PUBLIC_IP_ADDRESS \
    --only-show-errors 1>/dev/null
if [[ $? == 0 ]]; then
    echo "A record for the [$SUBDOMAIN] subdomain with [$PUBLIC_IP_ADDRESS] IP address successfully created in [$DNS_ZONE_NAME] DNS zone"
else
    echo "Failed to create an A record for the $SUBDOMAIN subdomain with [$PUBLIC_IP_ADDRESS] IP address in [$DNS_ZONE_NAME] DNS zone"
fi

Observação

Antes de implementar a aplicação Yelb e criar o objeto ingress, o script gera um SecretProviderClass para recuperar o certificado TLS de Azure Key Vault e gerar o segredo Kubernetes para o objeto ingress. É importante notar que o Secrets Store CSI Driver para Key Vault cria o segredo do Kubernetes contendo o certificado TLS apenas quando a definição de volume SecretProviderClass está incluída no deployment. Para garantir que o certificado TLS é devidamente recuperado da Azure Key Vault e armazenado no segredo Kubernetes usado pelo objeto ingress, precisamos de fazer as seguintes modificações ao manifesto YAML da implementação yelb-ui:

  • Adicionar a definição csi volume usando o driver secrets-store.csi.k8s.io, que faz referência ao objeto SecretProviderClass responsável por recuperar o certificado TLS de Azure Key Vault.
  • Inclua volume mount para ler o certificado como segredo no Azure Key Vault.

Para mais informações, consulte Configurar Secrets Store CSI Driver para ativar o controlador de ingresso NGINX com TLS.

Testar a aplicação

Utilize o 05-call-yelb-ui.sh script para invocar o serviço yelb-ui, simular injeção de SQL, ataques XSS, e observe como o conjunto de regras gerido do ModSecurity bloqueia pedidos maliciosos.

#!/bin/bash
# Variables
source ./00-variables.sh
# Call REST API
echo "Calling Yelb UI service at $URL..."
curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL
# Simulate SQL injection
echo "Simulating SQL injection when calling $URL..."
curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users=ExampleSQLInjection%27%20--
# Simulate XSS
echo "Simulating XSS when calling $URL..."
curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users=ExampleXSS%3Cscript%3Ealert%28%27XSS%27%29%3C%2Fscript%3E
# A custom rule blocks any request with the word blockme in the querystring.
echo "Simulating query string manipulation with the 'blockme' word in the query string..."
curl -w 'HTTP Status: %{http_code}\n' -s -o /dev/null $URL/?users?task=blockme

O script Bash deve produzir o seguinte resultado, onde a primeira chamada é bem-sucedida, enquanto as regras do ModSecurity bloqueiam as duas chamadas seguintes.

Calling Yelb UI service at https://yelb.contoso.com...
HTTP Status: 200
Simulating SQL injection when calling https://yelb.contoso.com...
HTTP Status: 403
Simulating XSS when calling https://yelb.contoso.com...
HTTP Status: 403
Simulating query string manipulation with the 'blockme' word in the query string...
HTTP Status: 403

Monitorizar a aplicação

Na solução proposta, o processo de implementação configura automaticamente o recurso Gateway de Aplicação do Azure para recolher registos de diagnóstico e métricas para um espaço de trabalho Azure Log Analytics Workspace. Ao ativar logs, pode obter informações valiosas sobre as avaliações, correspondências e blocos realizados pelo Firewall de Aplicações Web do Azure (WAF) dentro do Application Gateway. Mais informações podem ser consultadas em Registos de diagnóstico para Application Gateway. Também pode usar o Log Analytics para examinar os dados dentro dos registos do firewall. Quando tem os registos do firewall no seu espaço de trabalho do Log Analytics, pode visualizar dados, escrever consultas, criar visualizações e adicioná-los ao painel do seu portal. Para informações detalhadas sobre consultas de registo, consulte Visão geral das consultas de registo em Azure Monitor.

Explore dados com consultas Kusto

Na solução proposta, o processo de implementação configura automaticamente o recurso Gateway de Aplicação do Azure para recolher registos de diagnóstico e métricas para um espaço de trabalho Azure Log Analytics. Ao ativar logs, pode obter informações sobre as avaliações, os acertos e os bloqueios realizados pelo Firewall de Aplicações Web do Azure (WAF) no Application Gateway. Mais informações podem ser consultadas em Registos de diagnóstico para Application Gateway.

Também pode usar o Log Analytics para examinar os dados dentro dos registos do firewall. Quando tem os registos do firewall no seu espaço de trabalho do Log Analytics, pode visualizar dados, escrever consultas, criar visualizações e adicioná-los ao painel do seu portal. Para mais informações sobre consultas de log, consulte Visão geral das consultas de log em Azure Monitor.

AzureDiagnostics 
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"
| limit 10

Alternativamente, ao trabalhar com a tabela Específica de Recursos os dados de log bruto do firewall podem ser acedidos usando a seguinte consulta. Para saber mais sobre tabelas específicas de recursos, consulte a documentação de Referência de dados de monitorização.

AGWFirewallLogs
| limit 10

Assim que tiver os dados, poderá aprofundar-se e criar gráficos ou visualizações. Aqui estão alguns exemplos adicionais de consultas KQL que podem ser utilizadas:

Pedidos correspondidos/bloqueados por IP

AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"
| summarize count() by clientIp_s, bin(TimeGenerated, 1m)
| render timechart

Solicitações correspondentes/bloqueadas por URI

AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"
| summarize count() by requestUri_s, bin(TimeGenerated, 1m)
| render timechart

Principais regras correspondentes

| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"
| summarize count() by ruleId_s, bin(TimeGenerated, 1m)
| where count_ > 10
| render timechart

Os cinco principais grupos de regras correspondentes

AzureDiagnostics
| where ResourceProvider == "MICROSOFT.NETWORK" and Category == "ApplicationGatewayFirewallLog"
| summarize Count=count() by details_file_s, action_s
| top 5 by Count desc
| render piechart

Analisar os recursos implementados

Pode usar o CLI do Azure ou o Azure PowerShell para listar os recursos implementados no grupo de recursos.

Liste os recursos implementados no grupo de recursos usando o comando [az resource list][az-resource-list].

az resource list --resource-group <resource-group-name>

Podes usar o CLI do Azure ou o Azure PowerShell para eliminar o grupo de recursos quando já não precisares dos recursos que criaste neste tutorial.

Elimine o grupo de recursos e os seus recursos associados utilizando o comando az group delete.

az group delete --name <resource-group-name>

Próximos passos

Pode aumentar a segurança e a proteção contra ameaças da solução usando Azure DDoS Protection e Azure Firewall. Para obter mais informações, consulte os seguintes artigos:

Se usar o controlador de entrada NGINX ou qualquer outro controlador de entrada hospedado em AKS em vez do Gateway de Aplicação do Azure, pode usar o Azure Firewall para inspecionar o tráfego de e para o cluster AKS e proteger o cluster da exfiltração de dados e outro tráfego indesejado de rede. Para obter mais informações, consulte os seguintes artigos:

Contribuidores

A Microsoft mantém este artigo. Os seguintes colaboradores escreveram-no originalmente:

Autor principal:

Outros contribuidores: