Securing CI/CD Pipelines: Implementing Approval Workflows in GitHub Actions

The integration of Continuous Integration and Continuous Delivery (CI/CD) into modern software development relies heavily on automation, yet automation introduces inherent risks when untrusted code executes in production environments. GitHub Actions has evolved from a simple automation tool into a comprehensive orchestration platform, addressing critical security and operational needs through features like workflow approval mechanisms. These mechanisms allow maintainers to enforce manual checkpoints before sensitive operations, such as deployments to production or releases to public repositories, are executed. The implementation of these controls varies based on the context of the workflow trigger, the structure of the repository, and the specific requirements for environmental protection.

Managing Forked Pull Request Approvals

One of the most common security scenarios in open-source and collaborative development involves contributions from external forks. When a contributor submits a pull request from a fork, the workflow runs triggered by that event may contain code that could potentially compromise the repository or its associated secrets. To mitigate this risk, GitHub provides a mechanism for manual approval of workflow runs originating from forks.

Workflow runs triggered by a contributor's pull request from a fork may require manual approval from a maintainer with write access. This requirement can be configured at the repository, organization, or enterprise level, allowing organizations to enforce consistent security policies across their entire infrastructure. If a workflow run awaits approval for more than 30 days, it is automatically deleted, preventing stale tasks from cluttering the interface or consuming resources indefinitely.

Maintainers with write access to a repository follow a specific procedure to review and approve these workflows. The process begins by navigating to the Pull requests tab under the repository name. After selecting the specific pull request, the maintainer clicks the Files changed tab to inspect the proposed modifications. This inspection is critical; maintainers must ensure they are comfortable running the workflows on the pull request branch. Particular attention must be paid to any proposed changes in the .github/workflows/ directory, as modifications to workflow files could alter the behavior of the pipeline itself.

Once the maintainer is satisfied with the integrity of the code, they proceed to the approval step. They click the button in the upper right corner labeled Awaiting approval, which opens the Merge status panel. Within this panel, the maintainer finds and clicks Approve workflows to run. This action explicitly authorizes the execution of the workflows associated with that specific pull request, ensuring that no unvetted code is executed in the repository’s environment.

Leveraging Environments for Approval Flows

While forked pull request approvals are managed through repository settings, internal workflow approvals are often managed through GitHub Environments. Environments provide a segmented definition within a repository, allowing administrators to associate specific secrets and protection rules with distinct stages of the deployment lifecycle, such as staging or production.

The core concept behind using environments for approval is the protection rule known as "Required reviewers." This feature allows users to define specific collaborators who must approve a workflow job before it can proceed. This functionality addresses the long-standing desire for a straightforward approval flow within GitHub Actions, similar to features found in Azure Pipelines. By implementing this, teams can enhance workflow control, ensuring smoother and more secure deployments.

To set up an environment-based approval workflow, administrators navigate to the repository settings and select the Environments section. They can create new environments, such as "staging" and "production," or specific environments for approval testing. For each environment, administrators can configure independent secrets and protection rules. To enable approvals, the administrator must check the Required reviewers checkbox and add specific users who have collaborator status in the repository.

Once the environment is configured with required reviewers, the workflow behavior changes significantly. When a workflow job associated with that environment is triggered, the run stops and waits for approval. The designated reviewers are notified through standard GitHub notification channels, such as email or in-app notifications. The notification typically includes a link to the workflow run, allowing the reviewer to inspect the context. The reviewer can then approve the workflow step, optionally adding comments to document the decision. Once approved, the job continues execution. This process empowers users to integrate approval steps directly into their CI/CD pipelines without requiring external tools or complex scripting.

Implementing Notifications and Automated Approval Triggers

For teams that require more granular control or external communication channels, approval workflows can be extended using custom notifications and issue-based triggers. This approach is particularly useful when approvals need to be routed through team communication platforms like Google Chat, Slack, or Microsoft Teams, or when the approval logic is tied to specific repository events.

A common pattern involves sending a notification to a team channel when a deployment to a protected environment is pending approval. This can be achieved by creating a workflow job that runs before the deployment step. For example, a job named notification can execute on an ubuntu-latest runner to send a message via a webhook.

yaml notification: runs-on: ubuntu-latest steps: - name: Notify on Google Chat run: | GOOGLE_CHAT_ROOM="xYO8qkAAAAE" MESSAGE="Deployment approval request. Click [here](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) to approve." curl -X POST -H 'Content-Type: application/json' \ -d '{ "text": "'"$MESSAGE"'" }' "${{ secrets.PROAPPROVAL }}"

