Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use reusable GitHub workflows and keep secrets in a single place?

While reusable GitHub workflows do help with maintenance of GitHub Actions in general, reducing a lot of copy/paste from one repository to another, they still seem to have one big issue: dealing with secrets.

When implementing an action like "post to Slack", or "post to matrix/IRC", you will need some secrets for the bot account, and if you want to reuse this action in 50 repositories you can imagine while managing secrets in each repository does not scale.

I am looking for a solution to this problem that does not involve deploying secrets to all repositories using an action, some way to centralize them.

Keep in mind that reusable workflows work across organizations and I already have some of them shared across 4+ organizations. So configuring organization level secrets is not a solution either, also for other reasons: they can easily be exposed because they are available to any workflow (as opposed to environment based ones).

like image 831
sorin Avatar asked Sep 02 '25 04:09

sorin


2 Answers

Check if the new (May 2022) keyword secrets: inherit can help:

GitHub Actions: Simplify using secrets with reusable workflows

GitHub Actions simplifies using secrets with reusable workflows with the secrets: inherit keyword.

Previously when passing secrets to a reusable workflow, you had to pass each secret as a separate argument.

Now you can simply pass the secrets: inherit to the reusable workflow and the secrets will be inherited from the calling workflow.

Learn more about reusable workflows in GitHub Actions and jobs.<job_id>.steps[*].uses.

In the reusable workflow, reference the input or secret that you defined in the on key in the previous step.

If the secrets are inherited using secrets: inherit, you can reference them even if they are not defined in the on key.

jobs:
 reusable_workflow_job:
   runs-on: ubuntu-latest
   environment: production
   steps:
     - uses: ./.github/workflows/my-action
       with:
         username: ${{ inputs.username }}
         token: ${{ secrets.envPAT }}

In the example above, envPAT is an environment secret that's been added to the production environment. This environment is therefore referenced within the job.

Note: Environment secrets are encrypted strings that are stored in an environment that you've defined for a repository.
Environment secrets are only available to workflow jobs that reference the appropriate environment.
For more information, see "Using environments for deployment."

Again, see jobs.<job_id>.steps[*].uses for additional examples.


As noted by wkhatch in the comments:

I don't believe this syntax is correct for using a reusable workflow, but is correct for using reusable actions.
You cannot call a reusable workflow as a step, but you can with an action.


As noted by Mickael V.'s comment, if you rely only on secrets: inherit without further organization, you would indeed have to set the secrets individually in all calling repositories... which does not scale well.

However, you can centralize secrets while still benefiting from secrets: inherit:

  • Create a dedicated repository (e.g., secrets-repo) for managing reusable workflows.
  • Store environment-level secrets only once, centrally, in this repository's environment (e.g., production).
  • Call reusable workflows hosted in this dedicated repository from multiple other repositories, using secrets: inherit, and specifying the central environment.

As an example of a reusable workflow in a central repository (using here slackapi/slack-github-action:

# .github/workflows/slack-notify.yml in central secrets-repo
name: Notify Slack

on:
  workflow_call:
    inputs:
      message:
        required: true
        type: string

jobs:
  notify_slack:
    runs-on: ubuntu-latest
    environment: production  # centralized secrets environment
    steps:
      - name: Send Slack notification
        uses: slackapi/[email protected]
        with:
          channel-id: ${{ secrets.SLACK_CHANNEL_ID }}
          slack-message: ${{ inputs.message }}
        env:
          SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}

You would call that workfrom from your other repositories (A, B, C, ...) like this:

jobs:
  notify:
    uses: my-org/secrets-repo/.github/workflows/slack-notify.yml@main
    secrets: inherit  # <===
    environment: production  # reference centralized secrets
    with:
      message: "Build successful!"

That way, you only define and manage secrets once, even if 50+ repos call the same reusable workflow.

As noted by wkhatch above, reusable workflows must be invoked at the job-level, whereas reusable actions are invoked at the step-level:

  • the reusable workflow (slack-notify.yml) is called at the job-level. There are no steps defined here, because you are calling the entire workflow, not a step. Reusable workflows are designed to be complete jobs (or sets of jobs) themselves, so they are invoked directly under jobs.
  • slackapi/slack-github-action is invoked within a step, which makes sense because it is a reusable action, not a workflow.
like image 148
VonC Avatar answered Sep 04 '25 23:09

VonC


This exact scenario is addressed in the GitHub roadmap issue GitHub Actions secrets improvements for Reusable workflows:

With this current improvement, teams managing reusable workflows can refer to the secrets from the called (source) repos. These secrets are available only in the reusable workflow run context within in the caller (target) repos.

Unfortunately, as of March 2024, the issue is scheduled for "Future"; there are columns up to Q4 2024.

like image 33
Benjamin W. Avatar answered Sep 05 '25 00:09

Benjamin W.