Secure your .NET builds with StepSecurity and GitHub Actions

Secure your .NET builds with StepSecurity and GitHub Actions

Dave Murray's photo
Dave Murray
·Aug 7, 2022·

10 min read

Play this article

Table of contents

  • What are Software Supply Chain Attacks?
  • What is StepSecurity?
  • Secure Workflows
  • Harden Runner
  • How do I use all this?
  • Where do we go from here?

Software supply chain attacks are increasing in severity and frequency. StepSecurity are working to help secure DevOps workflows using GitHub Actions. In this article I show how you can use StepSecurity's tools to improve the security of your workflows with an example for .NET.

What are Software Supply Chain Attacks?

Software supply chain attacks are an emerging threat that targets software developers and suppliers. The goal of attackers is to access source code, build processes or update mechanisms to infect legitimate apps and distribute malware. There has been a surge in supply chain attacks following the well known SolarWinds attack in 2020. These attacks have breached all types of organisation ranging from startups to Fortune 500 companies, government agencies and open source projects. Attackers are now focusing their efforts on breaching software suppliers because a successful attack may affect a large number of victims that use the supplier's software.

Software Supply Chain Attacks

What is StepSecurity?

StepSecurity aims to help secure your software release and distribution supply chain as simply as possible. The Secure Workflows app provides remediation of vulnerabilities in GitHub Actions workflows, significantly reducing the time and effort required to apply security settings. And, the Harden Runner Action audits and blocks outbound traffic to prevent exfiltration of credentials, detect compromised dependencies or tools and detect code tampering during builds.

StepSecurity Overview

StepSecurity was recently recognized by the Linux Foundation for "high-impact and lasting improvements that almost certainly prevent major vulnerabilities in the affected code or supporting infrastructure." Secure Workflows is used by many of the OpenSSF's critical open source projects including Python, Gatsby and Babel as well as products from Microsoft, Google and Cisco.

StepSecurity currently supports GitHub Actions but support for GitLab, CircleCI and more providers is on the roadmap.

Secure Workflows

The Secure Workflows app can automatically:

  • Set minimum GITHUB_TOKEN permissions
  • Pin Actions to a full length commit SHA
  • Add the Harden Runner Action to each job

StepSecurity Secure Workflows

Token Permissions

At the start of a workflow run, GitHub generates a unique GITHUB_TOKEN that is passed to the runner to be used for authentication and expires when the workflow finishes or after a maximum of 24 hours. By default the token has write permissions for all scopes except when generated by a pull request from a fork, in which case it has only read permissions.

A compromised token with write access could be used to push malicious code into a project. By setting all the token permissions to read except where required by the workflow, Secure Workflows follows the principle of least privilege and reduces the risk of a compromised token.

StepSecurity maintains a knowledge base of GitHub Actions that is used by Secure Workflows to determine the permissions needed by each step in your workflow and recommend the minimum permissions. If you maintain a GitHub Action please contribute to the knowledge base so users of your Action can know they have the correct permissions set. Both my Actions - Code Coverage Summary & Edit Release - are already in the knowledge base.

Pin Actions by SHA

Git and Docker tags are mutable. This poses a security risk because if the tag changes you will not have a chance to review the change before it gets used. The current GitHub recommended best practice for using Actions is pinning them to a full length commit SHA rather than a version tag. This helps mitigate the risk of an attacker compromising the Action's repository because they would need to generate an SHA-1 collision.

Although pinning to a commit SHA is the most secure option, specifying a tag is more convenient and easier to read so it is widely used. Secure Workflows allows you to write your workflow using tags then update it with the Secure Workflows app to pin all the Actions by commit SHA. If you use Dependabot to keep your workflow dependencies up to date, don't worry, it also understands pinning by SHA so will continue to keep your workflows updated and following best practice.

Add Harden Runner Action

Secure Workflows can add a step that installs the Harden Runner security agent to the start of each job in a workflow, configuring it in audit mode. Harden Runner only supports Ubuntu runners so disable this option if you are using a Windows or macOS runner, more on this below.

Harden Runner

