Skip to content

Commit 72ad14a

Browse files
committed
adding post on GitHub Actions allow list as code
1 parent 9e45b08 commit 72ad14a

File tree

5 files changed

+259
-0
lines changed

5 files changed

+259
-0
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
---
2+
title: 'Implementing a GitHub Actions Allow List as Code'
3+
author: Josh Johanning
4+
date: 2025-08-21 09:00:00 -0500
5+
description: How to manage GitHub Actions allow lists using configuration as code for an improved management and user experience
6+
categories: [GitHub, Actions]
7+
tags: [GitHub, GitHub Actions, Policy Enforcement, Configuration as Code]
8+
media_subpath: /assets/screenshots/2025-08-21-github-actions-allow-list-as-code
9+
image:
10+
path: actions-allow-list-as-code-light.png
11+
width: 100%
12+
height: 100%
13+
alt: GitHub Actions Allow List as Code workflow
14+
mermaid: true
15+
---
16+
17+
## Overview
18+
19+
As organizations scale and mature their use of GitHub Actions, maintaining oversight of action usage becomes increasingly important. While GitHub provides native [Actions permissions settings](https://docs.github.com/en/organizations/managing-organization-settings/disabling-or-limiting-github-actions-for-your-organization#allowing-select-actions-and-reusable-workflows-to-run), managing these through the UI can become cumbersome, error-prone, and developers have no idea which actions they're actually allowed to use.
20+
21+
This post explores how to implement a GitHub Actions allow list using configuration as code which is version controlled, provides self-service visibility, and has a built-in request and approval process natively through pull requests.
22+
23+
I'm going to be using my [actions-allow-list-as-code](https://github.com/joshjohanning-org/actions-allow-list-as-code) repository as the example implementation. This is the same repository I use to manage my own GitHub Actions allow list, so you can see it in action! 🚀
24+
25+
> If you're already sold on this idea and just want to get started, skip to the [TLDR / Getting Started](#tldr--getting-started) section below for the quick and dirty implementation details!
26+
{: .prompt-tip }
27+
28+
## Why Configuration as Code?
29+
30+
Before we talk about the benefits of this method, let's quickly review the GitHub Actions permission options (these can be defined at the enterprise, organization, or repository level):
31+
32+
> - Allow all actions and reusable workflows
33+
> - *Increasingly less common for security-conscious organizations*
34+
> - Allow enterprise actions and reusable workflows
35+
> - *Not as common; don't allow any marketplace actions including GitHub's own actions*
36+
> - ⭐️ **Allow enterprise, and select non-enterprise, actions and reusable workflows**
37+
> - *This is becoming increasingly more common since organizations want to maintain better control over their third-party actions usage*
38+
39+
> As of [August 2025](https://github.blog/changelog/2025-08-15-github-actions-policy-now-supports-blocking-and-sha-pinning-actions/), there's a new setting below these to "Require actions to be pinned to a full-length commit SHA" which is the best practice to prevent against supply chain attacks.
40+
{: .prompt-tip }
41+
42+
We're going to be focusing on the last option, which allows you to specify a list of approved actions and reusable workflows. This is where this setting shines when combined with configuration as code.
43+
44+
![GitHub Actions permission settings](actions-allow-list-light.png){: .light }
45+
![GitHub Actions permission settings](actions-allow-list-dark.png){: .dark }
46+
_GitHub Actions permission settings - with the "Allow enterprise, and select non-enterprise, actions and reusable workflows" setting checked_
47+
48+
The challenge with managing allow lists through the GitHub UI is that it has several drawbacks:
49+
50+
1. **Lack of self-service visibility**: Developers can't easily see which actions are approved - so you most likely have to duplicate this list into documentation or a wiki page anyways
51+
2. **No approval process**: Changes are made directly without review by organization owners or [CI/CD admins](https://docs.github.com/en/organizations/managing-peoples-access-to-your-organization-with-roles/roles-in-an-organization#about-pre-defined-organization-roles) - and have to build out a separate process for developers to request new actions
52+
3. **No audit trail**: Limited visibility into when and why actions were added/removed (without digging up the audit log)
53+
4. **Scalability issues**: Managing hundreds of approved actions becomes unwieldy (better not miss a comma!)
54+
5. **No context**: No way to document why certain actions were approved or rejected
55+
56+
## Implementation Walkthrough
57+
58+
Now, let's walk through implementing the configuration as code solution using the [actions-allow-list-as-code](https://github.com/joshjohanning-org/actions-allow-list-as-code) repository. We'll take the different components step-by-step to see how they work together to create a robust allow list management system.
59+
60+
### Allow List File Structure
61+
62+
The heart of this approach is a simple YAML configuration file that lists all approved actions:
63+
64+
```yml
65+
actions:
66+
- actionsdesk/github-actions-allow-list-as-code-action@* # have to allow this action to manage the allow list!
67+
- joshjohanning/*
68+
- azure/login@v2*
69+
- issue-ops/parser*
70+
- docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1 # v3.5.0
71+
```
72+
{: file='github-actions-allow-list.yml'}
73+
74+
This simple format allows for:
75+
76+
- Wildcards (`*`) for allowing all versions of an action (`issue-ops/parser*`)
77+
- Specific version pinning (`@v1`, `@main`)
78+
- Organization-wide approvals (`joshjohanning/*`)
79+
- Specific commit SHA pinning (`docker/login-action@184bdaa0721073962dff0199f1fb9940f07167d1`)
80+
- Documentation comments to provide context (add comments after the action with `#`)
81+
82+
### Actions Workflow
83+
84+
The automation workflow handles validation and deployment of the allow list:
85+
86+
{% raw %}
87+
```yaml
88+
name: 🚀🔐 Deploy Actions allow list
89+
90+
on:
91+
push:
92+
branches: [main]
93+
paths:
94+
- github-actions-allow-list.yml
95+
- .github/workflows/actions-allow-list.yml
96+
pull_request:
97+
branches: [ main ]
98+
workflow_dispatch:
99+
100+
jobs:
101+
run:
102+
runs-on: ubuntu-latest
103+
104+
permissions: read-all
105+
106+
steps:
107+
- name: Checkout
108+
uses: actions/checkout@v5
109+
110+
- name: validate yml
111+
run: |
112+
if yq eval github-actions-allow-list.yml; then
113+
echo "Actions YML is valid"
114+
else
115+
echo "Actions YML validation failed"
116+
exit 1
117+
fi
118+
119+
- uses: actions/create-github-app-token@v2
120+
if: github.event_name != 'pull_request'
121+
id: app-token
122+
with:
123+
app-id: ${{ vars.APP_ID }}
124+
private-key: ${{ secrets.PRIVATE_KEY }}
125+
owner: ${{ github.repository_owner }}
126+
127+
# if using Enterprise, use the `/enterprises/<enterprise-slug>` endpoint
128+
# and PAT - can't use GitHub app at Enterprise at Enterprise level
129+
# setting this in case someone changed the settings in the UI manually
130+
- name: Enable Actions Policy in Org
131+
if: github.event_name != 'pull_request'
132+
env:
133+
GH_TOKEN: ${{ steps.app-token.outputs.token }} # replace this if using PAT
134+
run: |
135+
gh api -X PUT /orgs/${{ github.repository_owner }}/actions/permissions \
136+
-f enabled_repositories='all' \
137+
-f allowed_actions='selected'
138+
139+
gh api -X PUT /orgs/${{ github.repository_owner }}/actions/permissions/selected-actions \
140+
-F github_owned_allowed=true \
141+
-F verified_allowed=true
142+
143+
- name: Deploy GitHub Actions allow list
144+
if: github.event_name != 'pull_request'
145+
uses: ActionsDesk/github-actions-allow-list-as-code-action@013d3b0b014f3a7656c5b0a28c00fe8c7e41b5e3 # v3.1.0
146+
with:
147+
token: ${{ steps.app-token.outputs.token }} # replace this if using PAT
148+
organization: ${{ github.repository_owner }}
149+
allow_list_path: github-actions-allow-list.yml
150+
```
151+
{: file='.github/workflows/actions-allow-list.yml'}
152+
{% endraw %}
153+
154+
Key features of this workflow:
155+
156+
1. **Pull Request Support & YML Validation**: Validates the YML without deploying the changes
157+
2. **GitHub App Authentication**: Uses a GitHub App for secure, auditable access (you could also use a Personal Access Token - and if you're trying to apply this at the Enterprise level, you would need to use a PAT since GitHub Apps can't be used at the Enterprise level yet)
158+
3. **Automated Deployment**: Only runs on pushes to main branch, and only triggered by changes to the allow list file or workflow file
159+
4. **Sets/Resets the Actions Policy**: Ensures the Actions policy is set to "selected actions" in case someone changed it manually in the UI
160+
161+
> I'm using a GitHub App here for authentication (with Organization > Administration permissions set to Read & Write) rather than a personal access token. This provides better security, auditing, and doesn't consume a license. Check out my [other post on GitHub Apps](/posts/github-apps/) if you're interested in learning more! 🚀
162+
{: .prompt-info }
163+
164+
### Pull Request Template
165+
166+
The real power here is the built-in approval process through something that all GitHub users are probably already familiar with: pull requests. When someone wants to add a new action to the allow list, they create a pull request with the proposed changes.
167+
168+
To streamline the approval process, you can create a [pull request template](https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository) that guides contributors through providing the necessary information. Here's an example:
169+
170+
```md
171+
## 🚀 Action Addition Request
172+
173+
### 📝 Action Details
174+
- **Action Name**:
175+
- **Version(s)**:
176+
- **Publisher**: (GitHub user/org or Marketplace publisher)
177+
- **Action Repository**: (link to source code)
178+
179+
### 💡 Justification
180+
- **Use Case**: Describe how this action will be used
181+
- **Business Justification**: Why is this action needed?
182+
- **Security Review**:
183+
- [ ] Action is from a verified publisher
184+
- [ ] Action source code has been reviewed
185+
- [ ] No security concerns identified
186+
- **Alternatives Considered**: List any alternative actions evaluated
187+
188+
### ✅ Approval Checklist
189+
- [ ] Action serves a legitimate business purpose
190+
- [ ] Publisher is trusted (GitHub user/org or verified Marketplace publisher)
191+
- [ ] Version pinning strategy is appropriate
192+
- [ ] Documentation/README is clear and professional
193+
- [ ] Action follows security best practices
194+
195+
### 🧪 Testing
196+
- [ ] Action has been tested in a sandbox environment
197+
- [ ] Action works as expected
198+
- [ ] No unexpected side effects observed
199+
200+
### 👥 Reviewers
201+
Please tag the appropriate teams for review:
202+
- [ ] @your-org/security-team (required for all Marketplace actions) 🔒
203+
- [ ] @your-org/platform-team (required for infrastructure-related actions) ⚙️
204+
205+
### Additional Notes
206+
Add any additional context, concerns, or considerations here.
207+
```
208+
{: file='.github/pull_request_template.md'}
209+
210+
### CODEOWNERS
211+
212+
While you're not required to use a CODEOWNERS file, it can be helpful to automatically request reviews from the right teams when someone creates a pull request to add a new action. Here's an example:
213+
214+
```md
215+
# CODEOWNERS file for GitHub Actions allow list
216+
CODEOWNERS @joshjohanning-org/actions-approver-team
217+
github-actions-allow-list.yml @joshjohanning-org/actions-approver-team
218+
.github/workflows/actions-allow-list.yml @joshjohanning-org/actions-approver-team
219+
.github/dependabot.yml @joshjohanning-org/actions-approver-team
220+
README.md @joshjohanning-org/actions-approver-team
221+
```
222+
{: file='.github/CODEOWNERS'}
223+
224+
## TLDR / Getting Started
225+
226+
To implement this solution in your organization:
227+
228+
1. **Set up the YML file with approved actions**: Add in your approved actions ([example](https://github.com/joshjohanning-org/actions-allow-list-as-code/blob/main/github-actions-allow-list.yml))
229+
2. **Set Up GitHub App (recommended if using this for org) or Personal Access Token (for Enterprise)**: Set up a token and grant the necessary permissions
230+
- If GitHub App: Organization > Administration permissions set to Read & Write
231+
- If using a Personal Access Token, `admin:enterprise` or `admin:org` depending on your use case
232+
3. **Configure Workflow**: See my [example](https://github.com/joshjohanning-org/actions-allow-list-as-code/blob/main/.github/workflows/actions-allow-list.yml), noting to modify the `app-id` and `private-key` secrets to match your GitHub App
233+
- If using a Personal Access Token, remove the `actions/create-github-app-token` step and update the subsequent steps to use the secret directly from the secret store
234+
4. **Create PR Template** (optional): Add a pull request template for new action requests ([example](https://github.com/joshjohanning-org/actions-allow-list-as-code/blob/main/.github/pull_request_template.md))
235+
5. **Use CODEOWNERS** (optional): Automatically request reviews from the right teams when someone creates a pull request to add a new action ([example](https://github.com/joshjohanning-org/actions-allow-list-as-code/blob/main/CODEOWNERS))
236+
237+
## Summary
238+
239+
If your organization is looking to manage GitHub Actions allow lists more effectively, implementing this configuration as code approach provides significant benefits over manual UI-based management. Whether it's this approach, [Terraform](https://registry.terraform.io/providers/integrations/github/latest/docs/resources/actions_organization_permissions), or rolling your own, the benefits include:
240+
241+
- **Increased self-service visibility** through version-controlled configuration
242+
- **Built-in approval process** via pull requests
243+
- **Automation & validation** that eliminates manual work
244+
- **Auditability** with complete change history in Git
245+
246+
To help illustrate the flow, I created a mermaid diagram of high-level process process from end-to-end. Happy automating! 🚀
247+
248+
```mermaid
249+
flowchart TD
250+
A[Developer wants new action] --> B[Creates PR with action request]
251+
B --> C[Security/Platform team reviews]
252+
C --> D{Approved?}
253+
D -->|Yes| E[PR merged to main]
254+
D -->|No| F[PR closed with feedback]
255+
E --> G[Workflow validates YAML]
256+
G --> H[GitHub App deploys allow list]
257+
H --> I[Action available organization-wide]
258+
F --> A
259+
```
164 KB
Loading
171 KB
Loading
194 KB
Loading
201 KB
Loading

0 commit comments

Comments
 (0)