RO EN

CI/CD profesionist cu GitHub Actions (5): secrete fără parole cu Managed Identity și OIDC

CI/CD profesionist cu GitHub Actions (5): secrete fără parole cu Managed Identity și OIDC ✨ Imagine generată cu AI
Doru Bulubasa
09 septembrie 2026
43 vizualizări

Am ajuns la ultima piesă din serie – și e una la care am tot făcut trimitere. În toate exemplele de până acum, pasul azure/login s-a autentificat la Azure fără nicio parolă, doar cu câteva ID-uri. E timpul să vedem cum și, mai ales, de ce ăsta e modul corect.

Sunt de fapt două întrebări diferite despre secrete:

  • Cum se autentifică GitHub Actions la Azure ca să facă deploy – fără un client secret stocat în repo?

  • Cum accesează aplicația ta resurse Azure (Key Vault, SQL, Storage) – fără connection strings cu parolă?

Răspunsul la prima e OIDC. Răspunsul la a doua e Managed Identity. Împreună, fac să dispară parolele „la vedere”.


🔓 Problema parolelor clasice

Varianta veche arăta așa: generai un client secret pentru un service principal, îl puneai în GitHub Secrets, iar connection string-urile cu parole stăteau în app settings. Funcționează, dar:

  • Secretele expiră – și într-o zi deploy-ul pică pentru că parola a expirat.

  • Trebuie rotite manual – o corvoadă pe care toată lumea o amână.

  • Pot fi scurse – un log, un screenshot, un fork greșit configurat.

  • Oricine are acces la repo sau la configurație se apropie periculos de ele.

Cel mai sigur secret e cel care nu există. Dacă nu stochezi nicio parolă pe termen lung, nu ai ce să scurgi sau să rotești.

🎫 Partea 1: GitHub → Azure prin OIDC

OIDC (OpenID Connect) înlocuiește parola cu încredere. La fiecare rulare, GitHub emite un token scurt (un JWT) care spune, verificabil, „sunt rularea X din repo-ul Y, pe environment-ul production”. Azure a fost configurat în prealabil să aibă încredere în astfel de token-uri – o relație numită federated credential.

Rezultatul: GitHub primește, doar pe durata rulării, un token de acces la Azure. Niciun client secret nu e stocat nicăieri.

Pasul 1 – creezi identitatea și încrederea federată

Într-o aplicație Entra ID, legi un federated credential de repo-ul și environment-ul tău:

az ad app create --display-name "github-myapp-deploy"

az ad app federated-credential create \
  --id <APP_ID> \
  --parameters '{
    "name": "github-prod",
    "issuer": "https://token.actions.githubusercontent.com",
    "subject": "repo:owner/myapp:environment:production",
    "audiences": ["api://AzureADTokenExchange"]
  }'

subject e cheia: leagă încrederea exact de environment-ul production din repo-ul tău. Un alt repo, alt branch sau alt environment nu se potrivește – deci nu primește token. Asta se leagă direct de approval gate-urile din partea 2.

Pasul 2 – azure/login fără client secret

În workflow, dai job-ului permisiunea de a cere un token OIDC și te loghezi doar cu ID-uri:

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
    steps:
      - uses: azure/login@v2
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}

permissions: id-token: write e obligatoriu – fără el, runner-ul nu poate cere token-ul OIDC. Iar AZURE_CLIENT_ID, AZURE_TENANT_ID și AZURE_SUBSCRIPTION_ID nu sunt parole: sunt simpli identificatori. Chiar dacă s-ar vedea, nu deblochează nimic fără încrederea federată configurată în Azure.


🪪 Partea 2: aplicația → Azure prin Managed Identity

Am rezolvat deploy-ul. Dar aplicația în sine? Ea trebuie să citească un secret din Key Vault, să se conecteze la SQL, să scrie în Storage. Varianta clasică: connection strings cu parole în app settings. Varianta corectă: Managed Identity.

O Managed Identity e o identitate gestionată de Azure, atașată App Service-ului tău. Azure se ocupă complet de ciclul ei de viață – nu există nicio parolă pe care s-o vezi sau s-o administrezi.

Activezi identitatea în Bicep

Pe web app, adaugi un bloc identity (continuând Bicep-ul din partea 3):

resource webApp 'Microsoft.Web/sites@2023-12-01' = {
  name: webAppName
  location: location
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    serverFarmId: plan.id
    httpsOnly: true
  }
}

Apoi îi dai acces cu RBAC – de exemplu rolul Key Vault Secrets User pe vault – tot din Bicep. Identitatea primește exact permisiunile de care are nevoie, nimic în plus.

În cod: DefaultAzureCredential

În aplicația .NET, folosești pachetul Azure.Identity. DefaultAzureCredential detectează automat Managed Identity în Azure (și credențialele tale locale când dezvolți), fără niciun secret în cod:

using Azure.Identity;
using Azure.Security.KeyVault.Secrets;

var client = new SecretClient(
    new Uri("https://my-vault.vault.azure.net/"),
    new DefaultAzureCredential());

KeyVaultSecret secret = await client.GetSecretAsync("DbPassword");

Pentru SQL, nici măcar nu mai ai nevoie de parolă în connection string – te conectezi cu identitatea:

Server=tcp:my-sql.database.windows.net,1433;
Database=mydb;
Authentication=Active Directory Default;

Authentication=Active Directory Default spune clientului SQL să folosească aceeași Managed Identity. Zero parole, nici în cod, nici în app settings.


🧹 De ce dispar secretele „la vedere”

Pune cap la cap cele două bucăți și rezultatul e curat:

  • În GitHub nu stochezi niciun client secret – doar ID-uri nesensibile, iar autentificarea se face cu token-uri de scurtă durată prin OIDC.

  • În app settings nu ai connection strings cu parole – aplicația se autentifică prin Managed Identity.

  • Nimic de rotit, nimic de expirat, nimic de scurs. Secretele care nu există nu pot fi furate.


✅ Câteva reguli sănătoase

  • Leagă federated credential-ul de environment (production), nu doar de repo – least privilege.

  • Folosește RBAC minim pentru Managed Identity – doar rolurile strict necesare.

  • O user-assigned managed identity poate fi reutilizată de mai multe resurse, dacă ai nevoie.

  • Păstrează în GitHub Secrets doar identificatori, niciodată parole pe termen lung.


Concluzie: unde am ajuns

Cu asta, seria e completă. Am pornit de la „am un build automat” și am ajuns la un pipeline cu adevărat profesionist pentru .NET + Azure:

  • Partea 1 – fundația: build o singură dată, teste care blochează, un artifact promovabil.

  • Partea 2 – environments cu approval gates: același artifact prin dev → staging → producție, cu aprobare umană.

  • Partea 3 – Infrastructure as Code cu Bicep: infrastructura definită în cod, versionată și reproductibilă.

  • Partea 4 – deployment slots: release-uri cu zero downtime și rollback în câteva secunde.

  • Partea 5 – secrete fără parole: OIDC pentru deploy, Managed Identity pentru aplicație.

Un pipeline profesionist nu înseamnă mai mult YAML, ci mai multe garanții: construiești o dată, promovezi controlat, definești totul în cod, livrezi fără downtime și nu mai ții nicio parolă la vedere. Acum ai harta completă – pas cu pas, de la zero la producție.