Compartilhar via


Considerações de segurança para cargas de trabalho de missão crítica

As cargas de trabalho críticas devem ser inerentemente protegidas. Se um aplicativo ou sua infraestrutura for comprometido, a disponibilidade estará em risco. O foco dessa arquitetura é maximizar a confiabilidade para que o aplicativo permaneça eficiente e disponível em todas as circunstâncias. Os controles de segurança são aplicados principalmente com o objetivo de mitigar ameaças que afetam a disponibilidade e a confiabilidade.

Observação

Seus requisitos de negócios podem precisar de mais medidas de segurança. É altamente recomendável que você estenda os controles em sua implementação de acordo com as diretrizes em Considerações de segurança do Azure Well-Architected Framework para cargas de trabalho críticas.

Gerenciamento de identidade e de acesso

No nível do aplicativo, essa arquitetura usa um esquema de autenticação simples baseado em chaves de API para algumas operações restritas, como criar itens de catálogo ou excluir comentários. Cenários avançados, como autenticação de usuário e funções de usuário, estão além do escopo da arquitetura de linha de base.

Se o aplicativo exigir autenticação de usuário e gerenciamento de conta, siga as recomendações para gerenciamento de identidade e acesso. Algumas estratégias incluem o uso de provedores de identidade gerenciados, evitando o gerenciamento de identidade personalizado e usando autenticação sem senha quando possível.

Acesso com privilégio mínimo

Configure políticas de acesso para que usuários e aplicativos obtenham o nível mínimo de acesso necessário para cumprir sua função. Os desenvolvedores normalmente não precisam de acesso à infraestrutura de produção, mas o pipeline de implantação precisa de acesso total. Os clusters do Kubernetes não enviam imagens de contêiner para um registro, mas os fluxos de trabalho do GitHub podem fazê-lo. As APIs de front-end geralmente não recebem mensagens do agente de mensagens, e os trabalhadores de back-end não necessariamente enviam novas mensagens para o agente. Essas decisões dependem da carga de trabalho e do nível de acesso que você atribui deve refletir a funcionalidade de cada componente.

Exemplos da implementação de referência crítica do Azure incluem:

  • Cada componente de aplicativo que funciona com os Hubs de Eventos do Azure usa uma cadeia de conexão com permissões de Escuta (BackgroundProcessor) ou Enviar (CatalogService). Esse nível de acesso garante que cada pod tenha apenas o acesso mínimo necessário para cumprir sua função.
  • A entidade de serviço do pool de agentes do AKS (Serviço de Kubernetes do Azure) só tem permissões Obter e Listar para Segredos no Azure Key Vault.
  • A identidade do AKS Kubelet só tem a permissão AcrPull para acessar o registro de contêiner global.

Identidades gerenciadas

Para melhorar a segurança de uma carga de trabalho crítica, evite usar segredos baseados em serviço, como cadeias de conexão ou chaves de API, quando possível. Recomendamos que você use identidades gerenciadas se o serviço do Azure der suporte a essa funcionalidade.

A implementação de referência usa uma identidade gerenciada atribuída pelo serviço no pool de agentes do AKS ("identidade do Kubelet") para acessar o Registro de Contêiner do Azure global e o cofre de chaves de um selo. As funções internas apropriadas são usadas para restringir o acesso. Por exemplo, este código do Terraform atribui apenas a AcrPull função à identidade do Kubelet:

resource "azurerm_role_assignment" "acrpull_role" {
  scope                = data.azurerm_container_registry.global.id
  role_definition_name = "AcrPull"
  principal_id         = azurerm_kubernetes_cluster.stamp.kubelet_identity.0.object_id
}

Segredos

Quando possível, use a autenticação do Microsoft Entra em vez de chaves ao acessar os recursos do Azure. Muitos serviços do Azure, como o Azure Cosmos DB e o Armazenamento do Azure, dão suporte à opção de desabilitar completamente a autenticação de chave. O AKS dá suporte à ID de carga de trabalho do Microsoft Entra.

Para cenários em que você não pode usar a autenticação do Microsoft Entra, cada carimbo de implantação tem uma instância dedicada do Key Vault para armazenar chaves. Essas chaves são criadas automaticamente durante a implantação e são armazenadas no Key Vault com o Terraform. Nenhum operador humano, exceto desenvolvedores em ambientes de ponta a ponta, pode interagir com segredos. Além disso, as políticas de acesso do Key Vault são configuradas para que nenhuma conta de usuário tenha permissão para acessar segredos.