Compromised dependencies and build tools typically make outbound calls. This was seen in the Codecov breach, Alex Birsan's research on dependency confusion attacks, malicious npm cryptomining packages and other recent attacks. Restricting outbound traffic to only the endpoints needed for the workflow thwarts many attack vectors that target the build server.

Harden Runner is a GitHub Action that installs a security agent on the runner to audit and block outbound traffic to prevent exfiltration of credentials, help detect compromised dependencies or tools and detect code tampering during builds. There are a few limitations:

  • Harden Runner only works for GitHub hosted runners, self-hosted runners are not supported.
  • Only Ubuntu runners are supported, Windows and macOS runners are not currently supported but are being discussed.
  • Harden Runner does not support running a job in a container. It can monitor jobs that use containers to run steps, the limitation is if the entire job is run in a container which is not common for GitHub Actions workflows.

Since Harden Runner will only work on an Ubuntu runner you can't use it for jobs that require a Windows or macOS runner such as building .NET Framework, Xamarin or .NET MAUI apps. It does work for .NET Core and .NET 5/6 apps that will build on Linux and can be used for other jobs that don't require a Windows or macOS runner such as project management workflows.

StepSecurity Harden Runner Action

Harden Runner supports both public and private repositories. For private repositories you need to install the Harden Runner App in GitHub which requires actions: read permissions to your repositories. I also recommend installing the app for public repositories because otherwise you have to wait 30 seconds for the results and I'm impatient. If you use Harden Runner in a private repository the generated audit report is not public, authentication is required to access the report and only those who have access to the repository can view it.

The Secure Workflows app can be used to add Harden Runner to every job in a workflow, configuring it in audit mode. Future runs of the workflow will include a link to the audit report for that run. Once you have audited several runs you can configure Harden Runner in block mode with a list of the allowed endpoints for the workflow. From then on, outbound traffic from the workflow will be restricted to only those domains on the list. Calls to any other domain will fail DNS resolution and cause the step that made them and the workflow to fail.

StepSecurity Harden Runner Audit Result

How do I use all this?

If all that security talk sounds complicated don't worry, StepSecurity have made implementing all the recommendations a simple process.

To secure a GitHub Actions workflow:

  1. Copy the workflow YAML file
  2. Paste it into the Secure Workflows app
  3. Click the Secure Workflows button
  4. Click the Copy button
  5. Paste the workflow back into your codebase

If there are any problems securing your workflow, such as using an Action not in the knowledge base, the errors will be listed with guidance. Whenever the app discovers an Action that is missing from the knowledge base it automatically opens an issue in the Secure Workflows repository so it will be added in the near future.

.NET Build Example

Let's look at a simple .NET 6 CI build workflow:

name: CI Build

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

env:
  DOTNET_NOLOGO: true                     # Disable the .NET logo in the console output
  DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true # Disable the .NET first time experience
  DOTNET_CLI_TELEMETRY_OPTOUT: true       # Disable sending .NET CLI telemetry to Microsoft

jobs:
  build:
    runs-on: ubuntu-latest
    name: CI Build
    steps:

    - name: Checkout
      uses: actions/checkout@v3
      with:
        fetch-depth: 0

    - name: Setup .NET
      uses: actions/setup-dotnet@v2
      with:
        dotnet-version: 6.0.x

    - name: Restore Dependencies
      run: dotnet restore src/Example.sln

    - name: Build App
      run: dotnet build src/Example.sln --configuration Release --no-restore

    - name: Run Unit Tests
      run: dotnet test src/Example.sln --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage

    - name: Code Coverage Summary Report
      uses: irongut/CodeCoverageSummary@v1.3.0
      with:
        filename: coverage/**/coverage.cobertura.xml
        badge: true
        format: 'md'
        output: 'both'

    - name: Add Coverage PR Comment
      uses: marocchino/sticky-pull-request-comment@v2
      if: github.event_name == 'pull_request'
      with:
        recreate: true
        path: code-coverage-results.md

    - name: Upload Coverage Artifact
      uses: actions/upload-artifact@v3.1.0
      with:
        name: test-coverage-report
        path: code-coverage-results.md

    - name: Upload Build Artifact
      uses: actions/upload-artifact@v3.1.0
      with:
        name: ci-nuget
        path: src/Example/bin/Release/net6.0

