Containerized Uygulamaların Supply Chain’ini Güvence Altına Alarak Güvenlik Risklerini Azaltma (OPA Gatekeeper ve Ratify ile Otomatikleştirilmiş Politika Uygulanması) – Bölüm 2

Makalenin ilk bölümünde, Software Supply Chain güvenliğinin öneminden ve containerized uygulamaların güvenlik risklerini azaltabilmek için SDLC süreçleri boyunca alınabilecek önlemlerden bahsetmiştik. Bu kapsamda, shifting-left yaklaşımının öneminden, yani çeşitli güvenlik unsurlarının mümkün olduğunca SDLC süreçlerinin erken aşamalarına entegre edilmesinin faydalarından ve software supply chain içerisinde güvenilirlik, bütünlük ve güvenlik sağlamak amacıyla alabileceğimiz önlemlerden bahsetmiştik.

Eğer makalenin ilk bölümünü henüz okumadıysanız, konunun bütünlüğü açısından öncelikle buradan ilk bölümü okumanızı tavsiye ederim.

Makalenin bu bölümünde ise kubernetes ortamında OPA Gatekeeper ile Ratify kullanarak, çeşitli politikaları declarative bir şekilde nasıl tanımlayabileceğimizi, containerized uygulamaların dağıtılma işlemleri sırasında bu politikaları nasıl zorunlu kılabileceğimizi ve otomatikleştirilmiş kararlarları nasıl alabileceğimizi ele alacağız.

NOT: Bu bölümden itibaren v1.20+ bir kubernetes ortamına sahip olduğumuzu varsayacağım.

OPA Gatekeeper & Ratify

Open Policy Agent (OPA) ‘ı daha önce hiç duymadıysanız, çeşitli politikaları Rego isimli declarative query dil’i ile code olarak tanımlayabilmemize olanak sağlayan, genel amaçlı open-source bir politika engine’idir diyebiliriz. Daha fazla detay için ise, buraya bir göz atmanızı şiddetle tavsiye ederim. Gatekeeper için ise OPA constraint framework’ünü kullanarak çeşitli politikaları kubernetes özelinde ConstraintTemplate ‘ler olarak tanımlayabilmemizi ve bu politikaları zorunlu kılabilmemizi sağlayan bir admission controller webhook ‘udur diyebiliriz.

https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/

Gatekeeper ile containerized uygulamaları kubernetes ortamında fonksiyonel bir hale getirmeden önce, bir başka değişle admission request’i API server tarafında intercept ederek, tanımlanmış olan politikalara uygunluklarını uyumluluk için kontrol edebilir, bu doğrultuda zorlayabilir ve böylece software supply chain güvenliğini ve governance modelini infrastructure seviyesinde de güçlendirebilmekteyiz.

Ratify ise container security supply chain alanında supply chain artifact’lerini doğrulayabilmemize yardımcı olan bir başka open source bir projedir. Ratify aslında her ortamda çalışabilecek bir executable olsa da, kubernetes özelinde baktığımızda Gatekeeper için external bir data provider olarak hareket etmekte ve container image’leri ile ilişkilendirmiş olduğumuz OCI artifact metadata’larına karşı politikalar tanımlayabilmemize ve onların doğrulanabilmesine olanak tanımaktadır.

Aksiyona Geçelim!

Gatekeeper’ı Hazırlayalım

Makalenin ilk bölümünde oluşturmuş olduğumuz software supply chain artifact’lerini ve imzalarını, dağıtılma öncesi en basit yoldan pipeline üzerinde nasıl doğrulayabileceğimizi ORAS ve Notation CLI araçları ile birlikte ele almıştık. Ayrıca ilgili artifact’leri oluştururken makalenin bu bölümünde OPA Gatekeeper ve Ratify ‘dan yararlanabilmek için, ilgili artifact’leri belirli formatlarda da oluşturmuştuk.

