[DevSecOps] Automatizirane transformacije konfiguracijskih datoteka na lak način sa Azure DevOps

Tokom razvoja modernih aplikacija, koristimo više okolina za sami razvoj, zatim testiranje i na koncu, izvođenje aplikacije u produkciji. Takve okoline su izložene programerima, QA osoblju i krajnjim korisnicima.

Kako različite okoline koriste različite parametre, jednim dijelom i zbog sigurnosnih zahtjeva, konfiguracijske datoteke su općenito drugačije za dev, test i produkcijske okoline. Te razlike su uglavnom samo u dijelovima konfiguracijskih datoteka, odnosno ovise o okolini na koju se aplikacija postavlja.

Želimo dakle postaviti mehanizam za automatsku izmjenu konfiguracijskih segmenata, u ovisnosti o destinacijskoj okolini. Ovakvu izmjenu konfiguracijskih datoteka prilikom Build ili Deployment faze u našim isporukama nazivamo – provizioniranje. Pri tome je važno odmah napomenuti kako moramo izbjegavati spremanje osjetljivih informacija poput lozinki unutar izvornog koda, za što ćemo također ponuditi rješenje.

U ovom članku pojasnit ćemo kako koristiti DevOps pristup automatizaciji, upotrebom YAML definiranih Pipelinea kao brzo, pouzdano i sigurno rješenje sa jednostavnim održavanjem.

 

Preduvjeti za automatizaciju

 

Ovisno o tipu konfiguracijskih datoteka, one mogu biti u formatu naziv-vrijednost tekstualnog filea, JSON ili XML, ali i drugim oblicima zapisa. U svrhu pojednostavljenja, u ovom članku ćemo koristiti konfiguracijsku datoteku koja sadrži naziv-vrijednost parove za podatke pristupa (korisničko ime i lozinka).

Potrebna nam je specifikacija vrijednosti korištenih varijabli za različite okoline, primjerice:

 

Specifikacija vrijednosti varijabli

 

Primjerice, segment konfiguracijske datoteke sa našim korisničkim podacima za Dev okolinu izgledao bi ovako:

 

#Creds
Username=ekobitDev@ekobit.hr
Password=P@55w0rDev

 

Rješenje

 

Naš YAML definirani pipeline koristit će lozinke spremljene u Azure KeyVault. Možemo koristiti i više različitih KeyVaultova, ali zbog jednostavnosti ovdje ćemo koristiti samo jedan. Azure KeyVault pohranjivat će različite lozinke s oznakama destinacije, npr.: PasswordDev, PasswordTest, PasswordProd.

Ovakav pristup koristit će nam za dohvat potrebnih lozinki u nastavku.

Također, ovim pristupom osigurat ćemo dohvat samo potrebnih lozinki.

Željeni ishod nam je proizvesti konfiguracijske datoteke ovisno o odabiru vrijednosti okoline (Environment na slici dolje). Takav odabir u pipeline možemo ugraditi kao parametar:

 

parameters:
- name: Destination
  displayName: Destination
  type: string
  default: Dev
  values:
  - Dev
  - Test
  - Prod

 

Rezultat će biti ponuđeni odabir prilikom pokretanja pipelinea:

 

Run-pipeline

 

Zatim, unutar YAML datoteke, definiramo varijable , ovisne o parametru, na slijedeći način:

 

variables:
- name: System.Debug
  value: true
- name: azureKeyVault
  value: 'YourAzureKeyVaultName'
- name: password
  value: password${{Parameters.Destination}}

- ${{ if eq(parameters.Destination, 'Dev') }}:
   - name: username
     value: ekobitDev@ekobit.hr
- ${{ if eq(parameters.Destination, 'Test') }}:
   - name: username
     value: ekobitTest@ekobit.hr
- ${{ if eq(parameters.Destination, 'Prod') }}:
   - name: username
     value: ekobitProd@ekobit.hr

 

Takvim pristupom dodjeljujemo vrijednosti ovisno o okolini.

No, što s lozinkama?

Zahtjev je poštivati i sigurnosni aspekt, stoga ćemo passworde pohraniti u Azure KeyVault u obliku Secreta. Za dohvaćanje iz Azure KeyVaulta koristit ćemo AzureKeyVault task, sa YAML definicijom:

 

- task: AzureKeyVault@2
  inputs:
    azureSubscription: 'YourAzureCloudSubscription'
    KeyVaultName: $(azureKeyVault)
    SecretsFilter: Password${{Parameters.Destination}}
    RunAsPreJob: true

 

