From 0eb4a7831b39c1aaba71a56f6a7101e7b118dd54 Mon Sep 17 00:00:00 2001 From: Otto Bittner Date: Wed, 21 Sep 2022 10:47:38 +0200 Subject: [PATCH] AB#2413: Add workflow for snp-report-verify * Extend azure-snp-report-verify to also report fw SVNs. * Add workflow based on azure-cvm to get maa-jwt and verify it on a second runner. --- .../actions/azure_snp_reporter/action.yaml | 12 +++ .github/runners/azure-cvm/README.md | 4 + .github/workflows/azure-snp-reporter.yml | 81 +++++++++++++++++++ .../Dockerfile | 2 +- .../README.md | 11 +-- .../verify.go | 41 +++++++--- 6 files changed, 133 insertions(+), 18 deletions(-) create mode 100644 .github/actions/azure_snp_reporter/action.yaml create mode 100644 .github/workflows/azure-snp-reporter.yml rename hack/{azure-snp-idkey-digest => azure-snp-report-verify}/Dockerfile (96%) rename hack/{azure-snp-idkey-digest => azure-snp-report-verify}/README.md (56%) rename hack/{azure-snp-idkey-digest => azure-snp-report-verify}/verify.go (65%) diff --git a/.github/actions/azure_snp_reporter/action.yaml b/.github/actions/azure_snp_reporter/action.yaml new file mode 100644 index 000000000..2c0d836bb --- /dev/null +++ b/.github/actions/azure_snp_reporter/action.yaml @@ -0,0 +1,12 @@ +name: Azure SNP Reporter +description: "Get SNP MAA statement from Azure." +inputs: + outputPath: + description: "Path to put signed JWT into." + required: true +runs: + using: "composite" # some azure SNP-enabled machine. + steps: + - name: Fetch report + shell: bash + run: docker run --rm --privileged -v/sys/kernel/security:/sys/kernel/security ghcr.io/edgelesssys/constellation/azure-snp-reporter | tail -n 1 > ${{ inputs.outputPath }} diff --git a/.github/runners/azure-cvm/README.md b/.github/runners/azure-cvm/README.md index b87e83121..fe3301d56 100644 --- a/.github/runners/azure-cvm/README.md +++ b/.github/runners/azure-cvm/README.md @@ -5,6 +5,10 @@ This folder contains the files to setup an Azure function and ARM template in or - `azure-function`: All necessary files to redeploy the function. Changes in `requirements.txt` are installed during deployment of the function. `cloud-init.txt` is put into the CVM by supplying it as a parameter to the ARM template deployment. # Update cvm-template +In order to make the Azure function use your changes you will have to publish them in the `cvm-template.json` file. +While developing you can point the `template_id` variable in `__init__.py` to a different location. + +Doing the following you can debug your template changes: - Look for the `Template spec` resource in your Azure project (e.g. "snp-value-reporter-template"). - Click on "Create new version". - Select the latest version available. diff --git a/.github/workflows/azure-snp-reporter.yml b/.github/workflows/azure-snp-reporter.yml new file mode 100644 index 000000000..c93b451e4 --- /dev/null +++ b/.github/workflows/azure-snp-reporter.yml @@ -0,0 +1,81 @@ +name: Fetch, validate and report SNP report data. +on: + workflow_dispatch: + schedule: + - cron: "0 14 * * 0" + +# Abort runs of *this* workflow, if a new commit with the same ref is pushed that is not main. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + build-snp-reporter: + name: "Build SNP-reporter container" + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + + - name: Set up Go + uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f + with: + go-version: 1.18 + + - name: Build and upload azure SNP reporter container image + id: build-and-upload + uses: ./.github/actions/build_micro_service + with: + name: azure-snp-reporter + dockerfile: ./hack/azure-snp-report-verify/Dockerfile + githubToken: ${{ secrets.GITHUB_TOKEN }} + + + fetch-snp-report: + needs: build-snp-reporter + name: "Fetch SNP report" + runs-on: [self-hosted, azure-cvm] + env: + SHELL: /bin/bash + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + + - name: Fetch SNP report + uses: ./.github/actions/azure_snp_reporter + with: + outputPath: ${{ github.workspace }}/maa-report.jwt + + - name: Upload report JWT + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 + with: + name: maa-report.jwt + path: "${{ github.workspace }}/maa-report.jwt" + + validate-snp-report: + needs: fetch-snp-report + name: "Validate SNP report" + runs-on: ubuntu-latest + env: + SHELL: /bin/bash + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b + with: + submodules: recursive + token: ${{ secrets.CI_GITHUB_REPOSITORY }} + + - name: Set up Go + uses: actions/setup-go@268d8c0ca0432bb2cf416faae41297df9d262d7f + with: + go-version: 1.18 + + - name: Download report JWT + uses: actions/download-artifact@v3 + with: + name: "maa-report.jwt" + path: "." + + - name: Verify report + shell: bash + run: go run ./hack/azure-snp-report-verify/verify.go $(cat ./maa-report.jwt) diff --git a/hack/azure-snp-idkey-digest/Dockerfile b/hack/azure-snp-report-verify/Dockerfile similarity index 96% rename from hack/azure-snp-idkey-digest/Dockerfile rename to hack/azure-snp-report-verify/Dockerfile index 4c54ca329..138feb928 100644 --- a/hack/azure-snp-idkey-digest/Dockerfile +++ b/hack/azure-snp-report-verify/Dockerfile @@ -10,7 +10,7 @@ RUN wget -q https://github.com/Azure/confidential-computing-cvm-guest-attestatio && touch Utils.h \ && g++ -Os -I/usr/include/azguestattestation1 -oclient main.cpp -lazguestattestation -FROM ubuntu:20.04 +FROM ubuntu:20.04 AS release COPY --from=build client azguestattestation1_1.0.2_amd64.deb / RUN apt-get update && apt-get install -y /azguestattestation1_1.0.2_amd64.deb ENTRYPOINT ["/client"] diff --git a/hack/azure-snp-idkey-digest/README.md b/hack/azure-snp-report-verify/README.md similarity index 56% rename from hack/azure-snp-idkey-digest/README.md rename to hack/azure-snp-report-verify/README.md index 85acedfc3..10744bd41 100644 --- a/hack/azure-snp-idkey-digest/README.md +++ b/hack/azure-snp-report-verify/README.md @@ -1,20 +1,21 @@ -# Obtain current Azure SNP ID key digest +# Obtain current Azure SNP ID key digest & firmware versions On Azure, Constellation verifies that the SNP attestation report contains Azure's ID key digest. -Currently, the only way to verify this digest's origin is to perform guest attestation with the help of the Microsoft Azure Attestation (MAA) service. +Additionally, some firmware security version numbers (SVNs) are validated. +Currently, the only way to verify the digest's origin is to perform guest attestation with the help of the Microsoft Azure Attestation (MAA) service. There's a [sample](https://github.com/Azure/confidential-computing-cvm-guest-attestation) on how to do this, but it's not straightforward. So we created tooling to make things easier. -Perform the following steps to get the ID key digest: +Perform the following steps to get the ID key digest & firmware versions: 1. Create an Ubuntu CVM on Azure with secure boot enabled and ssh into it. 2. Run ``` - docker run --rm --privileged -v/sys/kernel/security:/sys/kernel/security ghcr.io/edgelesssys/constellation/get-azure-snp-jwt + docker run --rm --privileged -v/sys/kernel/security:/sys/kernel/security ghcr.io/edgelesssys/constellation/azure-snp-reporter ``` This executes the guest attestation and prints the JWT received from the MAA. (It's the long base64 blob.) 3. Copy the JWT and run **on your local trusted machine**: ``` go run verify.go ``` - On success it prints the ID key digest. + On success it prints the ID key digest and relevant firmware SVNs. diff --git a/hack/azure-snp-idkey-digest/verify.go b/hack/azure-snp-report-verify/verify.go similarity index 65% rename from hack/azure-snp-idkey-digest/verify.go rename to hack/azure-snp-report-verify/verify.go index eecbab3aa..d7ee1cbc1 100644 --- a/hack/azure-snp-idkey-digest/verify.go +++ b/hack/azure-snp-report-verify/verify.go @@ -23,33 +23,52 @@ import ( "gopkg.in/square/go-jose.v2/jwt" ) +type IsolationTEE struct { + IDKeyDigest string `json:"x-ms-sevsnpvm-idkeydigest"` + TEESvn int `json:"x-ms-sevsnpvm-tee-svn"` + SNPFwSvn int `json:"x-ms-sevsnpvm-snpfw-svn"` + MicrocodeSvn int `json:"x-ms-sevsnpvm-microcode-svn"` + BootloaderSvn int `json:"x-ms-sevsnpvm-bootloader-svn"` + GuestSvn int `json:"x-ms-sevsnpvm-guestsvn"` +} + +func (i *IsolationTEE) PrintSVNs() { + fmt.Println("\tTEE SVN:", i.TEESvn) + fmt.Println("\tSNP FW SVN:", i.SNPFwSvn) + fmt.Println("\tMicrocode SVN:", i.MicrocodeSvn) + fmt.Println("\tBootloader SVN:", i.BootloaderSvn) + fmt.Println("\tGuest SVN:", i.GuestSvn) +} + func main() { if len(os.Args) != 2 { fmt.Println("Usage:", os.Args[0], "") return } - idKeyDigest, err := getIDKeyDigest(os.Args[1]) + report, err := getTEEReport(os.Args[1]) if err != nil { panic(err) } - fmt.Println("Successfully validated ID key digest:", idKeyDigest) + fmt.Println("Successfully validated ID key digest:", report.IDKeyDigest) + fmt.Println("Currently reported SVNs:") + report.PrintSVNs() } -func getIDKeyDigest(rawToken string) (string, error) { +func getTEEReport(rawToken string) (IsolationTEE, error) { // Parse token. token, err := jwt.ParseSigned(rawToken) if err != nil { - return "", err + return IsolationTEE{}, err } // Get JSON Web Key set. keySetBytes, err := httpGet(context.Background(), "https://sharedeus.eus.attest.azure.net/certs") if err != nil { - return "", err + return IsolationTEE{}, err } keySet, err := parseKeySet(keySetBytes) if err != nil { - return "", err + return IsolationTEE{}, err } // Get claims. Private claims contain ID Key digest. @@ -57,19 +76,17 @@ func getIDKeyDigest(rawToken string) (string, error) { var publicClaims jwt.Claims var privateClaims struct { - IsolationTEE struct { - IDKeyDigest string `json:"x-ms-sevsnpvm-idkeydigest"` - } `json:"x-ms-isolation-tee"` + IsolationTEE IsolationTEE `json:"x-ms-isolation-tee"` } if err := token.Claims(&keySet, &publicClaims, &privateClaims); err != nil { - return "", err + return IsolationTEE{}, err } if err := publicClaims.Validate(jwt.Expected{Time: time.Now()}); err != nil { - return "", err + return IsolationTEE{}, err } - return privateClaims.IsolationTEE.IDKeyDigest, nil + return privateClaims.IsolationTEE, nil } func parseKeySet(keySetBytes []byte) (jose.JSONWebKeySet, error) {