Örneğin container güvenlik taraması sonucunu SARIF formatında, SBOM dokümanını ise SPDX formatında ve Notation ile imzalamış olduğumuz artifact’leri ise COSE formatında oluşturmuştuk. Şimdi bu artifact’ler özelinde Ratify ‘ın doğrulayıcı plugin’lerinden yararlanarak daha spesifik kontrol politikalarını declarative bir şekilde nasıl konfigure edip, zorunlu kılabileceğimize bir bakalım.

Öncelikle Ratify ‘ın doğrulama işlemini gerçekleştirip Gatekeeper ‘a geri bildirim verebilmesi için, ilgili container image’ i ile ilişkilendirmiş olduğumuz supply chain artifact’lerine ilgili container registry üzerinden erişebilmesi gerekmektedir. Ratify arka planda ise bunun için ORAS aracından yararlanmaktadır. Bu makale serisi kapsamında ise container registry olarak Azure Container Registry (ACR) kullanmıştık. Bu noktada Ratify, ORAS ‘ın ilgili registry’e erişebilmesi için bizlere bir kaç farklı auth provider seçeneği sunmaktadır. Ben ilgili AKS cluster’ını (v1.20+) ACR entegrasyonu ile oluşturduğum için, bu noktada kubelet identity ‘sini (managed identity) kullanıyor olacağım. Dilerseniz kendi oluşturacağınız user-assigned managed identity’i veya workload identity seçeneklerini de kullanabilirsiniz. Bunun ilgili seçeneklere ise buradan erişebilirsiniz.

Öncelikle Gatekeeper ‘ı external data özelliği ile ilgili kubernetes ortamına kurulumunu gerçekleştirmemiz gerekmektedir.

Bu noktada hızlı ilerleyebilmek için, Gatekeeper ve Ratify araçlarının kurulum işlemlerini bir önceki makale kapsamında kullanmış olduğumuz CICD template’i içerisinde gerçekleştireceğim. Ayrıca imzalanmış artifact’lerin politikalar aracılığı ile doğrulanabilmesi konusunda Ratify ‘ın built-in plugin’i olan Notation ‘ı da kullanabilmemiz için, imzalama süreçleri sırasında Notation aracı ile oluşturuyor olduğumuz CA certificate’ini, kurulum işlemi sırasında Ratify ‘a sağlıyor olmamız gerekmektedir. Eğer certificate oluşturma işlemlerini CI boyunca on-the-fly gerçekleştirmek yerine Azure Key Vault gibi merkezi bir noktada gerçekleştiriyor olsaydık, ozaman kurulum işlemlerini farklı noktalarda da ele alabilirdik.

Şimdi daha önce “Dev” adında oluşturmuş olduğumuz stage’in içerisinde bulunan “VerifyArtifacts” job’ından sonra aşağıdaki şekilde “DeployToDev“ adında yeni bir job ekleyelim. Bu job içerisinde de ilk aşama olarak, ilgili artifact’lerin “SigningStage“ içerisinde imzalama işlemlerinin gerçekleşmesinin ardından pipeline artifact’i olarak paylaşıyor olduğumuz Notation certificate’ini, oluşturmuş olduğumuz bu yeni job içerisine download edeceğiz.

  - job: DeployToDev
    displayName: 'Deploy to Dev'
    dependsOn: VerifyArtifacts
    steps:
    - task: DownloadPipelineArtifact@2
      inputs:
        buildType: 'current'
        artifactName: 'notation'
        downloadPath: '$(Agent.BuildDirectory)/../../.config/notation/localkeys'
    - task: AzureCLI@2
      displayName: 'Prepare OPA Gatekeeper & Ratify'
      inputs:
        azureSubscription: 'DevOpsPoC'
        scriptType: 'bash'
        scriptLocation: 'inlineScript'
        inlineScript: |
          curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

          az aks get-credentials --resource-group YOUR_AKS_RG --name YOUR_AKS_NAME

          helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts

          helm upgrade --install gatekeeper gatekeeper/gatekeeper --atomic  \
              --namespace gatekeeper-system --create-namespace \
              --set enableExternalData=true \
              --set validatingWebhookTimeoutSeconds=5 \
              --set mutatingWebhookTimeoutSeconds=2 \
              --set externaldataProviderResponseCacheTTL=10s