Observação

Essa carga de trabalho não usa certificados personalizados, mas os mesmos princípios se aplicam.

No cluster do AKS, o Provedor do Key Vault para Repositório de Segredos permite que o aplicativo consuma segredos. O driver CSI carrega chaves do Key Vault e as monta como arquivos em pods individuais.

#
# /src/config/csi-secrets-driver/chart/csi-secrets-driver-config/templates/csi-secrets-driver.yaml
#
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: azure-kv
spec:
  provider: azure
  parameters:
    usePodIdentity: "false"
    useVMManagedIdentity: "true"
    userAssignedIdentityID: {{ .Values.azure.managedIdentityClientId | quote }}
    keyvaultName: {{ .Values.azure.keyVaultName | quote }}
    tenantId: {{ .Values.azure.tenantId | quote }}
    objects: |
      array:
        {{- range .Values.kvSecrets }}
        - |
          objectName: {{ . | quote }}
          objectAlias: {{ . | lower | replace "-" "_" | quote }}
          objectType: secret
        {{- end }}

A implementação de referência usa o Helm com o Azure Pipelines para implantar o driver CSI que contém todos os nomes de chave do Key Vault. O driver também é responsável por atualizar os segredos montados se eles forem alterados no Key Vault.

No lado do consumidor, ambos os aplicativos .NET usam o recurso integrado para ler a configuração de arquivos (AddKeyPerFile):

//
// /src/app/AlwaysOn.BackgroundProcessor/Program.cs
// + using Microsoft.Extensions.Configuration;
//
public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .ConfigureAppConfiguration((context, config) =>
    {
        // Load values from Kubernetes CSI Key Vault driver mount point.
        config.AddKeyPerFile(directoryPath: "/mnt/secrets-store/", optional: true, reloadOnChange: true);
        
        // More configuration if needed...
    })
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStartup<Startup>();
    });

A combinação do recarregamento automático do driver CSI ajuda reloadOnChange: true a garantir que, quando as chaves forem alteradas no Key Vault, os novos valores sejam montados no cluster. Esse processo não garante a rotação secreta no aplicativo. A implementação usa uma instância de cliente singleton do Azure Cosmos DB que exige que o pod seja reiniciado para aplicar a alteração.

Domínios personalizados e TLS

As cargas de trabalho baseadas na Web devem usar HTTPS para evitar ataques man-in-the-middle em todos os níveis de interação, como comunicação do cliente para a API ou de API para API. Certifique-se de automatizar a rotação de certificados, pois os certificados expirados ainda são uma causa comum de interrupções e experiências degradadas.

A implementação de referência oferece suporte total a HTTPS com nomes de domínio personalizados, como contoso.com. Ele também aplica a configuração apropriada aos int ambientes e prod . Você também pode adicionar domínios personalizados para e2e ambientes. No entanto, essa implementação de referência não usa nomes de domínio personalizados devido à natureza de curta duração e ao aumento do tempo de e2e implantação quando você usa domínios personalizados com certificados SSL no Azure Front Door.

Para habilitar a automação completa da implantação, você deve gerenciar o domínio personalizado por meio de uma zona DNS do Azure. O pipeline de implantação de infraestrutura cria dinamicamente registros CNAME na zona DNS do Azure e mapeia esses registros automaticamente para uma instância do Azure Front Door.

Os certificados SSL gerenciados pelo Azure Front Door estão habilitados, o que elimina a necessidade de renovações manuais de certificados SSL. O TLS 1.2 é configurado como a versão mínima.

#
# /src/infra/workload/globalresources/frontdoor.tf
#
resource "azurerm_frontdoor_custom_https_configuration" "custom_domain_https" {
  count                             = var.custom_fqdn != "" ? 1 : 0
  frontend_endpoint_id              = "${azurerm_frontdoor.main.id}/frontendEndpoints/${local.frontdoor_custom_frontend_name}"
  custom_https_provisioning_enabled = true

  custom_https_configuration {
    certificate_source = "FrontDoor"
  }
}

Os ambientes que não são provisionados com domínios personalizados podem ser acessados por meio do ponto de extremidade padrão do Azure Front Door. Por exemplo, você pode contatá-los em um endereço como env123.azurefd.net.

Observação