In this example, the workflow constructs a message containing a direct link to the GitHub Actions run. The link allows reviewers to click through and approve the workflow step directly from the messaging platform. The curl command posts this message to a configured webhook URL, stored securely in the repository secrets as PROAPPROVAL. The target room or channel is defined by variables such as GOOGLE_CHAT_ROOM.

After the deployment step completes, a subsequent job, such as notify-after-deploy, can send a confirmation message to indicate that the deployment and approval process has finished.

yaml notify-after-deploy: runs-on: ubuntu-latest needs: build-and-deploy-prod steps: - name: Notify on Google Chat after deploy if: success() run: | GOOGLE_CHAT_ROOM="xYO8qkAAAAE" MESSAGE="Deployment and approval request is done." curl -X POST -H 'Content-Type: application/json' \ -d '{ "text": "'"$MESSAGE"'" }' "${{ secrets.PROAPPROVAL }}"

This pattern ensures that the team is aware of the status of the deployment, bridging the gap between the GitHub interface and daily communication tools. It is important to note that while some features are free for public repositories, advanced security features like environment protection rules with required reviewers may require a GitHub Enterprise Server subscription for private repositories.

Complex Workflows and Approval Scopes

As workflows become more complex, involving multiple jobs and dependencies, the scope of approval becomes a critical consideration. Users often encounter scenarios where a single workflow contains multiple jobs, each tied to an environment that requires approval. In such cases, the current setup may require approving parties to approve each job individually as it becomes ready to run.

This behavior can be inconvenient for teams that wish to approve an entire workflow deployment with a single action. For instance, a workflow might include a build job, a test job, and a deployment job. If the deployment job is tied to a protected environment, the approval is required at that specific job level. However, if multiple deployment jobs exist within the same workflow, each may require separate approval, leading to administrative overhead.

Users have explored ways to configure the workflow so that approval is required only once for the entire workflow, rather than per job. Attempts to tie the deployment environment to the entire workflow rather than job-by-job have not yielded straightforward solutions in standard documentation. This limitation highlights a gap in the current feature set for complex, multi-job workflows.

To work around this, some teams have implemented custom approval flows using external pipelines or by leveraging the issues event to trigger workflows. For example, a workflow can be configured to listen for the creation of an issue with a specific label. When a maintainer creates an issue titled "Release v1.0," the workflow compiles the application, creates packages, and waits for a manual trigger or approval before pushing to a public repository like NuGet. This approach provides a level of control against accidental pushes to the master branch, mimicking the simple approval flow found in Azure Pipelines where a single "approve" button releases the entire package.

Strategic Considerations for Approval Implementation

The implementation of approval workflows in GitHub Actions requires careful planning to balance security, efficiency, and developer experience. Key considerations include:

  • Environment Segmentation: Clearly define environments such as staging, production, and approval testing. Each environment should have independent secrets and protection rules tailored to its risk level.
  • Reviewer Selection: Add only trusted collaborators as required reviewers. Ensure that reviewers are available to handle approvals in a timely manner to avoid bottlenecks.
  • Notification Channels: Integrate notifications via email, in-app alerts, or external messaging platforms to ensure reviewers are promptly informed of pending approvals.
  • Workflow Complexity: For complex workflows with multiple jobs, consider consolidating jobs where possible to minimize the number of required approvals. If consolidation is not feasible, document the approval process clearly to avoid confusion.
  • Enterprise Features: Be aware that advanced security features, including environment protection rules with required reviewers, may require a GitHub Enterprise Server subscription for private repositories. Public repositories often have access to these features at no additional cost.

By understanding these nuances, teams can design approval workflows that enhance security without compromising the speed and flexibility of their CI/CD pipelines. The ability to integrate approvals directly into GitHub Actions, whether through environment protection rules or custom notification scripts, empowers developers to maintain strict control over their deployment processes.

Conclusion

The evolution of GitHub Actions to include robust approval mechanisms marks a significant step forward in secure software delivery. By supporting manual approvals for forked pull requests and enabling environment-based protection rules with required reviewers, GitHub has addressed critical security concerns in modern CI/CD pipelines. While challenges remain in managing approvals for complex, multi-job workflows, the current feature set provides powerful tools for teams to enforce strict controls over their deployments. As developers continue to adopt these features, the integration of custom notifications and external triggers will further enhance the flexibility and usability of approval workflows, ensuring that security and efficiency go hand in hand.

Sources

  1. Manage workflow runs - Approve runs from forks

  2. Adding Approval Workflow to Your GitHub Action

  3. Approval Workflows with GitHub Actions

  4. GitHub Community Discussion: Workflow Approval Scope

Related Posts