Ardından Gatekeeper ‘in kurulum işlemini ilgili AKS cluster’ına Helm vasıtasıyla gerçekleştiriyoruz. Gatekeeper kurulumunun ardından ise Ratify ‘ın kurulumunu kullanmak istediğimiz doğrulayıcı plugin’leri ile birlikte gerçekleştirmeye başlayabiliriz.

Ratify

Notation ile İmzaların Doğrulanması

Şimdi ilgili task’ın devamı olarak, aşağıdaki gibi Ratify helm chart’ını konfigure edelim ve Gatekeeper ile aynı namespace içerisine kurulumunun gerçekleştirilmesini sağlayalım.

          helm repo add ratify https://deislabs.github.io/ratify

          helm upgrade --install ratify ratify/ratify --version 1.12.1 \
              --namespace gatekeeper-system \
              --set featureFlags.RATIFY_CERT_ROTATION=true \
              --set logger.level=debug \
              --set-file notationCerts={$(Agent.BuildDirectory)/../../.config/notation/localkeys/order-api.io.crt} \
              --set oras.authProviders.azureManagedIdentityEnabled=true \
              --set azureManagedIdentity.clientId=\"YOUR_CLIENT_ID\" \
              --set azureManagedIdentity.tenantId="YOUR_TENANT_ID"

          kubectl apply -f https://deislabs.github.io/ratify/library/default/template.yaml
          kubectl apply -f https://deislabs.github.io/ratify/library/default/samples/constraint.yaml

Bu noktada Ratify ‘a, farklı doğrulama işlemlerinde kullanabilmesi için ilgili artifact metadata’larına ACR üzerinden erişebilmesi amacıyla sağlamış olduğumuz Azure Managed Identity bilgilerini kullanmasını söylüyoruz. Ayrıca, Notation, daha önce de bahsettiğimiz gibi Ratify içerisinde varsayılan imza doğrulayıcısı olarak geldiği için, “notationCerts” parametresi ile imzalama işlemlerinde kullanmış olduğumuz CA certificate’ini sağlıyoruz. Böylece Ratify, kubernetes içerisinde sadece imzalanmış container image’lerini çalıştırmamıza olanak sağlayacaktır.

Makalenin girişinde Gatekeeper ‘ı tanımlarken, OPA constraint framework’ünü kullanarak çeşitli politikaları kubernetes özelinde ConstraintTemplate ‘ler olarak yani CRD-based olarak tanımlayabilmemizi ve bu politikaları zorunlu kılabilmemizi sağlamaktadır demiştik.

OPA Constraint ve ConstraintTemplate konseptine kısaca değinmek gerekirse, ConstraintTemplate, kısıtlamaları (constraint) uygulamak için kullanılacak olan politika mantığının ve kısıtlamanın schema’sının tanımlandığı ConstraintTemplate tipinde bir CRD ‘dir. Ayrıca Rego politikalarını da konfigürasyon olarak tanımladığımız yerdir. Tanımlanmış olan bu politikanın uygulanabilmesi için ise, bir instance’ının alınması gerekmektedir. Bu işlemi ise, CRD-based Constraint ‘ler tanımlayarak gerçekleştirmekteyiz.