No controlador de entrada de cluster, os domínios personalizados não são usados em nenhum dos casos. Em vez disso, um nome DNS fornecido pelo Azure, como o [prefix]-cluster.[region].cloudapp.azure.com usado com Let's Encrypt, que pode emitir certificados SSL gratuitos para esses pontos de extremidade.

A implementação de cert-manager referência usa o Jetstack para provisionar automaticamente certificados SSL/TLS da Let's Encrypt para regras de entrada. Mais definições de configuração, como o , que solicita ClusterIssuercertificados do Let's Encrypt, são implantadas por meio de um gráfico de helm separado cert-manager-config armazenado em src/config/cert-manager/chart.

Essa implementação usa ClusterIssuer em vez de Issuer evitar ter emissores para cada namespace. Para obter mais informações, consulte a documentação do cert-manager e as notas de versão do cert-manager.

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:

Configuração

Toda a configuração de runtime do aplicativo é armazenada no Key Vault, incluindo segredos e configurações não confidenciais. Você pode usar um repositório de configuração, como a Configuração de Aplicativos do Azure, para armazenar as configurações. No entanto, ter um único armazenamento reduz o número de possíveis pontos de falha para aplicativos de missão crítica. Use o Key Vault para configuração de runtime para simplificar a implementação geral.

Os cofres de chaves devem ser preenchidos pelo pipeline de implantação. Na implementação, os valores necessários são originados diretamente do Terraform, como cadeias de conexão de banco de dados, ou passados como variáveis Terraform do pipeline de implantação.

A configuração de infraestrutura e implantação de ambientes individuais, como e2e, int, e prod, é armazenada em arquivos variáveis que fazem parte do repositório de código-fonte. Essa abordagem tem dois benefícios:

  • Todas as alterações em um ambiente são controladas e passam por pipelines de implantação antes de serem aplicadas ao ambiente.
  • Ambientes individuais e2e podem ser configurados de forma diferente porque a implantação é baseada no código em uma ramificação.

Uma exceção é o armazenamento de valores sensíveis para pipelines. Esses valores são armazenados como segredos em grupos de variáveis do Azure DevOps.

Segurança do contêiner

É necessário proteger imagens de contêiner para todas as cargas de trabalho em contêineres.

Essa implementação de referência usa contêineres do Workload Docker que são baseados em imagens de tempo de execução, não no SDK, para minimizar o volume e a superfície de ataque potencial. Não há outras ferramentas, como ping, wget, ou curl, instaladas.

O aplicativo é executado em um usuário workload sem privilégios que foi criado como parte do processo de construção da imagem:

RUN groupadd -r workload && useradd --no-log-init -r -g workload workload
USER workload

A implementação de referência usa o Helm para empacotar os manifestos YAML necessários para implantar componentes individuais. Esse processo inclui a implantação do Kubernetes, os serviços, a configuração de dimensionamento automático do pod horizontal e o contexto de segurança. Todos os gráficos do Helm contêm medidas de segurança básicas que seguem as práticas recomendadas do Kubernetes.

Essas medidas de segurança são:

  • readOnlyFilesystem: O sistema / de arquivos raiz em cada contêiner é definido como somente leitura para impedir que o contêiner grave no sistema de arquivos do host. Essa restrição impede que invasores baixem mais ferramentas e persistam código no contêiner. Os diretórios que exigem acesso de leitura-gravação são montados como volumes.
  • privileged: Todos os contêineres são definidos para serem executados como não privilegiados. A execução de um contêiner como privilegiado fornece todos os recursos ao contêiner e também elimina todas as limitações impostas pelo controlador do grupo de controle do dispositivo.
  • allowPrivilegeEscalation: Impede que o interior de um contêiner obtenha mais privilégios do que seu processo principal.

Essas medidas de segurança também são configuradas para contêineres que não são da Microsoft e gráficos do Helm, como cert-manager quando possível. Você pode usar o Azure Policy para auditar essas medidas de segurança.

#
# Example:
# /src/app/charts/backgroundprocessor/values.yaml
#
containerSecurityContext:
  privileged: false
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false

Cada ambiente, incluindo prod, inte cada e2e ambiente, tem uma instância dedicada do Registro de Contêiner que tem replicação global para cada uma das regiões em que os selos são implantados.

Observação

Essa implementação de referência não usa a verificação de vulnerabilidades de imagens do Docker. Recomendamos que você use o Microsoft Defender para registros de contêiner, potencialmente com GitHub Actions.

