4.90.1 4.80.4 4.80.1 4.70.1 4.60.3 4.60.2 4.40.2 4.40.1 4.30.4
4.90.1 4.80.4 4.80.1 4.70.1 4.60.3 4.60.2 4.40.2 4.40.1 4.30.4

GitHub Actions Integration

Using a Signum Agent, a GitHub Actions signing workflow can run on any of the standard runners or their self-hosted equivalents.

This integration guide explains how to set up a GitHub Actions workflow by building a .NET project on a Windows runner, authenticating the user and publishing certificates, and signing the resulting binaries with SignTool. The guide is for the Windows Agent, but it is possible to adapt it to the other Agents as well.

Signing keys stay in Signum's HSM. The Agent exposes the certificates your account is entitled to through the Windows certificate store, so SignTool selects one by thumbprint exactly as if it were a local certificate. The private key operation is forwarded to Signum and the key never reaches the GitHub runner.

For setup with the legacy Windows Agent instead, see Using the Legacy Windows Agent.


Prerequisites

In Signum

  • A user account on the Signum instance, LocalUsers or Certificate domain type. See Domains.

  • A code-signing certificate in the Signum instance.

  • A Signum policy granting the user account the code-signing certificate for the SignTool app.

  • Thumbprint of the signing certificate.

  • Your Signum server URL (Deployment URL, without the <https://> prefix).

Certificate-based Authentication Option: As an alternative to using the CI user’s password as a GitHub Secret, Signum supports authenticating to an Agent with a client certificate instead. To use this, you need Set Up Certificate-based Authentication.

In GitHub

  • A GitHub repository with a buildable project (this guide uses .NET, but the signing steps are language-agnostic).

  • A Windows runner. Signing is a Windows operation: the Agent is a Windows service and SignTool is a Windows SDK tool.

  • Admin permissions on the repository to configure Secrets and Variables.

Runner Requirements

GitHub-hosted windows-latest runner meet Signum's baseline OS requirements out of the box. If you are using a self-hosted runner instead, match the OS-specific prerequisites for whichever OS you are targeting (Windows Server 2019+/Windows 11).


GitHub-hosted Runner (windows-latest)

Self-hosted Runner

Signum Server requirement:

Reachable from the public internet

Reachable from your network

Signum network policy must accept:

GitHub's runner IP ranges, which are broad and change

Your runner's address

Agent installation:

Every run, from a URL the runner can reach

Once, ahead of time

Agent installer distribution:

You must solve this (see below)

Not a workflow concern

Runner lifetime:

Ephemeral, clean state each run

Persistent, Agent session outlives the job


Install Agent onto the Hosted Runner

The .msi is distributed through the Signum SaaS portal, which requires authentication, so a workflow cannot download it directly. Either:

  • Republish the .msi to a location your runner can reach: an internal artifact repository, or blob storage. If the URL must carry a credential (a SAS token, a signed URL), store it as a secret, not a variable.

Or

  • Pre-install the Agent on a self-hosted runner, and have the workflow detect it and skip installation.

Use specific Agent version that matches your Signum server version rather the latest one.


Resources

This guide includes an example YAML pipeline configuration:

Resource

Link

YAML pipeline

minimal-build-and-sign.yml


Set Up .NET Project

Before writing your workflow YAML, store all sensitive values as GitHub Secrets and non-sensitive values as GitHub Variables.

Step 1 - Configure GitHub Secrets and Variables

  1. Navigate to your repository Settings > Secrets and variables > Actions.

  2. Create the following secrets:

It is not recommended to put credentials directly in the YAML file. Use GitHub Secrets for storing sensitive information.

Secret

Description

Example

Required

SIGNUM_PASSWORD

Password for the Signum service account

SuperSecretPassword

check mark

SIGNUM_AGENT_MSI_URL

URL to the Agent .msi, only if the installer URL carries a credential

https://artifacts.example.com/SignumAgent-x64.msi?sig=TOKEN

*

  1. Create the following variables:

Variable

Purpose

Example

Required

SIGNUM_PRIMARY_SERVER

Signum deployment URL with no https:// prefix

signum-server.domain.com

check mark

SIGNUM_USERNAME

Signum service account, in the form username@domain, where the domain is the Local User Domain Alias

user@domain.com

check mark

SIGNUM_CERT_THUMBPRINT

SHA-1 thumbprint of the code-signing certificate

CE1EB74EC5A8CC93EABA4066D15DDCBCEEF28EC2

check mark

SIGNUM_AGENT_MSI_URL

URL to the Agent .msi, only if the installer URL does not contain credentials

https://artifacts.example.com/SignumAgent-x64.msi

*

SIGNING_RUNNER

Runner label for the sign job

windows-latest


SIGNUM_TARGET_STORE

LocalMachine (adds signtool /sm) or My

LocalMachine


SIGNUM_SERVICE_PORT

Port the connectivity check probes

443


* Set SIGNUM_AGENT_MSI_URL as either a secret or variable, depending on if the URL contains a token or not.

Step 2 - Adjust the Trigger and Global Environment

Triggers shown in the following workflow (running on v* tags and manual dispatch) are only an example of when to run signing. Choose the right triggers for your pipeline, such as branches, tags, events, or environments should invoke signing.

Adjust the on: section to match your project's release process and your organization's policy on when signing should occur:

YAML
# Only on release tags
on:
  push:
    tags: ['v*']
# Manual only
on:
  workflow_dispatch:
# Push to main + manual
on:
  push:
    branches: [main]
  workflow_dispatch:
defaults:
  run:
    # Explicit so the scripts below behave the same on GitHub-hosted and
    # self-hosted Windows runners (PowerShell 7, not Windows PowerShell 5.1).
    shell: pwsh
env:
  DOTNET_VERSION: "8.0.x"
  CONFIGURATION: Release
  PUBLISH_DIR: artifacts/publish
  DOTNET_NOLOGO: "true"
  DOTNET_CLI_TELEMETRY_OPTOUT: "true"

Step 3 - Build .NET Project

The workflow follows the standard .NET steps: checkout, setup SDK, restore, build, and publish. This phase is independent of Signum and can be adapted to any technology.

The dotnet build step shown in the workflow is only an example, based on a simple .NET project, to illustrate where the build fits in the pipeline:

YAML
jobs:
  build:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-dotnet@v4
        with:
          dotnet-version: "8.0.x"
      - run: dotnet publish src/SignumSample.App/SignumSample.App.csproj -c Release -o $env:PUBLISH_DIR
      - uses: actions/upload-artifact@v4
        with:
          name: unsigned
          path: ${{ env.PUBLISH_DIR }}

Step 4 - Set Environment Variables

If you did not install the Agent on the runner, some environment variables must be set. This is related to the .NET configuration and depends on the build environment:

YAML
sign:
    needs: build
    runs-on: ${{ vars.SIGNING_RUNNER || 'windows-latest' }}

    env:
      SIGNUM_UTIL: C:\Program Files\Keyfactor\Signum\setup\signum-util.exe
      SIGNUM_SERVICE: C:\Program Files\Keyfactor\Signum\service\SignumService.exe
      SIGNUM_AGENT_MSI_URL: ${{ vars.SIGNUM_AGENT_MSI_URL }}
      SIGNUM_PRIMARY_SERVER: ${{ vars.SIGNUM_PRIMARY_SERVER }}
      SIGNUM_USERNAME: ${{ vars.SIGNUM_USERNAME }}
      SIGNUM_PASSWORD: ${{ secrets.SIGNUM_PASSWORD }}
      SIGNUM_CERT_THUMBPRINT: ${{ vars.SIGNUM_CERT_THUMBPRINT }}

    steps:
      - uses: actions/download-artifact@v4
        with:
          name: unsigned
          path: ${{ env.PUBLISH_DIR }}

Authenticate and Publish the Certificates

After setting up the .NET project, configure your Signum deployment with user authentication and certificate storage.

If you did not install the Agent on the runner, install the Agent now. For instructions, see Install Windows Agent.

Step 5 - Authenticate User

Run the following to authenticate the user and store the session:

YAML
signum-util setup -h $env:SIGNUM_PRIMARY_SERVER -u $env:SIGNUM_USERNAME -p $env:SIGNUM_PASSWORD

Setup Flag

Meaning

setup -h

Signum deployment URL. Do not include the https:// prefix

setup -u

Username as username@domain, where the domain is the Local User Domain Alias

setup -p

Password. Omit it to be prompted; a workflow has to pass it.

The signum-util setup takes the password as -p <value>, so it is visible in that process command line while the call runs. Reading it from the environment keeps it out of the workflow file and the run log, but not out of the process table.

setup -x

HTTPS proxy, when the runner reaches Signum through one

setup -l

CLI log level: NONE (default), LOW, MEDIUM, HIGH

Step 6 - Publish Certificates

Run the following to add the entitled certificates to the Windows certificate store, bound to the Signum KSP:

signum-util cs -a -t My

Certificate Store Flag

Meaning

cs -a

Add the available certificates to the Windows certificate store

cs -c

Remove the Signum certificates from the store

cs -t

Target store: My (current user) or LocalMachine (needs elevation). Repeatable. Default is My.

Choosing the target store

Choose the target store to match how the signing tool runs:

  • cs -t My puts the certificates in the current user's store and SignTool runs without /sm

  • cs -t LocalMachine puts certificates in the machine store and SignTool needs /sm.

The Agent runs per user, so My is the natural choice in CI, but depends on where the signing tool search for the certificates.


Sign Artifacts

With the certificate(s) visible in the store, native tools can sign binaries transparently. The signing command depends on the artifact type, not the CI platform.

Step 7 - Sign Artifacts with SignTool

With the certificates visible in the store, SignTool signs the binaries transparently through the Signum KSP:

YAML
  - name: Sign, verify and run
    run: |
      $signtool = (Get-ChildItem "C:\Program Files (x86)\Windows Kits\10\bin\*\x64\signtool.exe" | Sort-Object FullName)[-1].FullName
      $files = "$env:PUBLISH_DIR\SignumSample.App.exe", "$env:PUBLISH_DIR\SignumSample.Library.dll"

      & $signtool sign /fd SHA256 /sha1 $env:SIGNUM_CERT_THUMBPRINT /tr http://timestamp.digicert.com /td SHA256 /v @files

SignTool Flag

Description

/sm

Use the LocalMachine certificate store. Required only when the certificates were published with cs -t LocalMachine.

/n "CN"

Select certificate by subject name (resilient to renewals).

/sha1 THUMBPRINT

Select certificate by thumbprint (alternative to /n) .

/fd SHA256

Hash algorithm for the signature .

/td SHA256

Hash algorithm for the timestamp .

/t URL

RFC 3161 timestamp authority. Timestamping keeps signatures valid after the certificate expires.

/v

Verbose output .

Step 8 - Verify Signature (Optional)

Verify each signature. For SignTool, use signtool verify:

signtool.exe verify /pa /v @files

Step 9 - Upload Artifact

Upload the signed binaries as a GitHub Actions artifact:

YAML
- name: Upload signed artifacts
  uses: actions/upload-artifact@v4
  with:
    name: signed
    path: ${{ env.PUBLISH_DIR }}
    if-no-files-found: error

Step 10 - Cleanup

Log out of the Signum session and remove temporary files. This step uses if: always() so it runs even if a previous step fails:

YAML
  - name: Log the agent out
    if: always()
    run: |
      & $env:SIGNUM_UTIL logout
      exit 0

GitHub Actions appends exit to every pwsh step, so the explicit exit 0 prevents a failed logout from masking the real failure of the job.


Troubleshooting

The following solutions are for common issues that may occur. To find the logs for the Agents, see Logging.

Certificate Does Not Appear After Login

  • Verify that the user has a certificate assigned through a Signum policy.

  • Wait a few seconds after login. Add Start-Sleep -Seconds 10 in the workflow.

  • Check which store the certificates were installed into:

YAML
# Search in LocalMachine
Get-ChildItem Cert:\LocalMachine\My | Select-Object Subject, Thumbprint

# Search in CurrentUser
Get-ChildItem Cert:\CurrentUser\My | Select-Object Subject, Thumbprint

SignumService IPC endpoint is not reachable

Every signum-util command fails this way when no agent is serving the named pipe. In order of likelihood:

  • The Agent was never started. A silent install does not launch it and the per-user service cannot start without an interactive logon.

  • The Agent is running as a different Windows account than signum-util. The pipe is Keyfactor_Signum_<SID>; check that the account that started the Agent is the account running the CLI.

  • The Agent started and exited. Check the agent log, and the Windows Application log for an Application Error or Windows Error Reporting record naming SignumService.

  • The Agent is still starting. The example waits 10 seconds after launching it; a slow runner may need more.

  • To confirm what is being served, list the pipes and look for the one named after your SID:

[System.IO.Directory]::GetFiles("\\.\pipe\") | Where-Object { $_ -match "Signum" }
Test-Path "\\.\pipe\Keyfactor_Signum_$(([System.Security.Principal.WindowsIdentity]::GetCurrent()).User.Value)"

SignTool: "No certificates were found that met all the given criteria"

  • If using Server Mode, make sure you include the /sm flag in the SignTool command.

  • Verify that the /n value matches the certificate's CN exactly.

  • Check that the certificate has the Code Signing EKU (Enhanced Key Usage):

YAML
Get-ChildItem Cert:\LocalMachine\My | Where-Object {
    $_.EnhancedKeyUsageList.FriendlyName -contains "Code Signing"
} | Select-Object Subject, Thumbprint

Connection Error to the Signum Server

  • Verify that RTPRIMARY does not include the <https://> prefix.

  • If the runner is behind a proxy, configure the WEBPROXY_URI parameter in the MSI installation.

  • Review the installation log (signum-install.log) and RTService logs.

MSI Installation Fails

  • Review the log: Get-Content signum-install.log -Tail 80

  • Exit code 1603: generic failure, usually a permissions or prerequisites issue.

  • Exit code 3010: installation succeeded but requires a reboot (can be ignored in CI).

  • Ensure the runner has administrator privileges.

Signing is Slow

  • Each signature requires a network call to the remote HSM. This is expected to be slower than local signing.

  • If you are signing many files, consider signing only the main executables rather than all third-party DLLs.


Using the Legacy Windows Agent

To integrate your legacy Windows Agent with GitHub Actions, use Server Mode, which is a headless installation controlled entirely through the command line. The following information covers the installation and authentication of the legacy Windows Agent.

This guide includes a complete YAML pipeline configuration for the legacy Windows Agent:

Resource Description

Link

Legacy Windows Agent YAML pipeline

build-and-sign.yml

When configuring the .NET project, in addition to the other GitHub secrets and variables, create the following:

Secret

Purpose

Example

Required

SIGNUM_CLIENT_ID

Use the Client ID from the Signum SaaS portal



Variable

Purpose

Example

Required

SIGNUM_PRIMARY_SERVER

Signum deployment URL (RTPRIMARY)

signum-server.domain.com

check mark

SIGNUM_TARGET_STORE

LocalMachine (adds signtool /sm) or My

LocalMachine


SIGNUM_SERVICE_PORT

Port the connectivity check probes

443


Check Configuration (Optional)

Before continuing the GitHub action execution, it is recommended to perform a check. In this example, these tasks are performed by scripts that are in the same repository, but can be performed directly in the pipeline.

  1. Check out the workflow scripts:

YAML
- name: Check out the workflow scripts
  uses: actions/checkout@v4
  with:
    sparse-checkout: .github/scripts
    sparse-checkout-cone-mode: false
  1. Check required configuration:

YAML
  # -----------------------------------------------------------------------
  # Fail fast with a readable message when the repo is not configured yet,
  # instead of a cryptic msiexec or rtsetup error some steps later.
  # -----------------------------------------------------------------------
  - name: Check required configuration
    run: |
      $required = @(
        "SIGNUM_PRIMARY_SERVER",
        "SIGNUM_CLIENT_ID",
        "SIGNUM_USERNAME",
        "SIGNUM_PASSWORD",
        "SIGNUM_CERT_THUMBPRINT"
      )
      $missing = $required | Where-Object {
        [string]::IsNullOrWhiteSpace((Get-Item "env:$_" -ErrorAction SilentlyContinue).Value)
      }
      if ($missing) {
        throw "Missing repository configuration: $($missing -join ', '). " +
              "See README.md for which are variables and which are secrets."
      }
      Write-Host "Configuration present. Target certificate store: $env:SIGNUM_TARGET_STORE"
  1. Test connectivity to check that the runner can actually reach the Signum server before installing the Agent:

.github/scripts/Test-SignumConnectivity.ps1
    -Server "$env:SIGNUM_PRIMARY_SERVER"
    -Port $env:SIGNUM_SERVICE_PORT
    -TimeoutSeconds 10

Installation

For installation instructions, see Windows Agent - Server Mode (CLI Interface).

Wait for the Signum Agent Service

The msiexec returns before RTService finishes starting. Running rtsetup.exe too soon races the service, showing up as a confusing connection or login error, including on a pre-installed self-hosted runner, where the install step is skipped but the service could still be stopped.

Run the following:

YAML
# -----------------------------------------------------------------------
# Wait for the agent service to come up.
# -----------------------------------------------------------------------
- name: Wait for the Signum agent service
  run: >
    .github/scripts/Wait-AgentService.ps1
    -ServiceName RTService
    -TimeoutSeconds 120
    -AllowStart

Authenticate

Once the Agent is installed, use rtsetup.exe (located at C:\\Program Files\\KeyFactor\\) to authenticate the user. This starts the RTService and makes the assigned certificates appear in the Windows certificate store.

YAML
- name: Add Signum to PATH
  run: |
   $env:PATH = "C:\Program Files\KeyFactor\;" + $env:PATH
   echo "PATH=$env:PATH" >> $env:GITHUB_ENV

- name: Authenticate with Signum
  run: |
    echo "${{ secrets.SIGNUM_PASSWORD }}" | rtsetup.exe `
      -authMode=LocalUsers `
      -username="${{ secrets.SIGNUM_USERNAME }}" `
      -targetStore=LocalMachine

Confirm Certificate Visibility

YAML
# -----------------------------------------------------------------------
# Confirm the agent actually surfaced the expected certificate.
# -----------------------------------------------------------------------
- name: Verify the signing certificate is available
  run: >
    .github/scripts/Test-SigningCertificate.ps1
    -Thumbprint "$env:SIGNUM_CERT_THUMBPRINT"
    -TargetStore "$env:SIGNUM_TARGET_STORE"

Verify

YAML
- name: Verify certificate is present in store
  run: |
    $certs = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object {
            $_.Subject -like "${{ secrets.CERT_SUBJECT_NAME }}"
          }
    if (-not $certs) {
        Write-Host "::error::Certificate '${{ secrets.CERT_SUBJECT_NAME }}' not found in LocalMachine\\My"
        Write-Host "Available certificates in LocalMachine\\My:"
        Get-ChildItem -Path Cert:\\LocalMachine\\My | ForEach-Object {
          Write-Host "  Subject: $($_.Subject) | Thumbprint: $($_.Thumbprint)"
        }
        exit 1
      }
    foreach ($cert in $certs) {
        Write-Host "Certificate found:"
        Write-Host "  Subject:    $($cert.Subject)"
        Write-Host "  Thumbprint: $($cert.Thumbprint)"
        Write-Host "  Valid:      $($cert.NotBefore) - $($cert.NotAfter)"
      }