Şimdi yukarıdaki kod bloğunda kubectl ile uyguluyor olduğumuz temel bir OPA Gatekeeper ConstraintTemplate ‘ine (template.yaml) bir bakalım.

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: ratifyverification
spec:
  crd:
    spec:
      names:
        kind: RatifyVerification
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package ratifyverification
        
        # Get data from Ratify
        remote_data := response {
          images := [img | img = input.review.object.spec.containers[_].image]
          response := external_data({"provider": "ratify-provider", "keys": images})
        }

        # Base Gatekeeper violation
        violation[{"msg": msg}] {
          general_violation[{"result": msg}]
        }
        
        # Check if there are any system errors
        general_violation[{"result": result}] {
          err := remote_data.system_error
          err != ""
          result := sprintf("System error calling external data provider: %s", [err])
        }
        
        # Check if there are errors for any of the images
        general_violation[{"result": result}] {
          count(remote_data.errors) > 0
          result := sprintf("Error validating one or more images: %s", remote_data.errors)
        }
        
        # Check if the success criteria is true
        general_violation[{"result": result}] {
          subject_validation := remote_data.responses[_]
          subject_validation[1].isSuccess == false
          result := sprintf("Subject failed verification: %s", [subject_validation[0]])
        }

Bu noktada ConstraintTemplate, içerisinde Rego dilinde uygulanacak olan politika mantığının bulunduğu RatifyVerification tipinde bir CRD schema’sı oluşturmaktadır.

Bu noktada özellikle aşağıdaki satır’a dikkat edersek,

response := external_data({"provider": "ratify-provider", "keys": images})
        }

Gatekeeper ‘ın container image’ini doğrulayabilmesi için external data sağlayıcısı olarak Ratify ‘ı kullandığını görebiliriz.

Şimdi yine kubectl ile uyguluyor olduğumuz Constraint ‘e (constraint.yaml) bir bakalım.

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: RatifyVerification
metadata:
  name: ratify-constraint
spec:
  enforcementAction: deny
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaces: ["default"]

Bahsettiğimiz gibi, oluşturulan bu Constraint, az önce tanımlamış olduğumuz ConstraintTemplate ‘in bir nevi instance ‘ı görevini görmektedir. Bu Constraint içerisinde ise, ConstraintTemplate ‘de yer alan ilgili politika mantığının, “default” namespace’i altındaki tüm “Pod” lara uygulanması gerektiğini belirtiyoruz.

Bu noktadan itibaren, Notation, Ratify içerisinde built-in bir doğrulayıcı olarak geldiği ve Ratify ‘ın kurulumunu gerçekleştirirken imzalama sırasında kullandığımız CA certificate’ini de ilettiğimiz için, kubernetes içerisinde “default” namespace altında sadece bu certificate ile imzalanmış olan container image’lerinin çalıştırılmasına izin verilecektir.

Test etmeye geçmeden önce built-in gelen Ratify doğrulayıcılarına kubernetes tarafında baktığımızda ise, aşağıdaki gibi Notation ve Cosign ‘ın varsayılan olarak geldiğini görebiliriz.

Notation doğrulayıcısına baktığımızda ise, container ile ilişkilendirilmiş “application/vnd.cncf.notary.signature” tipindeki artifact metadata’ları ile çalıştığını da görebiliriz.

Şimdi ilgili politikayı test edebilmek için aşağıdaki task’ı kullanarak imzalanmamış örnek bir container image’ini, ilgili kubernetes ortamına yayımlamayı deneyelim.

    - task: AzureCLI@2
      displayName: 'Deploy the $(orderAPIImageName) container'
      inputs:
        azureSubscription: 'DevOpsPoC'
        scriptType: 'bash'
        scriptLocation: 'inlineScript'
        inlineScript: |
          cat <

Gördüğümüz gibi, yayımlama işlemi, ilgili container image’inin oluşturmuş olduğumuz CA certificate’i tarafından imzalanmamış olmasından dolayı, Gatekeeper ‘ın Ratify ‘dan aldığı geri bildirim doğrultusunda başarısız gerçekleşti.

SBOM ‘un Doğrulanması

