> ## Documentation Index
> Fetch the complete documentation index at: https://www.truefoundry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Add TLS Certificates

> Configure secure HTTPS access to your TrueFoundry deployment

## Adding TLS Certificates to Your TrueFoundry Deployment

This guide explains how to configure TLS certificates to enable HTTPS access to your TrueFoundry deployments. We'll cover multiple approaches based on your cloud and DNS provider.

<Tabs>
  <Tab title="GCP">
    When running TrueFoundry on GCP, you have the following options for TLS termintation

    <Accordion title="Terminate TLS by using cert-manager with GCP Cloud DNS (recommended)">
      For production GCP deployments, we recommend using cert-manager with GCP Cloud DNS. This helps in ensuring that the domain you are using for TrueFoundry's deployment is separate from your main domain. It also helps in managing the certificates and renewals automatically. If you want to use your own DNS provider with cert-manager, you can follow the instructions [here](https://cert-manager.io/docs/).

      ### Creating the certificate using cert-manager with GCP Cloud DNS

      This approach requires [workload identity](https://cloud.google.com/kubernetes-engine/docs/concepts/workload-identity) to be enabled on your GKE cluster. For clusters created through OpenTofu/Terraform code generated by TrueFoundry, this is enabled by default.

      #### Step 1: Configure GCP Service Account and Permissions

      <CodeGroup>
        ```shell Shell lines theme={"dark"}
        # Set your variables
        export CLUSTER_NAME="<your-gke-cluster>"
        export CLUSTER_LOCATION="<your-cluster-zone>" #(us-central1)
        export DNS_ROLE_NAME="<cert_manager_dns_role>" # role name for cert-manager to use in GCP 
        export GCP_SERVICEACCOUNT_NAME="cert-manager-dns"
        export MAIL_ID="<your-email@example.com>"
        export PROJECT_ID=$(gcloud config get-value project) # set your project ID here
        export LOAD_BALANCERIP=$(kubectl get svc -n istio-system tfy-istio-ingress -ojsonpath='{.status.loadBalancer.ingress[0].ip}')

        # Verify values
        echo "PROJECT_ID: ${PROJECT_ID}"
        echo "CLUSTER_NAME: ${CLUSTER_NAME}"
        echo "CLUSTER_LOCATION: ${CLUSTER_LOCATION}"
        echo "LOAD_BALANCERIP: ${LOAD_BALANCERIP}"

        # Set project
        gcloud config set project $PROJECT_ID

        # Create service account
        gcloud iam service-accounts create $GCP_SERVICEACCOUNT_NAME --display-name "$GCP_SERVICEACCOUNT_NAME"

        # Create custom role with minimal DNS permissions
        gcloud iam roles create custom_dns_role --project=$PROJECT_ID --title=$DNS_ROLE_NAME \
          --permissions=dns.resourceRecordSets.create,dns.resourceRecordSets.delete,dns.resourceRecordSets.get,dns.resourceRecordSets.list,dns.resourceRecordSets.update,dns.changes.create,dns.changes.get,dns.changes.list,dns.managedZones.list

        # Bind role to service account
        gcloud projects add-iam-policy-binding $PROJECT_ID \
          --condition=None \
          --member=serviceAccount:$GCP_SERVICEACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com \
          --role="projects/$PROJECT_ID/roles/custom_dns_role"

        # Allow cert-manager to use the service account through workload identity
        gcloud iam service-accounts add-iam-policy-binding --role roles/iam.workloadIdentityUser \
          --member "serviceAccount:$PROJECT_ID.svc.id.goog[cert-manager/cert-manager]" \
          $GCP_SERVICEACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com
        ```
      </CodeGroup>

      #### Step 2: Configure DNS for Your Domain

      If you already have a DNS zone in GCP, you can skip the step of creating the DNS zone and simply assign the variables `DNS_HOSTED_ZONE` and `DNS_ZONE_PROJECT_ID` with the name of your DNS zone and the project ID respectively.

      <CodeGroup>
        ```shell Shell lines theme={"dark"}
        # Set DNS zone name
        export DNS_HOSTED_ZONE="tfy.example.com"
        export DNS_ZONE_PROJECT_ID="${PROJECT_ID}"

        # Create DNS zone (if it doesn't exist)
        gcloud dns managed-zones create example-com --project=${DNS_ZONE_PROJECT_ID} \
          --description="DNS zone for ${DNS_HOSTED_ZONE}" \
          --dns-name=${DNS_HOSTED_ZONE} \
          --visibility=public
          
        # Get nameservers
        gcloud dns managed-zones describe example-com \
          --format="json" | jq -r '.nameServers[]'
        ```
      </CodeGroup>

      Add the nameservers from the output to your domain registrar (GoDaddy, Namecheap, etc.) so that this DNS zone is now used for your domain.

      ### Step 3: Install and Configure cert-manager

      From the platform, head over to the Cluster's page and ensure that you have `cert-manager` addon installed. Ensure that you are passing the google serviceaccount email to the cert-manager serviceaccount annotations in the values section.

      <CodeGroup>
        ```yaml YAML lines theme={"dark"}
        cert-manager:
          serviceAccount:
            annotations:
              iam.gke.io/gcp-service-account: $GCP_SERVICEACCOUNT_NAME@$PROJECT_ID.iam.gserviceaccount.com
        ```
      </CodeGroup>

      ### Step 4: Create Certificate Issuer and Request Certificate

      1. Get your cluster credentials:
               <CodeGroup>
                 ```shell Shell lines theme={"dark"}
                 gcloud container clusters get-credentials $CLUSTER_NAME --zone $CLUSTER_LOCATION --project $PROJECT_ID
                 ```
               </CodeGroup>

      2. Create an issuer for Let's Encrypt.

               <CodeGroup>
                 ```yaml YAML lines theme={"dark"}
                 kubectl apply -f - <<EOF
                 apiVersion: cert-manager.io/v1
                 kind: Issuer
                 metadata:
                   name: letsencrypt-issuer
                   namespace: istio-system
                 spec:
                   acme:
                     email: $MAIL_ID
                     server: https://acme-v02.api.letsencrypt.org/directory
                     privateKeySecretRef:
                       name: letsencrypt-key
                     solvers:
                     - dns01:
                         cloudDNS:
                           project: $DNS_ZONE_PROJECT_ID
                 EOF
                 ```
               </CodeGroup>

      3. Request a certificate. You can replace the `.metadata.name` of the certificate and the `.spec.secretName` accordingly. Ensure that you have also replaced the `.spec.issuerRef.name` with the name of the issuer you created in the previous step and the `.spec.dnsNames` with your chosen domain name. Here we are using `example.com` and `*.example.com` as the domain names.

               <CodeGroup>
                 ```yaml YAML lines theme={"dark"}
                 kubectl apply -f - <<EOF
                 apiVersion: cert-manager.io/v1
                 kind: Certificate
                 metadata:
                   name: example-com-cert
                   namespace: istio-system
                 spec:
                   secretName: example-com-tls
                   duration: 2160h # 90d
                   renewBefore: 360h # 15d
                   issuerRef:
                     name: letsencrypt-issuer
                   dnsNames:
                   - "tfy.example.com"
                   - "*.tfy.example.com"
                 EOF
                 ```
               </CodeGroup>

      4. Check certificate status:

               <CodeGroup>
                 ```shell Shell lines theme={"dark"}
                 kubectl get certificates -n istio-system
                 ```
               </CodeGroup>

               <Warning>
                 ### **Troubleshooting**

                 If the certificate remains in a non-ready state for more than 10 minutes, check the cert-manager logs:

                 ```shell Shell lines theme={"dark"}
                 kubectl logs -n cert-manager -l app=cert-manager
                 ```
               </Warning>

      ### Step 5: Configure TLS in Your Ingress Gateway

      Once the certificate is created, head over to the cluster's page and navigate to the `tfy-istio-ingress` addon. Add the secret name in the `tfyGateway.spec.servers[1].tls.credentialName` section and ensure that `tfyGateway.spec.servers[1].port.protocol` is set to `HTTPS`. Here we are using `example-com-tls` as the secret name which contains the certificate and key.

      <CodeGroup>
        ```yaml YAML lines theme={"dark"}
            servers:
              - <REDACTED>
              - hosts:
                  - "*"
                port:
                  name: https-tfy-wildcard
                  number: 443
                  protocol: HTTPS
                tls:
                  mode: SIMPLE
                  credentialName: example-com-tls
        ```
      </CodeGroup>
    </Accordion>

    <Accordion title="Terminate TLS by using cert-manager with your DNS provider (e.g. Route 53, cloudflare, godaddy, etc.)">
      Other way to handle TLS termination is to use cert-manager with your DNS provider. This is useful if you don't want to use GCP Cloud DNS.
      For this you can follow the instructions [here](https://cert-manager.io/docs/getting-started/).
    </Accordion>
  </Tab>

  <Tab title="Azure">
    When running TrueFoundry on Azure, you have the following options for TLS termintation

    <Accordion title="Terminate TLS by using cert-manager with Azure DNS zone (recommended)">
      For production Azure deployments, we recommend using cert-manager with Azure DNS. This helps in ensuring that the domain you are using for TrueFoundry's deployment is separate from your main domain. It also helps in managing the certificates and renewals automatically. If you want to use your own DNS provider with cert-manager, you can follow the instructions [here](https://cert-manager.io/docs/).

      ### Creating the certificate using cert-manager with Azure DNS zone

      This approach requires [workload identity](https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview?tabs=dotnet) to be enabled on your AKS cluster. For clusters created through OpenTofu/Terraform code generated by TrueFoundry, this is enabled by default.

      #### Step 1: Configure user identity and permissions

      <CodeGroup>
        ```shell Shell lines theme={"dark"}
        # Set your variables
        export CLUSTER_NAME="<your-aks-cluster>"
        export RESOURCE_GROUP="<your-resource-group>"
        export AZURE_SUBSCRIPTION_ID="<your-subscription-id>"
        export SERVICE_ACCOUNT_NAME=cert-manager
        export SERVICE_ACCOUNT_NAMESPACE=cert-manager
        export MAIL_ID="<your-email@example.com>"

        # Get OIDC issuer URL and load balancer IP
        export OIDC_ISSUER_URL=$(az aks show --resource-group $RESOURCE_GROUP --name $CLUSTER_NAME --query "oidcIssuerProfile.issuerUrl" -o tsv)

        export LOAD_BALANCERIP=$(kubectl get svc -n istio-system tfy-istio-ingress -ojsonpath='{.status.loadBalancer.ingress[0].ip}')

        # Create and get managed identity
        export IDENTITY_NAME="$CLUSTER_NAME"

        # Create managed identity and get IDs
        PRINCIPAL_ID=$(az identity create --name "${IDENTITY_NAME}" --resource-group "${RESOURCE_GROUP}" --query principalId -otsv)

        IDENTITY_CLIENT_ID=$(az identity show --name "${IDENTITY_NAME}" --resource-group "${RESOURCE_GROUP}" --query 'clientId' -otsv)

        # Verify values
        echo "PRINCIPAL_ID: ${PRINCIPAL_ID}"
        echo "IDENTITY_CLIENT_ID: ${IDENTITY_CLIENT_ID}"
        echo "OIDC_ISSUER_URL: ${OIDC_ISSUER_URL}"
        echo "LOAD_BALANCERIP: ${LOAD_BALANCERIP}"
        ```
      </CodeGroup>

      #### Step 2: Configure DNS Zone and Permissions

      If you already have a DNS zone in Azure, you can skip the step of creating the DNS zone and simply assign the variables `DNS_HOSTED_ZONE` and `DNS_ZONE_RESOURCE_GROUP` with the name of your DNS zone and the resource group respectively. Run the remaining commands to assign permissions and set up federated credentials.

      <CodeGroup>
        ```bash Shell lines theme={"dark"}
        # Set DNS zone name
        export DNS_HOSTED_ZONE="tfy.example.com"
        export DNS_ZONE_RESOURCE_GROUP="${RESOURCE_GROUP}"

        # Create DNS zone
        az network dns zone create --name ${DNS_HOSTED_ZONE} --resource-group ${DNS_ZONE_RESOURCE_GROUP} --query nameServers

        # Get the DNS zone ID
        DNS_ZONE_ID=$(az network dns zone show --name ${DNS_HOSTED_ZONE} --resource-group ${DNS_ZONE_RESOURCE_GROUP} --query id -otsv)

        # Assign permissions
        az role assignment create --assignee $PRINCIPAL_ID --role "DNS Zone Contributor" --scope $DNS_ZONE_ID

        # Set up federated credentials
        az identity federated-credential create --name "cert-manager" --identity-name "${IDENTITY_NAME}" --issuer "${OIDC_ISSUER_URL}" --resource-group "${RESOURCE_GROUP}" --subject "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}"
        ```
      </CodeGroup>

      Add the nameservers from the output to your domain registrar (GoDaddy, Namecheap, etc.) so that this DNS zone is now used for your domain.

      #### Step 3: Install and Configure cert-manager

      From the platform, head over to the Cluster's page and ensure that you have `cert-manager` addon installed. Ensure that you are passing the right pod and serviceAccount labels for cert-manager to use workload identity. These will be present if cert-manager is installed through TrueFoundry's Addon/Helm section.

      <CodeGroup>
        ```yaml YAML lines theme={"dark"}
        installCRDs: true
        extraArgs:
          - --issuer-ambient-credentials
        podLabels:
          azure.workload.identity/use: "true"
        serviceAccount:
          labels:
            azure.workload.identity/use: "true"
        ```
      </CodeGroup>

      #### Step 4: Create Certificate Issuer and Request Certificate

      1. Get your cluster credentials:

               <CodeGroup>
                 ```shell Shell lines theme={"dark"}
                 az aks get-credentials --name $CLUSTER_NAME --resource-group $RESOURCE_GROUP
                 ```
               </CodeGroup>

      2. Create an issuer.

               <CodeGroup>
                 ```yaml YAML lines theme={"dark"}
                 kubectl apply -f - <<EOF
                 apiVersion: cert-manager.io/v1
                 kind: Issuer
                 metadata:
                   name: azure-issuer
                   namespace: istio-system
                 spec:
                   acme:
                     email: $MAIL_ID
                     server: https://acme-v02.api.letsencrypt.org/directory
                     privateKeySecretRef:
                       name: azure-issuer-key
                     solvers:
                     - dns01:
                         azureDNS:
                           hostedZoneName: $DNS_HOSTED_ZONE
                           resourceGroupName: $DNS_ZONE_PROJECT_ID
                           subscriptionID: $AZURE_SUBSCRIPTION_ID
                           environment: AzurePublicCloud
                           managedIdentity:
                             clientID: $IDENTITY_CLIENT_ID
                 EOF
                 ```
               </CodeGroup>

      3. Request a certificate. You can replace the `.metadata.name` of the certificate and the `.spec.secretName` accordingly. Ensure that you have also replaced the `.spec.issuerRef.name` with the name of the issuer you created in the previous step and the `.spec.dnsNames` with your chosen domain name. Here we are using `example.com` and `*.example.com` as the domain names.

               <CodeGroup>
                 ```yaml YAML lines theme={"dark"}
                 kubectl apply -f - <<EOF
                 apiVersion: cert-manager.io/v1
                 kind: Certificate
                 metadata:
                   name: example-cert
                   namespace: istio-system
                 spec:
                   secretName: example-com-tls
                   duration: 2160h # 90d
                   renewBefore: 360h # 15d
                   issuerRef:
                     name: azure-issuer
                   dnsNames:
                   - "example.com"
                   - "*.example.com"
                 EOF
                 ```
               </CodeGroup>

      4. Check certificate status:

               <CodeGroup>
                 ```shell Shell lines theme={"dark"}
                 kubectl get certificates -n istio-system
                 ```
               </CodeGroup>

               <Warning>
                 ### **Troubleshooting**

                 If the certificate remains in a non-ready state for more than 10 minutes, check the cert-manager logs:

                 ```shell Shell lines theme={"dark"}
                 kubectl logs -n cert-manager -l app=cert-manager
                 ```
               </Warning>

      ### Step 5: Configure TLS in Your Ingress Gateway

      Once the certificate is created, head over to the cluster's page and navigate to the `tfy-istio-ingress` addon. Add the secret name in the `tfyGateway.spec.servers[1].tls.credentialName` section and ensure that `tfyGateway.spec.servers[1].port.protocol` is set to `HTTPS`. Here we are using `example-com-tls` as the secret name which contains the certificate and key.

      <CodeGroup>
        ```yaml YAML lines theme={"dark"}
            servers:
              - <REDACTED>
              - hosts:
                  - "*"
                port:
                  name: https-tfy-wildcard
                  number: 443
                  protocol: HTTPS
                tls:
                  mode: SIMPLE
                  credentialName: example-com-tls
        ```
      </CodeGroup>
    </Accordion>

    <Accordion title="Terminate TLS by using cert-manager with your DNS provider (e.g. Route 53, cloudflare, godaddy, etc.)">
      Other way to handle TLS termination is to use cert-manager with your DNS provider. This is useful if you don't want to use Azure DNS.
      For this you can follow the instructions [here](https://cert-manager.io/docs/getting-started/).
    </Accordion>
  </Tab>
</Tabs>

***