Entrada de tráfego

O Azure Front Door é o balanceador de carga global nessa arquitetura. Todas as solicitações da Web são roteadas por meio do Azure Front Door, que seleciona o back-end apropriado. Os aplicativos críticos devem aproveitar outros recursos do Azure Front Door, como WAFs (firewalls de aplicativo Web).

Firewall do aplicativo Web

Uma funcionalidade importante do Azure Front Door é o WAF, pois ele permite que o Azure Front Door inspecione o tráfego que está passando. No modo Prevenção, todas as solicitações suspeitas são bloqueadas. Na implementação, dois conjuntos de regras são configurados. Esses conjuntos de regras são Microsoft_DefaultRuleSet e Microsoft_BotManagerRuleSet.

Dica

Ao implantar o Azure Front Door com o WAF, recomendamos que você comece com o modo de detecção . Monitore de perto seu comportamento com o tráfego natural do cliente e ajuste as regras de detecção. Depois de eliminar falsos positivos, ou se os falsos positivos forem raros, alterne para o modo de prevenção . Esse processo é necessário porque cada aplicativo é diferente e algumas cargas podem ser consideradas mal-intencionadas, mesmo que sejam legítimas para essa carga de trabalho específica.

Roteamento

Somente as solicitações que vêm por meio do Azure Front Door são roteadas para os contêineres de API, como CatalogService e HealthService. Use uma configuração de entrada do Nginx para ajudar a impor esse comportamento. Ele verifica a presença de um X-Azure-FDID cabeçalho e se ele é o correto para a instância global do Azure Front Door de um ambiente específico.

#
# /src/app/charts/catalogservice/templates/ingress.yaml
#
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  # ...
  annotations:
    # To restrict traffic coming only through our Azure Front Door instance, we use a header check on the X-Azure-FDID.
    # The pipeline injects the value. Therefore, it's important to treat this ID as a sensitive value.
    nginx.ingress.kubernetes.io/modsecurity-snippet: |
      SecRuleEngine On
      SecRule &REQUEST_HEADERS:X-Azure-FDID \"@eq 0\"  \"log,deny,id:106,status:403,msg:\'Front Door ID not present\'\"
      SecRule REQUEST_HEADERS:X-Azure-FDID \"@rx ^(?!{{ .Values.azure.frontdoorid }}).*$\"  \"log,deny,id:107,status:403,msg:\'Wrong Front Door ID\'\"
  # ...

Os pipelines de implantação ajudam a garantir que esse cabeçalho seja preenchido corretamente, mas também precisa ignorar essa restrição para testes de fumaça porque eles investigam cada cluster diretamente em vez de por meio do Azure Front Door. A implementação de referência usa o fato de que os testes de fumaça são executados como parte da implantação. Esse design permite que o valor do cabeçalho seja conhecido e adicionado às solicitações HTTP de teste de fumaça.

#
# /.ado/pipelines/scripts/Run-SmokeTests.ps1
#
$header = @{
  "X-Azure-FDID" = "$frontdoorHeaderId"
  "TEST-DATA"  = "true" # Header to indicate that posted comments and ratings are for tests and can be deleted again by the app.
}

Implantações seguras

Para seguir os princípios básicos bem arquitetados para excelência operacional, automatize totalmente todas as implantações. Eles não devem exigir etapas manuais, exceto para acionar a execução ou aprovar um portão.

Você deve evitar tentativas maliciosas ou configurações incorretas acidentais que podem desativar as medidas de segurança. A implementação de referência usa o mesmo pipeline para implantação de infraestrutura e aplicativo, o que força uma reversão automatizada de qualquer possível descompasso de configuração. Essa reversão ajuda a manter a integridade da infraestrutura e o alinhamento com o código do aplicativo. Qualquer alteração é descartada na próxima implantação.

O Terraform gera valores confidenciais para implantação durante a execução do pipeline ou o Azure DevOps os fornece como segredos. Esses valores são protegidos com restrições de acesso baseadas na função.

Observação

Os fluxos de trabalho do GitHub fornecem um conceito semelhante de repositórios separados para valores secretos. Os segredos são variáveis ambientais criptografadas que o GitHub Actions pode usar.