Şimdi ise Ratify ‘ın external plugin’lerinden olan SBOM doğrulayıcısını entegre edelim. Bunun için Ratify kurulumunu aşağıdaki gibi özelleştirmemiz gerekmektedir.

          helm upgrade --install ratify ratify/ratify --version 1.12.1 \
              --namespace gatekeeper-system \
              --set featureFlags.RATIFY_CERT_ROTATION=true \
              --set logger.level=debug \
              --set-file notationCerts={$(Agent.BuildDirectory)/../../.config/notation/localkeys/order-api.io.crt} \
              --set oras.authProviders.azureManagedIdentityEnabled=true \
              --set azureManagedIdentity.clientId=\"YOUR_CLIENT_ID\" \
              --set azureManagedIdentity.tenantId="YOUR_TENANT_ID" \
              --set sbom.enabled=true \
              --set sbom.notaryProjectSignatureRequired=true \
              --set sbom.disallowedPackages[0].name="EasyNetQ" \
              --set sbom.disallowedPackages[0].version="6.3.1"

Bu noktada örnek bir senaryo olarak, “sbom.enabled” parametresi ile SBOM doğrulayıcısını etkinleştiriyor ve “6.3.1” versiyonlu “EasyNetQ” paketini içeren container image’lerinin kubernetes ortamında çalıştırılmaması gerektiğini belirtiyoruz. Ayrıca, makalenin ilk bölümünde hatırlarsak, oluşturmuş olduğumuz SBOM dokümanını da supply chain güvenliği için imzalamıştık. Sağladığımız “sbom.notaryProjectSignatureRequired” parametresi ile ise SBOM dokümanı için oluşturmuş olduğumuz imzanın da doğrulanması gerektiğini belirtiyoruz. Ek olarak, SBOM doğrulayıcısı için de aynı ConstraintTemplate ve Constraint manifest’lerini kullanacağız.

Şimdi, Ratify ‘ın SBOM doğrulayıcı plugin’i SBOM dokümanını SPDX-JSON formatında beklediği için, makalenin ilk bölümünde Trivy ile SBOM dokümanını oluşturuyor olduğumuz task’a dönelim ve “–format spdx” parametresini “–format spdx-json” olarak güncelleyelim.

Ardından bir önceki aşamada imzalanmamış örnek bir container image’ini yayımlamak için kullanmış olduğumuz task’ı aşağıdaki gibi güncelleyelim ve uygulanacak olan politika detaylarını görebilmek için Ratify pod’unun log’larını getirecek olan task’ı da job içerisine dahil edelim.

    - task: AzureCLI@2
      displayName: 'Deploy the $(orderAPIImageName) container'
      inputs:
        azureSubscription: 'DevOpsPoC'
        scriptType: 'bash'
        scriptLocation: 'inlineScript'
        inlineScript: |
          cat <

Pipeline’ı tekrar çalıştırdığımızda ise “Dev“ortamına yayımlama işleminin yine başarısız gerçekleştiğini ve “verifierReports” array’i içerisinde sebep olan uygulanmış politikaları da görebilmekteyiz. İlk rapor’a baktığımızda imza doğrulama aşamasının başarılı gerçekleştiğini fakat uygulama içerisinde kullanılan “6.3.1” versiyonlu “EasyNetQ” paketi sebebiyle SBOM doğrulamasının başarısız gerçekleştiğini görebiliriz.

Bu sayede organizasyon olarak software supply chain’imiz içerisinde merkezi bir kontrol mekanizmasına sahip olabilir, ortamlarımızda bulunmasını istemediğimiz spesifik paketlerin kontrollerini sağlayabilmekteyiz. Ayrıca uygulamalar içerisinde kullanılan bu open-source paketler organizasyon tarafından izin verilmeyen bir lisanslama modellerine sahiplerse, bunların kontrolünü de “–set sbom.disallowedLicenses={“MPL”}” parametresi ile gerçekleştirebilmekteyiz.

Ratify ‘ın kurulumu ile etkinleştirilen SBOM doğrulayıcısının manifest’ine ise, aşağıdaki gibi erişebilir ve istediğimiz gibi değişiklikleri üzerinde gerçekleştirebiliriz.

Bu noktada, “Disallowed Packages” içerisinde “6.3.1” versiyonlu “EasyNetQ” paketinin yer aldığını ve bu doğrulayıcısının, container’lar ile ilişkilendirilmiş “application/spdx+json” tipindeki artifact metadata’ları ile ilgilendiğini de görebilmekteyiz.

