Gateway di ingresso sicuro per il componente aggiuntivo mesh del servizio Istio per il servizio Azure Kubernetes

L'articolo Distribuire l'ingresso Istio esterno o interno descrive come configurare un gateway di ingresso per esporre un servizio HTTP al traffico esterno/interno. Il presente articolo illustra come esporre un servizio HTTPS sicuro usando TLS semplice o reciproco.

Prerequisiti

Nota

Questo articolo si riferisce al gateway di ingresso esterno per la dimostrazione, si applicano gli stessi passaggi per la configurazione di TLS reciproco per il gateway di ingresso interno.

Certificati e chiavi client/server necessari

Questo articolo richiede l'impiego di vari certificati e chiavi. È possibile usare lo strumento preferito per crearli oppure utilizzare i seguenti comandi openssl.

  1. Creare un certificato radice e una chiave privata per firmare i certificati per i servizi di esempio:

    mkdir bookinfo_certs
    openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -subj '/O=bookinfo Inc./CN=bookinfo.com' -keyout bookinfo_certs/bookinfo.com.key -out bookinfo_certs/bookinfo.com.crt
    
  2. Generare un certificato e una chiave privata per productpage.bookinfo.com:

    openssl req -out bookinfo_certs/productpage.bookinfo.com.csr -newkey rsa:2048 -nodes -keyout bookinfo_certs/productpage.bookinfo.com.key -subj "/CN=productpage.bookinfo.com/O=product organization"
    openssl x509 -req -sha256 -days 365 -CA bookinfo_certs/bookinfo.com.crt -CAkey bookinfo_certs/bookinfo.com.key -set_serial 0 -in bookinfo_certs/productpage.bookinfo.com.csr -out bookinfo_certs/productpage.bookinfo.com.crt
    
  3. Generare un certificato client e una chiave privata:

    openssl req -out bookinfo_certs/client.bookinfo.com.csr -newkey rsa:2048 -nodes -keyout bookinfo_certs/client.bookinfo.com.key -subj "/CN=client.bookinfo.com/O=client organization"
    openssl x509 -req -sha256 -days 365 -CA bookinfo_certs/bookinfo.com.crt -CAkey bookinfo_certs/bookinfo.com.key -set_serial 1 -in bookinfo_certs/client.bookinfo.com.csr -out bookinfo_certs/client.bookinfo.com.crt
    

Configurare un gateway di ingresso TLS

Creare un segreto TLS Kubernetes per il gateway in ingresso; usare Azure Key Vault per ospitare certificati/chiavi e il componente aggiuntivo Provider di segreti di Azure Key Vault per sincronizzare i segreti nel cluster.