É importante prestar atenção a todos os artefatos que o pipeline produz, pois esses artefatos podem conter valores secretos ou informações sobre o funcionamento interno do aplicativo. A implantação do Azure DevOps da implementação de referência gera dois arquivos com saídas do Terraform. Um arquivo é para selos e um arquivo é para infraestrutura global. Esses arquivos não contêm senhas que possam comprometer a infraestrutura. No entanto, você deve considerar esses arquivos como confidenciais porque eles revelam informações sobre a infraestrutura, incluindo IDs de cluster, endereços IP, nomes de conta de armazenamento, nomes do Key Vault, nomes de banco de dados do Azure Cosmos DB e IDs de cabeçalho do Azure Front Door.

Para cargas de trabalho que usam o Terraform, você precisa fazer um esforço extra para proteger o arquivo de estado, pois ele contém o contexto de implantação completo, incluindo segredos. O arquivo de estado normalmente é armazenado em uma conta de armazenamento que deve ter um ciclo de vida separado da carga de trabalho e só deve ser acessível de um pipeline de implantação. Você deve registrar qualquer outro acesso a esse arquivo e enviar alertas para o grupo de segurança apropriado.

Atualizações de dependência

Bibliotecas, estruturas e ferramentas que o aplicativo usa são atualizadas ao longo do tempo. É importante concluir essas atualizações regularmente porque elas geralmente contêm correções para problemas de segurança que podem dar aos invasores acesso não autorizado ao sistema.

A implementação de referência usa o Dependabot do GitHub para atualizações de dependência do NuGet, Docker, npm, Terraform e GitHub Actions. O dependabot.yml arquivo de configuração é gerado automaticamente com um script do PowerShell devido à complexidade das várias partes do aplicativo. Por exemplo, cada módulo do Terraform precisa de uma entrada separada.

#
# /.github/dependabot.yml
#
version: 2
updates:
- package-ecosystem: "nuget"
  directory: "/src/app/AlwaysOn.HealthService"
  schedule:
    interval: "monthly" 
  target-branch: "component-updates" 

- package-ecosystem: "docker"
  directory: "/src/app/AlwaysOn.HealthService"
  schedule:
    interval: "monthly" 
  target-branch: "component-updates" 

# ... the rest of the file...
  • As atualizações são acionadas mensalmente como um compromisso entre ter as bibliotecas mais atualizadas e manter a sobrecarga de manutenção. Além disso, ferramentas importantes como o Terraform são monitoradas continuamente e atualizações importantes são executadas manualmente.
  • As PRs (solicitações de pull) são direcionadas ao component-updates branch em vez de main.
  • As bibliotecas Npm são configuradas para verificar apenas as dependências que vão para o aplicativo compilado em vez de para ferramentas de suporte como @vue-cli.

O Dependabot cria uma PR separada para cada atualização, o que pode sobrecarregar a equipe de operações. A implementação de referência primeiro coleta um lote de atualizações no component-updates branch e, em seguida, executa testes no e2e ambiente. Se esses testes forem bem-sucedidos, ele criará outra PR direcionada ao main branch.

Codificação defensiva

As chamadas de API podem falhar devido a vários motivos, incluindo erros de código, implantações com defeito e falhas de infraestrutura. Se uma chamada de API falhar, o chamador ou o aplicativo cliente não deverá receber informações abrangentes de depuração, pois essas informações podem fornecer aos adversários pontos de dados úteis sobre o aplicativo.

A implementação de referência demonstra esse princípio retornando apenas a ID de correlação na resposta com falha. Ele não compartilha o motivo da falha, como mensagem de exceção ou rastreamento de pilha. Usando essa ID e com a ajuda do Server-Location cabeçalho, um operador pode investigar o incidente usando o Application Insights.

//
// Example ASP.NET Core middleware, which adds the Correlation ID to every API response.
//
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
   // ...

    app.Use(async (context, next) =>
    {
        context.Response.OnStarting(o =>
        {
            if (o is HttpContext ctx)
            {
                context.Response.Headers.Add("Server-Name", Environment.MachineName);
                context.Response.Headers.Add("Server-Location", sysConfig.AzureRegion);
                context.Response.Headers.Add("Correlation-ID", Activity.Current?.RootId);
                context.Response.Headers.Add("Requested-Api-Version", ctx.GetRequestedApiVersion()?.ToString());
            }
            return Task.CompletedTask;
        }, context);
        await next();
    });
    
    // ...
}

Próxima etapa

Implante a implementação de referência para obter uma compreensão completa dos recursos e sua configuração.