Güvenlik Taraması Sonuçlarının Doğrulanması

Bu noktaya kadar, CI süreçleri boyunca oluşturmuş olduğumuz supply chain artifact’lerinin imzalarının ve SBOM dokümanının doğrulama işlemlerini politikalar aracılığıyla otomatikleştirdik. Şimdi ise oluşturmuş olduğumuz container güvenlik taraması sonuçlarını politikalar ile nasıl doğrulayabileceğimize bir göz atalım.

Bu noktada Ratify ‘ın yine external plugin’lerinden olan Vulnerability Report doğrulayıcısını kullanacağız. Bu doğrulayıcı, ilgili güvenlik taraması sonuçlarının SARIF formatında ve “application/sarif+json” tipinde bir artifact olarak ilgili container image’i ile ilişkilendirilmiş olmasını beklemektedir.

Aynı şekilde bu plugin’ini de kullanabilmek için, Ratify ‘ı ve ilgili politikayı konfigure etmemiz gerekmektedir. Örnek senaryo gereği, kubernetes ortamına yayımlanacak tüm container’ların, 24 saat içerisinde oluşturulmuş güvenlik taraması sonuçlarına sahip olmasını ve “HIGH” veya “CRITICAL” seviyede güvenlik ihlallerini bulundurmamalarını istediğimizi varsayalım.

          helm upgrade --install ratify ratify/ratify --version 1.12.1 \
              --namespace gatekeeper-system \
              --set featureFlags.RATIFY_CERT_ROTATION=true \
              --set logger.level=debug \
              --set-file notationCerts={$(Agent.BuildDirectory)/../../.config/notation/localkeys/order-api.io.crt} \
              --set oras.authProviders.azureManagedIdentityEnabled=true \
              --set azureManagedIdentity.clientId=\"36929c64-3fae-485c-82bd-b743cddfee56\" \
              --set azureManagedIdentity.tenantId="fc3df655-65a6-41ff-821f-0aa2e451a17b" \
              --set sbom.enabled=true \
              --set sbom.notaryProjectSignatureRequired=true \
              --set sbom.disallowedPackages[0].name="EasyNetQ" \
              --set sbom.disallowedPackages[0].version="6.3.1" \
              --set vulnerabilityreport.enabled=true \
              --set vulnerabilityreport.notaryProjectSignatureRequired=true \
              --set vulnerabilityreport.maximumAge="24h" \
              --set vulnerabilityreport.disallowedSeverities="{"high","critical"}"

          kubectl apply -f https://deislabs.github.io/ratify/library/default/template.yaml
          kubectl apply -f https://deislabs.github.io/ratify/library/default/samples/constraint.yaml
          kubectl apply -f https://raw.githubusercontent.com/deislabs/ratify/23b143d07a53fd61557703c9836e486353959530/library/vulnerability-report-validation/template.yaml
          kubectl apply -f https://raw.githubusercontent.com/deislabs/ratify/v1.1.0/library/vulnerability-report-validation/samples/constraint.yaml

Bu noktada, SBOM konfigürasyonu ardından, “vulnerabilityreport.enabled” parametresi ile ilgili doğrulayıcıyı etkinleştiriyor, “vulnerabilityreport.maximumAge” ve “vulnerabilityreport.disallowedSeverities” parametreleri ile de güvenlik taraması sonuçlarının 24 saat içerisinde oluşturulmuş olmasının ve “HIGH” veya “CRITICAL” seviyede güvenlik ihlallerinin bulunmaması gerektiğini söylüyoruz.

Ratify ‘ı ilgili doğrulayıcıyı oluşturması için konfigure ettikten sonra ise, bu doğrulayıcının da politika mantığının yer altığı kendi Gatekeeper Constraint Template ‘ini ve Constraint ‘ini oluşturuyoruz.

Bu doğrulayıcının da Rego politikasına hızlıca bir göz atalım.

apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: vulnerabilityreportvalidation
spec:
  crd:
    spec:
      names:
        kind: VulnerabilityReportValidation
      validation:
        openAPIV3Schema:
          type: object
          properties:
            issuer:
              type: string
  targets:
    - target: admission.k8s.gatekeeper.sh
      rego: |
        package vulnerabilityreportvalidation

        # This template defines policy for vulnerability report validation.
        # It checks the following:
        # - If there are any system errors
        # - If there are errors for any of the images
        # - There is at least one vulnerability report that was verified
        # - Only considers the latest vulnerability report
        # - The latest vulnerability report is valid (isSuccess = true)
        # - The latest vulnerability report has a valid notary project signature (if require_signature = true)

        import future.keywords.if
        import future.keywords.in
        import future.keywords.every

        default require_signature := false # change to true to require notary project signature on vulnerability report

        # Get data from Ratify
        remote_data := response {
          images := [img | img = input.review.object.spec.containers[_].image]
          images_init := [img | img = input.review.object.spec.initContainers[_].image]
          images_ephemeral := [img | img = input.review.object.spec.ephemeralContainers[_].image]
          other_images := array.concat(images_init, images_ephemeral)
          all_images := array.concat(other_images, images)
          response := external_data({"provider": "ratify-provider", "keys": all_images})
        }

        violation[{"msg": msg}] {
          general_violation[{"result": msg}]
        }

        # Check if there are any system errors
        general_violation[{"result": result}] {
          err := remote_data.system_error
          err != ""
          result := sprintf("System error calling external data provider for vulnerability report verification: %s", [err])
        }

        # Check if there are errors for any of the images
        general_violation[{"result": result}] {
          count(remote_data.errors) > 0
          result := sprintf("Error validating one or more images for vulnerability report verification: %s", remote_data.errors)
        }

        # Check if the success criteria is true
        general_violation[{"result": result}] {
          subject_validation := remote_data.responses[_]
          subject_result := subject_validation[1]
          not process_vuln_reports(subject_result)
          result := sprintf("Subject failed vulnerability report verification: %s", [subject_validation[0]])
        }

        process_vuln_reports(subject_result) if {
          # collect verifier reports from vulnerabilityreport verifier
          vuln_results := [res | subject_result.verifierReports[i].type == "vulnerabilityreport"; res := subject_result.verifierReports[i]]
          count(vuln_results) > 0
          # calculate the timestamp between current time and creation time
          timestamp_diff_results_map := {diff_in_ns: i | diff_in_ns := time.now_ns() - time.parse_rfc3339_ns(vuln_results[i].extensions["createdAt"])}
          count(timestamp_diff_results_map) > 0
          # extract time difference durations into separate array to find global minimum
          timestamp_diff_results_arr := [key | timestamp_diff_results_map[key]]
          smallest_timestamp_diff := min(timestamp_diff_results_arr)
          # validate latest report
          process_vuln_report(vuln_results[timestamp_diff_results_map[smallest_timestamp_diff]])
        }

        process_vuln_report(report) if {
          report.isSuccess == true
          valid_signatures(report)
        }

        valid_signatures(_) := true {
          require_signature == false
        }

        valid_signatures(report) := true {
          require_signature
          count(report.nestedResults) > 0
          some nestedResult in report.nestedResults
          nestedResult.artifactType == "application/vnd.cncf.notary.signature"
          nestedResult.isSuccess
        }

Bu noktada, Gatekeeper, Ratify ‘ı external data noktası olarak kullanarak, ilgili Vulnerability Report doğrulayıcısının sonuçlarına elde ediyor ve içerisinde güncel bir güvenlik rapor olduğundan emin oluyor. Ayrıca opsiyonel olarak ilgili artifact’in imzasının doğrulanmasını da gerçekleştiriyor.

Bu makalenin ilk kısmından hatırlayacak olursak örnek olarak kullandığımız Order API ‘ın toplam 70 adet farklı CVE ‘si bulunmaktaydı.

