Introduction

This year I set a goal to explore and learn the most popular CI solutions like GitLab, including all cloud-native ones. My first pick had to be GitHub Actions, which I heard a lot of but never used. So I decided to plunge into it to see how rich the solution was. Since I only use CI for Terraform deployments, I aimed to cover the features, not the dev part.

This post is rather a long-mixed pack of titbits & notes out of my 2 weeks’ immersion, highlighting the particularities and features of GitHub Actions in the CI/CD landscape, with no specific order. This will hopefully get you started with GitHub Actions, whether you’re familiar with CI or just a curious newcomer. 

 

Table of Contents

 

GitHub Actions Basics

As most of you know, GitHub Actions is a platform that allows developers to automate workflows and tasks directly within GitHub repositories. Below is a brief description of key components of GitHub Actions:

 

  • Workflows: Configurable, automated process that executes one or more actions and is defined by YAML files checked into the repository under .github/workflows

  • Runner: a GitHub or self-hosted VM used to run your workflow with a list of tools preinstalled

  • Triggers: Workflows can be triggered by specific events, such as push, and pull requests, or scheduled times

  • Actions: Are prebuilt tasks written in multiple languages that can be called in a workflow

  • Marketplace: Where developers can discover, and use actions and workflows created by the community

  • Secrets: Allow Devs to store and use sensitive information (credentials) securely in their workflows

  • Integration: Integrates with numerous tools and services, to streamline dev & deployment workflows

  • GitHub Plans: Personal (GitHub Free or GitHub Pro), Organizations (GitHub Team, GitHub Enterprise)

 

Ok, time to unveil the coffers of valuable git bits, curated from 35+ tabs of doc during my 2 weeks immersion.
Buckle up!

 

1. Default Runners

GitHub Actions has a variety of default runners with 3 OS allowing you to run your workflows on different platforms

  • Ubuntu: 20, 22.04 LTS
  • MacOS: 11,12,13
  • Windows: 2019,2022

Runners are VMs that come with preinstalled software that allows you to run any app or code in your pipeline

Language and Runtime

  • Bash, Node.js, Perl, Python, Ruby, Swift, Dash, C++, Julia, Kotlin, Mono, MSBuild

Package Management

  • Pip/pip3, Cpan, Helm, Yarn, Homebrew, Miniconda, Npm, NuGet, Pipx, RubyGems, Vcpkg

Tools

  • Git, SVN, Ansible, Packer, Terraform, Pulumi, Kubectl, Minikube, R, Heroku, Docker, Apt-fast, AzCopy

CLI Tools

  • AWS CLI, Azure CLI, GitHub CLI, Google Cloud SDK, Alibaba Cloud CLI, Hub CLI, OpenShift CLI, ORAS CLI, Vercel CLI

Other

  • JAVA, .NET Tools, Cached tools (Go, Node.js, Python, Pypy, Ruby), Cached Docker Images

  • PHP Tools, Haskell Tools, Rust Tools, PowerShell Tools, and modules

  • Browsers: Google Chrome, chromium, Microsoft Edge, Mozilla Firefox, Solenium Server

  • Databases: sqllite, MySQL, PostreSQL, MSSQL tools (sqlcmd, SQL package)

  • WebServers: Nginx, Apache2

  • Mobile OS: Android Command Line Tools, Android Emulator, Android SDK platforms, Google Play services, Google repo

 

2. Basic Structure of a Workflow

The basic structure of a GitHub Actions workflow consists of triggers, jobs, workflow syntax, and commands.

  • Triggers define the events that can trigger a workflow, such as pushes to the repository or pull requests

  • Jobs define the individual tasks or steps that need to be executed as part of the workflow

 

Example

 

    1. Name: Workflow Name 

    2. on: Events that trigger a workflow (push on the branch git_actions for any change in the paths section)

    3. env: Variables definition (hardcoded or imported from the environment such as secrets, vars)

    4. Permissions: To allow your actions to use the token_id

    5. Jobs section:

      • JobName, runner OS (runs-on), environment, default shell, and working directory

        • Steps section: Check out your code repo + other steps(run tests, build artifacts..)

      • Same for the next job…

 

3. How are public Actions used?

As shown below, these prebuilt tasks can be easily called in a workflow and are written in multiple languages. But public actions can’t be referenced from self-hosted runners. They are publicly stored in GitHub marketplace.

# example: “setup-node” action that downloads node.js and add it to the PATH steps: ... - uses: actions/setup-node@v3 with: node-version: 18

 

4. Are Public Actions Safe?

Public GitHub actions can also be risky to your security and privacy. For instance, malicious actions could steal your secrets, modify your code, or compromise your server. Even if the actions are not malicious, they could have vulnerabilities, or outdated dependencies that could affect your project.

Read more in this stackoverflow thread 
I don’t have time to check random people’s code for backdoors, so I only use actions from trusted sources, like official authentication actions from major cloud platforms and those made by GitHub.

 

5. Difference Between Public & Private Repositories

  • Beware: GitHub Actions logs of your public repo are visible to anyone as opposed to private ones

  • Features like Environments, environment secrets, and environment protection rules are available in public repositories for all GitHub plans (Free, Pro, Team, Enterprise)

  • For access to environments, environment secrets, and deployment branches in private or internal. repositories, you must use GitHub Pro, Team, or GitHub Enterprise

  • Actions and reusable workflows stored in private repositories cannot be used in public repositories

  • GitHub doesn’t allow individual accounts to use self-hosted runners on public repositories

  • You can share actions and reusable workflows from your private repo without making them public, by allowing GitHub workflows to access a private repository that contains the action or reusable workflow

 