This workflow runs on all pull requests or pushes to the master branch and builds a .NET 6 app on an Ubuntu runner. It does not set any token permissions and pins its Actions using the easier to read version tags. It consists of one job with several steps:

  • checkout the code
  • set up .NET
  • restore dependencies
  • build the .NET code
  • run unit tests
  • create a code coverage summary report
  • add the report to the PR
  • upload the report and build artifacts

Secured .NET Build

Now let's secure the workflow using the StepSecurity Secure Workflows app:

name: CI Build

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

env:
  DOTNET_NOLOGO: true                     # Disable the .NET logo in the console output
  DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true # Disable the .NET first time experience
  DOTNET_CLI_TELEMETRY_OPTOUT: true       # Disable sending .NET CLI telemetry to Microsoft

permissions:  # added using https://github.com/step-security/secure-workflows
  contents: read

jobs:
  build:
    permissions:
      contents: read  # for actions/checkout to fetch code
      pull-requests: write  # for marocchino/sticky-pull-request-comment to create or update PR comment
    runs-on: ubuntu-latest
    name: CI Build
    steps:

    - name: Harden Runner
      uses: step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504
      with:
        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs

    - name: Checkout
      uses: actions/checkout@d0651293c4a5a52e711f25b41b05b2212f385d28
      with:
        fetch-depth: 0

    - name: Setup .NET
      uses: actions/setup-dotnet@c0d4ad69d8bd405d234f1c9166d383b7a4f69ed8
      with:
        dotnet-version: 6.0.x

    - name: Restore Dependencies
      run: dotnet restore src/Example.sln

    - name: Build
      run: dotnet build src/Example.sln --configuration Release --no-restore

    - name: Run Unit Tests
      run: dotnet test src/Example.sln --configuration Release --no-build --verbosity normal --collect:"XPlat Code Coverage" --results-directory ./coverage

    - name: Code Coverage Summary Report
      uses:  irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95
      with:
        filename: coverage/**/coverage.cobertura.xml
        badge: true
        format: 'md'
        output: 'both'

    - name: Add Coverage PR Comment
      uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443
      if: github.event_name == 'pull_request'
      with:
        recreate: true
        path: code-coverage-results.md

    - name: Upload Coverage Summary Artifact
      uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8
      with:
        name: test-coverage-report
        path: code-coverage-results.md

    - name: Upload Artifact
      uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8
      with:
        name: ci-nuget
        path: src/Example/bin/Release/net6.0

As you can see, the app makes several changes to the workflow.

It starts by setting the default token permissions for all jobs to read:

permissions:  # added using https://github.com/step-security/secure-workflows
  contents: read

In the build job it sets the required token permissions for the Actions used:

jobs:
  build:
    permissions:
      contents: read  # for actions/checkout to fetch code
      pull-requests: write  # for marocchino/sticky-pull-request-comment to create or update PR comment

The Harden Runner Action is added in audit mode as the first step in the job:

    - name: Harden Runner
      uses: step-security/harden-runner@74b568e8591fbb3115c70f3436a0c6b0909a8504
      with:
        egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs

And, every Action is now pinned by commit SHA instead of a version tag, for example:

    - name: Setup .NET
      uses: actions/setup-dotnet@c0d4ad69d8bd405d234f1c9166d383b7a4f69ed8
      with:
        dotnet-version: 6.0.x

Where do we go from here?

Using Secure Workflows to restrict token permissions and pin Actions to a commit SHA is quick and easy to do for all your workflows. The next step is to continue to develop your project while Harden Runner audits outbound connections from your workflows.

In a follow-up article I show how to use the audit reports and configure Harden Runner in block mode to restrict outbound connections and further improve the security of your GitHub Actions software supply chain.

 

 

Cover image contains a vector created by fullvector from www.freepik.com.

Software Supply Chain Attacks chart is from Sonatype's 2021 State of the Software Supply Chain report.

Did you find this article valuable?

Support Dave Murray by becoming a sponsor. Any amount is appreciated!

See recent sponsors Learn more about Hashnode Sponsors
 
Share this