SecretsFilter varijabli dodjeljujemo izraz koji rezultira nazivom varijable pohranjene u KeyVaultu. Primjerice, ako odaberemo „Test“ okolinu pri pokretanju pipelinea, izraz će poprimiti vrijednost “PasswordTest” što čini filter za dohvat lozinke tako da samo ta lozinka biva dohvaćena.

Ako bi za SecretsFilter ostavili vrijednost “*”, bile bi dohvaćene sve vrijednosti Secreta pohranjenih u definiranom KeyVaultu. Stoga sa sigurnosnog stajališta, ne bismo trebali dohvaćati sve vrijednosti, nego samo one koje su nam potrebne.

AzureKeyVault task će rezultirati jednakim stanjem kao da smo definirali tajnu varijablu na pipeline i dodijelili joj vrijednost lozinke. Takva varijabla ne može biti prikazana, ali njezina vrijednost može biti korištena. Ako se u outputu pipelinea prikazuju vrijednosti, za tajnu varijablu se neće prikazati vrijednost nego maska poput “***”.

No, umjesto korištenja tajnih varijabli na razini pipelinea, koristit ćemo Azure KeyVault koji nudi mnoge druge mogućnosti i naširoko je korišteno oruđe u DevSecOps areni.

RunAsPreJob je za ovaj task podešen na “true”, što određuje da će se vrijednosti dohvaćenih lozinki moći koristiti kroz čitav pipeline. Ako ovu varijablu postavimo na “false”, dohvaćene lozinke bit će uporabljive samo u prvom sljedećem tasku u pipelineu.

Sada kada imamo naše varijable inicijalizirane sa vrijednostima ovisno o odredištu, trebamo ih smjestiti u konfiguracijsku datoteku. Za ovaj korak je potreban dogovor sa kolegama programerima, jer koristit ćemo task “replacetokens” za što trebamo usuglasiti “placeholdere”. YAML kod ovog taska je:

 

- task: replacetokens@3
  inputs:
    rootDirectory: '$(Build.SourcesDirectory)'
    targetFiles: 'YourRelativePathToConfig.file'
    encoding: 'utf-8'
    writeBOM: true
    verbosity: 'detailed'
    actionOnMissing: 'warn'
    keepToken: false
    tokenPrefix: '<#'
    tokenSuffix: '#>'
    useLegacyPattern: false
    enableTransforms: false
    enableTelemetry: false

 

Varijable tokenPrefix i tokenSuffix označavaju naše “placeholdere” u konfiguracijskoj datoteci. Ovaj će pristup biti ispravan dokle god je usuglašen sa programerima i ispravno definiran u YAML kodu taska i smješteni u konfiguracijsku datoteku u source kontroli.

U našem primjeru, ispravni konfiguracijski segment izgledao bi ovako:

 

#Creds
Username=<#username#>
Password=<#password#>

 

Provizioniranje će sada odraditi replacetokens task. Bit će zamijenjeni tokeni u konfiguracijskoj datoteci sa jednako nazvanim varijablama unutar našeg pipelinea. O zamjeni ćemo biti obaviješteni kroz output pipelinea, sa izvještajem popunjenih tokena i njihovih vrijednosti s iznimkom lozinki, čija će vrijednost biti maskirana.

 

Izazovi

 

Loša strana je što placeholderima popunjena konfiguracijska datoteka neće raditi na lokalnim računalima programera jer umjesto aplikativno funkcionalnih vrijednosti sadrži placeholdere. Ovo možemo prevazići korištenjem zasebnih konfiguracijskih datoteka na računalima programera, a koje bi se također čuvale na repozitoriju. S druge strane, kada koristimo pipeline, provizionirat ćemo konfiguracijsku datoteku popunjenu placeholderima.

Dodatno, mnogi će primijetiti, kako rezultirajuća konfiguracijska datoteka na kraju biva popunjena sa nemaskiranim i nekodiranim vrijednostima lozinki. Moguće pristupe rješenju i ovog problema ponudit ćemo u slijedećem blogu, tako da – stay tuned.

Posebno vrijedi istaknuti kako je replacetokens pristup ovom izazovu transparentan s obzirom na korištenu tehnologiju našeg rješenja. Neovisno o tome koristimo li Java, .Net ili neku treću tehnologiju, pipeline će odraditi svoj posao, što ga čini cross-platform rješenjem uz Azure DevOps.