Configurare Azure Key Vault e sincronizzare i segreti nel cluster

  1. Creare un Azure Key Vault

    È necessaria una risorsa di Azure Key Vault per fornire il certificato e gli input della chiave al componente aggiuntivo Istio.

    export AKV_NAME=<azure-key-vault-resource-name>  
    az keyvault create --name $AKV_NAME --resource-group $RESOURCE_GROUP --location $LOCATION
    
  2. Abilitare il componente aggiuntivo sul cluster provider di Azure Key Vault per il driver CSI dell'archivio segreto.

    az aks enable-addons --addons azure-keyvault-secrets-provider --resource-group $RESOURCE_GROUP --name $CLUSTER
    
  3. Autorizzare l'identità gestita assegnata dall'utente del componente aggiuntivo per accedere alla risorsa di Azure Key Vault mediante i criteri di accesso. In alternativa, se Key Vault utilizza il controllo degli accessi in base al ruolo di Azure per il modello di autorizzazioni, seguire le istruzioni riportate qui per assegnare un ruolo di Azure di Key Vault per l'identità gestita assegnata dall'utente del componente aggiuntivo.

    OBJECT_ID=$(az aks show --resource-group $RESOURCE_GROUP --name $CLUSTER --query 'addonProfiles.azureKeyvaultSecretsProvider.identity.objectId' -o tsv | tr -d '\r')
    CLIENT_ID=$(az aks show --resource-group $RESOURCE_GROUP --name $CLUSTER --query 'addonProfiles.azureKeyvaultSecretsProvider.identity.clientId')
    TENANT_ID=$(az keyvault show --resource-group $RESOURCE_GROUP --name $AKV_NAME --query 'properties.tenantId')
    
    az keyvault set-policy --name $AKV_NAME --object-id $OBJECT_ID --secret-permissions get list
    
  4. Creare segreti in Azure Key Vault utilizzando i certificati e le chiavi.

    az keyvault secret set --vault-name $AKV_NAME --name test-productpage-bookinfo-key --file bookinfo_certs/productpage.bookinfo.com.key
    az keyvault secret set --vault-name $AKV_NAME --name test-productpage-bookinfo-crt --file bookinfo_certs/productpage.bookinfo.com.crt
    az keyvault secret set --vault-name $AKV_NAME --name test-bookinfo-crt --file bookinfo_certs/bookinfo.com.crt
    
  5. Usare il manifesto seguente per distribuire SecretProviderClass per fornire parametri specifici di Azure Key Vault al driver CSI.

    cat <<EOF | kubectl apply -f -
    apiVersion: secrets-store.csi.x-k8s.io/v1
    kind: SecretProviderClass
    metadata:
      name: productpage-credential-spc
      namespace: aks-istio-ingress
    spec:
      provider: azure
      secretObjects:
      - secretName: productpage-credential
        type: tls
        data:
        - objectName: test-productpage-bookinfo-key
          key: key
        - objectName: test-productpage-bookinfo-crt
          key: cert
      parameters:
        useVMManagedIdentity: "true"
        userAssignedIdentityID: $CLIENT_ID 
        keyvaultName: $AKV_NAME
        cloudName: ""
        objects:  |
          array:
            - |
              objectName: test-productpage-bookinfo-key
              objectType: secret
              objectAlias: "test-productpage-bookinfo-key"
            - |
              objectName: test-productpage-bookinfo-crt
              objectType: secret
              objectAlias: "test-productpage-bookinfo-crt"
        tenantId: $TENANT_ID
    EOF
    
  6. Utilizzare il manifesto seguente per distribuire un pod di esempio. Il driver CSI dell'archivio segreti richiede un pod per fare riferimento alla risorsa SecretProviderClass per garantire la sincronizzazione dei segreti da Azure Key Vault al cluster.

    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Pod
    metadata:
      name: secrets-store-sync-productpage
      namespace: aks-istio-ingress
    spec:
      containers:
        - name: busybox
          image: mcr.microsoft.com/oss/busybox/busybox:1.33.1
          command:
            - "/bin/sleep"
            - "10"
          volumeMounts:
          - name: secrets-store01-inline
            mountPath: "/mnt/secrets-store"
            readOnly: true
      volumes:
        - name: secrets-store01-inline
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: "productpage-credential-spc"
    EOF
    
    • Verificare il segreto productpage-credential creato nello spazio dei nomi aks-istio-ingress del cluster, come definito nella risorsa SecretProviderClass.

      kubectl describe secret/productpage-credential -n aks-istio-ingress
      

      Output di esempio:

      Name:         productpage-credential
      Namespace:    aks-istio-ingress
      Labels:       secrets-store.csi.k8s.io/managed=true
      Annotations:  <none>
      
      Type:  tls
      
      Data
      ====
      cert:  1066 bytes
      key:   1704 bytes
      

Configurare il gateway in ingresso e il servizio virtuale

Instradare il traffico HTTPS tramite il gateway di ingresso Istio alle applicazioni di esempio. Applicare il manifesto seguente per distribuire le risorse del gateway e del servizio virtuale.

cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: bookinfo-gateway
spec:
  selector:
    istio: aks-istio-ingressgateway-external
  servers:
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: productpage-credential
    hosts:
    - productpage.bookinfo.com
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: productpage-vs
spec:
  hosts:
  - productpage.bookinfo.com
  gateways:
  - bookinfo-gateway
  http:
  - match:
    - uri:
        exact: /productpage
    - uri:
        prefix: /static
    - uri:
        exact: /login
    - uri:
        exact: /logout
    - uri:
        prefix: /api/v1/products
    route:
    - destination:
        port:
          number: 9080
        host: productpage