6. GitHub Environment

 

  • The environment is an abstraction allowing differentiated deployments(dev, prod, staging), preventing unauthorized deployments, preserving secrets, tracking changes, and much more

  • You can use environment protection rules to require manual approval, delay a job, or restrict the environment to certain branches (i.e. dev, staging, dev).

  • It’s Available for all public repositories and private repositories for Pro, Team, and Enterprise accounts 

  • Referencing environment in Git actions has 3 scopes:

    1. The entire workflow, by using env at the top level of the workflow file.

    2. The contents of a job within a workflow, by using jobs.<job_id>.env.

    3. A specific step within a job, by using jobs.<job_id>.steps[*].env.

 

7. Environment Files

  • You can share custom environment variable with any subsequent steps in a workflow job by defining or updating the environment variable and writing this to the GITHUB_ENV environment file.

  • echo
    "{environment_variable_name}={value}"
    >> "$GITHUB_ENV"

  • For sharing environment between jobs or
    between workflows you will need GITHUB_OUTPUT

 

8. GitHub Actions Contexts and Variables

  • You can have a repository, environment, as well as intra-workflow (job, step) variable level

  • Context is a collection of variables describing workflow runs, runner environments, jobs, steps, secrets & much more. The context reference syntax is $

  • github reference information about the workflow run & events that triggered the run. i.e GitHub.repository

  • env Reference environment variable defined in the workflow.i.e $

  • vars context to access configuration variable values $

  • There’s a similar collection of variables called default variables within a runner (i.e. GITHUB_RUN_ID)

  • If you want to see all the information that GitHub Actions has in context, use the handy toJson function

      • Runner env variables are always interpolated on the runner VM. However, parts of a workflow are processed by GitHub Actions and are not sent to the runner

 

9. GitHub Actions Security (Secrets) 

  • In GitHub Actions, secrets are used to store sensitive information like passwords, API keys, tokens, etc.

  • There are organization, repository, and environment-level secrets.

  • In case of conflict, the organization is overridden by the repository, and the repository is overridden by env values.

    • Secret names can’t contain spaces, are not case-sensitive, & must be unique within the same level.

    • Secret names must not begin with the prefix GITHUB_.

    • secrets.GITHUB_TOKEN is a temporary token for each workflow run.

  • Exploitability and impact of untrusted input are real, read more in this excellent GitHub Securitylab post 

How safe is it on public repos and forks?

  • Secrets are safe to use in public repositories as they are automatically masked in build logs & show as *

  • But If in your workflow you create a credential from a secret (e.g. base64 an API key) then you should mask the new value so it doesn’t leak in the build log.

  • Except for GITHUB_TOKEN, secrets are not passed to the runner when a workflow is triggered from a forked repository.

  • Secrets are not automatically passed to reusable workflows.

  • If you have a public repository, make sure all outside collaborators’ PR requires approval.

What is a mask and is it really useful?

You can mask, or hide, any sensitive data in GitHub Actions logs by adding a new step add-mask in a Workflow.

Unfortunately, a variable will still be visible at least once before a mask is applied to it, and from then that value cannot be passed between runners. More examples here

 

10.  Job Dependency (Needs)

  • needs context: contains outputs from all jobs that are defined as a direct dependency of the current job

  • By default, all your jobs run in parallel in a workflow, but you often need them to run sequentially

  • Use needs to add dependency between jobs

  • Other needs variables

  • need.<job_id>: A single job that the current job depends on

  • needs.<job_id>.outputs: outputs of a job that the current job depends on

  • needs.<job_id>.result: The result of a job that the current job depends on. Possible values are success, failure, canceled, or skipped

 

11. Artifact vs. Caching

Artifacts:

  • Allow you to share data between running jobs and save them after the workflow is complete

  • An artifact is a file or collection of files produced during a workflow run

The Differences

  • Artifacts are used to save files after the workflow ended. (handy for Logs, manifest, state file, config file, Tests Results, Reports, etc)

  • Caching is used to re-use data/files between jobs or workflows (i.e sharing build dependencies files that don’t change often between jobs)    

Beware: Both artifacts and Caches are accessible to anyone in public repositories. An artifact can be downloaded, the cache can be reused

 

12. Approval Triggers Best Practice

  • The default behavior of environment protection rules is to set manual approval for first-time contributors

  • In that case, an attacker could create a simple and innocent pull request (like a documentation update)

  • When accepted, his subsequent pull request could be malicious and automatically trigger the workflow

  • So you need to set “Require approval for all outside collaborators” to ensure a more robust defense

 

  • Private Manual Approval: use the below action if you don’t have an Enterprise/Pro account but still want manual approval without the use of environments

 

13. Random Actions Tips

  • Step ID vs step name:

    • ID is used as a reference, from other jobs or steps (i.e., in jobs. <job_id>. needs)

    • The name is used for display purposes on GitHub

  • How to run commands without specifying a step name: run: echo “Run my shell command “

  • Why install custom software via actions vs use local one in the runner (example setup-terraform):

    • Terraform_setup action downloads the right version for you to ensure that updates to your infrastructure are safe and predictable (required_version).

    • The local version of Terraform might be too new for the required version and will fail Terraform init 

 

Conclusion:

  • That’s it, a long but very useful cheat sheet that helped me and hopefully helps you explore GH Actions

  • Again if your workflow is in a public repo, remember below safety measures

    • Don’t have any workflow with a pull_request trigger and never plan to make one.

    • Have the “Require approval for all outside collaborators” option for GitHub Actions turned on.

    • You must only invite trustworthy outside collaborators.

  • Next, I will write a series about Multicloud terraform deployments in GitHub actions