Pipeline, beklediğimiz gibi başarısız bir şekilde gerçekleşti ve “verifierReports” array’i içerisine baktığımızda ise, SBOM ile ilgili oluşturmuş olduğumuz politikayı değiştirmediğimiz için doğrulamanın yine “EasyNetQ” paketi nedeniyle başarısız olduğunu görüyoruz. Buna ek olarak, bu sefer Vulnerability Report doğrulayıcısının da Order API container image’inin “HIGH” ve “CRITICAL” seviyelerde güvenlik ihlalleri bulundurması sebebiyle başarısız gerçekleştiğini görebiliriz.

Toparlayalım

Makalenin bu ikinci bölümünde, kubernetes ortamında OPA Gatekeeper ve Ratify kullanarak çeşitli güvenlik ve denetim politikalarını declarative bir şekilde nasıl konfigüre edebileceğimizi ve software supply chain güvenliğini artırmak için containerized uygulamaların dağıtım işlemleri sırasında bu politikaları otomatikleştirilmiş kararlarla nasıl zorunlu kılabileceğimize bir göz attık.

Gatekeeper ‘ın, Rego dili ile çeşitli politikaları declarative bir şekilde nasıl tanımlayabilmemize olanak sağladığına ve Ratify ‘ın ise container security supply chain alanında external bir data provider olarak bu süreçleri nasıl destekleyebileceğine değindik.

Özetle, software supply chain güvenliğinin sağlanması adına otomatikleştirilmiş kararlar da dahil olmak tüm bu adımların uygulanmasının ve bunları SDLC süreçlerine dahil etmenin önemini gördük. Bu, organizasyonların karşılaşabileceği potansiyel güvenlik risklerini en aza indirilebilmesine yardımcı olmakla beraber, güvenilir bir yazılım geliştirme ortamı oluşturulmasına katkıda bulunmaktadır.

Referanslar

https://ratify.dev/docs/what-is-ratify
https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/
https://open-policy-agent.github.io/gatekeeper/website/docs/howto/

Gökhan Gökalp

Recent Posts

Containerized Uygulamaların Supply Chain’ini Güvence Altına Alarak Güvenlik Risklerini Azaltma (Güvenlik Taraması, SBOM’lar, Artifact’lerin İmzalanması ve Doğrulanması) – Bölüm 1

{:tr}Bildiğimiz gibi modern yazılım geliştirme ortamında containerization'ın benimsenmesi, uygulamaların oluşturulma ve dağıtılma şekillerini oldukça değiştirdi.…

8 ay ago

Identity & Access Management İşlemlerini Azure AD B2C ile .NET Ortamında Gerçekleştirmek

{:tr}Bildiğimiz gibi bir ürün geliştirirken olabildiğince farklı cloud çözümlerinden faydalanmak, harcanacak zaman ve karmaşıklığın yanı…

1 yıl ago

Azure Service Bus Kullanarak Microservice’lerde Event’ler Nasıl Sıralanır (FIFO Consumers)

{:tr}Bazen bazı senaryolar vardır karmaşıklığını veya eksi yanlarını bildiğimiz halde implemente etmekten kaçamadığımız veya implemente…

2 yıl ago

.NET Microservice’lerinde Outbox Pattern’ı ile Eventual Consistency için Atomicity Sağlama

{:tr}Bildiğimiz gibi microservice architecture'ına adapte olmanın bir çok artı noktası olduğu gibi, maalesef getirdiği bazı…

2 yıl ago

Dapr ve .NET Kullanarak Minimum Efor ile Microservice’ler Geliştirmek – 02 (Azure Container Apps)

{:tr}Bir önceki makale serisinde Dapr projesinden ve faydalarından bahsedip, local ortamda self-hosted mode olarak .NET…

2 yıl ago

.NET 7 ile Gelen Bazı Harika Yenilikler

{:tr}Bildiğimiz gibi .NET Conf 2022, 8-10 kasım arasında gerçekleşti. Konferans sırasında ise .NET 7 ve…

2 yıl ago