EOF

Nota

Nella definizione del gateway, credentialName deve corrispondere a secretName nella risorsa SecretProviderClass e selector deve fare riferimento al gateway di ingresso esterno in base alla relativa etichetta, in cui la chiave dell'etichetta è istio e il valore è aks-istio-ingressgateway-external. Per l'etichetta gateway di ingresso interna è istio e il valore è aks-istio-ingressgateway-internal.

Impostare le variabili di ambiente per l'host e le porte in ingresso esterni:

export INGRESS_HOST_EXTERNAL=$(kubectl -n aks-istio-ingress get service aks-istio-ingressgateway-external -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
export SECURE_INGRESS_PORT_EXTERNAL=$(kubectl -n aks-istio-ingress get service aks-istio-ingressgateway-external -o jsonpath='{.spec.ports[?(@.name=="https")].port}')
export SECURE_GATEWAY_URL_EXTERNAL=$INGRESS_HOST_EXTERNAL:$SECURE_INGRESS_PORT_EXTERNAL

echo "https://$SECURE_GATEWAY_URL_EXTERNAL/productpage"

Verifica

Inviare una richiesta HTTPS per accedere al servizio productpage tramite HTTPS:

curl -s -HHost:productpage.bookinfo.com --resolve "productpage.bookinfo.com:$SECURE_INGRESS_PORT_EXTERNAL:$INGRESS_HOST_EXTERNAL" --cacert bookinfo_certs/bookinfo.com.crt "https://productpage.bookinfo.com:$SECURE_INGRESS_PORT_EXTERNAL/productpage" | grep -o "<title>.*</title>"

Verificare che la pagina del prodotto dell'applicazione di esempio sia accessibile. L'output previsto è il seguente:

<title>Simple Bookstore App</title>

Nota

Per configurare l'accesso in ingresso HTTPS a un servizio HTTPS, ad esempio per impostare un gateway di ingresso per eseguire il pass-through SNI anziché la terminazione TLS nelle richieste in ingresso, aggiornare a PASSTHROUGH la modalità tls nella definizione del gateway. Ciò indica al gateway di passare il traffico in ingresso "così come è", senza terminare TLS.

Configurare un gateway di ingresso TLS reciproco

Estendere la definizione del gateway per supportare il TLS reciproco.

  1. Aggiornare le credenziali del gateway in ingresso eliminando il segreto corrente e creandone uno nuovo. Il server utilizza il certificato della CA per verificare i client ed è necessario usare la chiave ca.crt per conservare il certificato della CA.

    kubectl delete secretproviderclass productpage-credential-spc -n aks-istio-ingress
    kubectl delete secret/productpage-credential -n aks-istio-ingress
    kubectl delete pod/secrets-store-sync-productpage -n aks-istio-ingress
    

    Usare il manifesto seguente per ricreare SecretProviderClass con il certificato della CA.

    cat <<EOF | kubectl apply -f -
    apiVersion: secrets-store.csi.x-k8s.io/v1
    kind: SecretProviderClass
    metadata:
      name: productpage-credential-spc
      namespace: aks-istio-ingress
    spec:
      provider: azure
      secretObjects:
      - secretName: productpage-credential
        type: opaque
        data:
        - objectName: test-productpage-bookinfo-key
          key: tls.key
        - objectName: test-productpage-bookinfo-crt
          key: tls.crt
        - objectName: test-bookinfo-crt
          key: ca.crt
      parameters:
        useVMManagedIdentity: "true"
        userAssignedIdentityID: $CLIENT_ID 
        keyvaultName: $AKV_NAME
        cloudName: ""
        objects:  |
          array:
            - |
              objectName: test-productpage-bookinfo-key
              objectType: secret
              objectAlias: "test-productpage-bookinfo-key"
            - |
              objectName: test-productpage-bookinfo-crt
              objectType: secret
              objectAlias: "test-productpage-bookinfo-crt"
            - |
              objectName: test-bookinfo-crt
              objectType: secret
              objectAlias: "test-bookinfo-crt"
        tenantId: $TENANT_ID
    EOF
    

    Utilizzare il manifesto seguente per ridistribuire il pod di esempio per sincronizzare i segreti da Azure Key Vault al cluster.

    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Pod
    metadata:
      name: secrets-store-sync-productpage
      namespace: aks-istio-ingress
    spec:
      containers:
        - name: busybox
          image: registry.k8s.io/e2e-test-images/busybox:1.29-4
          command:
            - "/bin/sleep"
            - "10"
          volumeMounts:
          - name: secrets-store01-inline
            mountPath: "/mnt/secrets-store"
            readOnly: true
      volumes:
        - name: secrets-store01-inline
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: "productpage-credential-spc"
    EOF
    
    • Verificare il segreto productpage-credential creato nello spazio dei nomi aks-istio-ingress del cluster.

      kubectl describe secret/productpage-credential -n aks-istio-ingress
      

      Output di esempio:

      Name:         productpage-credential
      Namespace:    aks-istio-ingress
      Labels:       secrets-store.csi.k8s.io/managed=true
      Annotations:  <none>
      
      Type:  opaque
      
      Data
      ====
      ca.crt:   1188 bytes
      tls.crt:  1066 bytes
      tls.key:  1704 bytes
      
  2. Usare il manifesto seguente per aggiornare la definizione del gateway per impostare la modalità TLS su MUTUAL.

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.istio.io/v1beta1
    kind: Gateway
    metadata:
      name: bookinfo-gateway
    spec:
      selector:
        istio: aks-istio-ingressgateway-external # use istio default ingress gateway
      servers:
      - port:
          number: 443
          name: https
          protocol: HTTPS
        tls:
          mode: MUTUAL
          credentialName: productpage-credential # must be the same as secret
        hosts:
        - productpage.bookinfo.com
    EOF
    

Verifica

Tentare di inviare una richiesta HTTPS usando l'approccio precedente, senza passare il certificato client, e verificarne l'esito negativo.

curl -v -HHost:productpage.bookinfo.com --resolve "productpage.bookinfo.com:$SECURE_INGRESS_PORT_EXTERNAL:$INGRESS_HOST_EXTERNAL" --cacert bookinfo_certs/bookinfo.com.crt "https://productpage.bookinfo.com:$SECURE_INGRESS_PORT_EXTERNAL/productpage" 

Output di esempio:


...
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS alert, unknown (628):
* OpenSSL SSL_read: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0
* Failed receiving HTTP2 data
* OpenSSL SSL_write: SSL_ERROR_ZERO_RETURN, errno 0
* Failed sending HTTP2 data
* Connection #0 to host productpage.bookinfo.com left intact
curl: (56) OpenSSL SSL_read: error:0A00045C:SSL routines::tlsv13 alert certificate required, errno 0

Passare il certificato del client con il flag --cert e la chiave privata con il flag --key a curl.

curl -s -HHost:productpage.bookinfo.com --resolve "productpage.bookinfo.com:$SECURE_INGRESS_PORT_EXTERNAL:$INGRESS_HOST_EXTERNAL" --cacert bookinfo_certs/bookinfo.com.crt --cert bookinfo_certs/client.bookinfo.com.crt --key bookinfo_certs/client.bookinfo.com.key "https://productpage.bookinfo.com:$SECURE_INGRESS_PORT_EXTERNAL/productpage" | grep -o "<title>.*</title>"

Verificare che la pagina del prodotto dell'applicazione di esempio sia accessibile. L'output previsto è il seguente:

<title>Simple Bookstore App</title>

Eliminare risorse

Se si vuole pulire la mesh del servizio Istio e i dati in ingresso (lasciando il cluster), eseguire il comando seguente:

az aks mesh disable --resource-group ${RESOURCE_GROUP} --name ${CLUSTER}

Se si desidera pulire tutte le risorse create dai documenti di istruzioni di Istio, eseguire il comando seguente:

az group delete --name ${RESOURCE_GROUP} --yes --no-wait