Statische Code-Analyse ist ein wichtiger Bestandteil der Qualitätssicherung von Software-Projekten. Durch sie können Fehler bereits frühzeitig in einer kurzen Feedback-Schleife erkannt und behoben werden. Der Quellcode wird anhand von definierten Regeln auf Fehler und potenzielle Schwachstellen hin überprüft und je nach verwendetem Tool wird direkt eine mögliche Lösung angeboten.

Mit dem aufkommen von Infrastructure As Code-Ansätzen werden nun auch zunehmend Infrastruktur-Beschreibungen in Form von Code- oder zumindest von strukturierten Textdateien hinterlegt. Damit lassen sich die Vorteile der statischen Code-Analyse auch auf Infrastruktur-Definitionen anwenden.

Analyse von Terraform-Dateien

Für die Analyse von Terraform-Definitionsdateien existiert das CLI-Tool tfsec an. Es steht unter der MIT-Lizenz als ausführbare Datei für Windows, Linux und macOS oder als Docker-Image zum Download bereit. tfsec kommt mit einem umfangreichen Set an Regeln für die großen Hyperscaler wie AWS, Azure und Google Cloud daher. Für eine selbst-gehostete Kubernetes Umgebung oder einen OpenStack-Cluster sind es allerdings bereits deutlich weniger. Zwar lassen sich über Custom Checks eigene Regel definieren, am sinnvollsten lässt sich tfsec aber vermutlich in einer managed Umgebung einsetzen.

tfsec

Im einfachsten Fall führt man tfsec im Ordner mit den Terraform-Dateien aus bzw. übergibt den Pfad als erstes Aufrufargument. tfsec durchsucht rekursiv alle Dateien mit der Endung .tf und .tfvars und gibt die Ergebnisse in der Konsole aus.

./tfsec ./terraform

Werden keine Regelverletzungen entdeckt, werden ein paar Statistiken ausgegeben und tfsec gibt Entwarnung.

  timings
  ──────────────────────────────────────────
  disk i/o             5.3983ms
  parsing              0s
  adaptation           610.2µs
  checks               3.2634ms
  total                9.2719ms

  counts
  ──────────────────────────────────────────
  modules downloaded   0
  modules processed    1
  blocks processed     11
  files read           1

  results
  ──────────────────────────────────────────
  passed               6
  ignored              0
  critical             0
  high                 0
  medium               0
  low                  0

No problems detected!

Werden Probleme festgestellt, folgen Details zur beanstandeten Datei und der genauen Position des Problems sowie Hinweise und Links zu weiteren Informationen. Der Parameter --concise-output reduziert die Ausgabe auf die wichtigsten Informationen.

> ./tfsec ./terraform --concise-output

Result #1 CRITICAL Storage account uses an insecure TLS version. 
────────────────────────────────────────────────────────────────────────────────
  ./Terraform/main.tf:55
────────────────────────────────────────────────────────────────────────────────
          ID azure-storage-use-secure-tls-policy
      Impact The TLS version being outdated and has known vulnerabilities
  Resolution Use a more recent TLS/SSL policy for the load balancer

  More Information
  - https://aquasecurity.github.io/tfsec/v1.28.1/checks/azure/storage/use-secure-tls-policy/
  - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account#min_tls_version
────────────────────────────────────────────────────────────────────────────────

  5 passed, 1 potential problem(s) detected.

In dem Beispiel bemängelt tfsec eine veraltete TLS-Version für einen Azure-Storage-Account.

Möchte man einzelne Regeln nicht zur Anwendung bringen, schließt man sie über den Parameter --exclude [RULE-ID] von der Prüfung aus. Auch lassen sich Beanstandungen einzelner Ressourcen oder Optionen innerhalb der Terraform-Definitionen per Kommentar ignorieren.

resource "azurerm_storage_account" "storage_account" {
  account_kind    = "StorageV2"
  account_tier    = "Standard"
  min_tls_version = "TLS1_1" #tfsec:ignore:azure-storage-use-secure-tls-policy
  ...
}

CI-Pipeline

Ein nützliches Feature von tfsec ist, dass es das Ergebnis einer Analyse in unterschiedlichen Formaten ausgeben kann. Über den Parameter --format wählt man die gewünschten Formate, darunter auch Markdown, HTML und JUnit. Mit --out legt man den Order (dieser muss existieren) und das Dateiprefix der Ausgabedateien fest. Das erste Format wird zusätzlich auf der Konsole ausgegeben.

Der folgende Aufruf erzeugt in dem Ordner ./terraform/tfsec-results die Dateien results.lovely, results.junit und results.markdown.

./tfsec ./terraform --concise-output \
    --format lovely,junit,markdown \
    --out ./terraform/tfsec-results/results

Die Ergebnisse lassen sich nun in einer CI-Pipeline weiterverarbeiten und der Build kann bei Regelverletzungen fehlschlagen.

Azure DevOps Pipeline

Nutzt man Azure DevOps Pipelines, kann man die JUnit-Datei als Test-Results veröffentlichen. So kann man tfsec einfach in vorhandene Test- und Build-Workflows integrieren und Fehler direkt in der Pipeline visualisieren.

Ich zeige hier für eine einfachere Adaption an andere CI-Systeme, wie man tfsec als Shell-Command nutzt. Daneben existiert auch eine Pipeline Extension im Visual Studio Marketplace und eine GitHub Action.

Zuerst lädt man das passende Binary für sein System herunter, setzt ggf. das Execute-Bit und legt den Ausgabe-Ordner an. Danach führt man tfsec aus. Möchte man das Ergebnis im Markdown-Format als Ergebnis des Pipeline-Laufs in der Weboberfläche sehen, kann man es über ein Logging-Command echo "##vso[task.uploadsummary]... hochladen.

In einem zweiten Task veröffentlicht man dann die JUnit-Datei als Test-Result.

- task: CmdLine@2
  displayName: "Run tfsec"
  inputs:
    workingDirectory: "$(System.DefaultWorkingDirectory)/Infrastructure/Terraform"
    script: |
      echo "Download tfsec v1.28.1 ..."
      wget -q https://github.com/aquasecurity/tfsec/releases/download/v1.28.1/tfsec-linux-amd64 -O tfsec
      chmod +x tfsec
      mkdir ./tfsec-results

      ./tfsec . --concise-output \
          --format lovely,junit,markdown \
          --out ./tfsec-results/results

      echo "##vso[task.uploadsummary]$(System.DefaultWorkingDirectory)/Infrastructure/Terraform/tfsec-results/results.markdown"

- task: PublishTestResults@2
  displayName: "Publish tfsec results"
  condition: succeededOrFailed()
  inputs:
    testResultsFormat: JUnit
    searchFolder: $(System.DefaultWorkingDirectory)/Infrastructure/Terraform/tfsec-results
    testResultsFiles: "results.junit"
    failTaskOnFailedTests: true
    testRunTitle: tfsec

Die tfsec-Results gehen mit in die Teststatistik ein.

Azure Pipeline Test

Im Extensions-Tab wird das Ergebnis der Markdown-Datei angezeigt.

Azure Pipeline Extensions Tab

Damit hat man eine automatische und kontinuierliche Überprüfung der Terraform-Dateien in seine CI-Pipeline integriert.