Skip to content

Commit 54920bd

Browse files
authored
feat(package): sing windows binaries [NR-489681] (#1938)
1 parent 1c5a5b9 commit 54920bd

File tree

9 files changed

+240
-5
lines changed

9 files changed

+240
-5
lines changed

.github/workflows/component_packages.yml

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@ name: 📞 Build binaries and create packages
22

33
on:
44
workflow_call:
5+
secrets:
6+
gh_token:
7+
description: 'Github token for uploading packages to release'
8+
required: false
9+
gpg_private_key_base64:
10+
description: 'Private key for signing packages'
11+
required: false
12+
gpg_passphrase:
13+
description: 'Passphrase the GPG private key'
14+
required: false
15+
pfx_certificate_base64:
16+
description: 'Pfx for signing windows executables'
17+
required: false
18+
pfx_passphrase:
19+
description: 'Passphrase of the PFX certificate'
20+
required: false
521
inputs:
622
pre-release:
723
description: 'set to true if running a real pre-release'
@@ -82,18 +98,22 @@ jobs:
8298
if: ${{ inputs.skip_sign }}
8399
run: |
84100
echo SKIP_SIGN="--skip=sign" >> $GITHUB_ENV
101+
echo SKIP_WINDOWS_SIGN="true" >> $GITHUB_ENV
85102
86103
- name: Release packages with GoReleaser
87104
uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6
88105
with:
89106
args: release ${{ env.SKIP_UPLOAD_RELEASE }} ${{ env.SKIP_SIGN }} --clean --verbose --timeout 2h
90107
env:
91-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
92-
GPG_PASSPHRASE: ${{ secrets.OHAI_GPG_PASSPHRASE }}
93-
GPG_PRIVATE_KEY_BASE64: ${{ secrets.OHAI_GPG_PRIVATE_KEY_BASE64 }} # base64 encoded
108+
GITHUB_TOKEN: ${{ secrets.gh_token }}
109+
GPG_PRIVATE_KEY_BASE64: ${{ secrets.gpg_private_key_base64 }} # base64 encoded
110+
GPG_PASSPHRASE: ${{ secrets.gpg_passphrase }}
111+
PFX_CERTIFICATE_BASE64: ${{ secrets.pfx_certificate_base64 }} # base64 encoded
112+
PFX_PASSPHRASE: ${{ secrets.pfx_passphrase }}
94113
GPG_MAIL: 'infrastructure-eng@newrelic.com'
95114
NR_RELEASE_TAG: ${{ inputs.tag_name }}
96115
GORELEASER_CURRENT_TAG: ${{ inputs.tag_name }}
116+
SKIP_WINDOWS_SIGN: ${{ env.SKIP_WINDOWS_SIGN }}
97117

98118
- name: Upload assets to pipeline
99119
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
@@ -103,3 +123,25 @@ jobs:
103123
path: |
104124
./bin/*
105125
./dist/*
126+
127+
verify-windows-signatures:
128+
runs-on: windows-latest
129+
name: Verify Windows signatures
130+
needs: build
131+
if: ${{ ! inputs.skip_sign }}
132+
steps:
133+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
134+
135+
- name: Download built binaries
136+
uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
137+
with:
138+
name: built-binaries-${{ inputs.tag_name }}
139+
path: ./artifacts
140+
141+
- name: Verify Windows executable signatures
142+
shell: powershell
143+
run: |
144+
./build/scripts/windows-exec-sign/verify-signature.ps1 -Executables @(
145+
"./artifacts/dist/newrelic-agent-control-windows_x86_64-pc-windows-msvc/newrelic-agent-control.exe",
146+
"./artifacts/dist/newrelic-agent-control-cli-windows_x86_64-pc-windows-msvc/newrelic-agent-control-cli.exe"
147+
)

.github/workflows/nightly.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ jobs:
1818
with:
1919
pre-release: false
2020
tag_name: 0.100.${{ github.run_id }}
21-
secrets: inherit
21+
secrets:
22+
gh_token: ${{ secrets.GITHUB_TOKEN }}
23+
gpg_private_key_base64: ${{ secrets.OHAI_GPG_PRIVATE_KEY_BASE64 }}
24+
gpg_passphrase: ${{ secrets.OHAI_GPG_PASSPHRASE }}
25+
pfx_certificate_base64: ${{ secrets.OHAI_PFX_CERTIFICATE_BASE64 }}
26+
pfx_passphrase: ${{ secrets.OHAI_PFX_PASSPHRASE }}
2227

2328
build-image:
2429
name: Build and Push nightly image

.github/workflows/prerelease.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,12 @@ jobs:
1717
with:
1818
pre-release: true
1919
tag_name: ${{ github.event.release.tag_name }}
20-
secrets: inherit
20+
secrets:
21+
gh_token: ${{ secrets.GITHUB_TOKEN }}
22+
gpg_private_key_base64: ${{ secrets.OHAI_GPG_PRIVATE_KEY_BASE64 }}
23+
gpg_passphrase: ${{ secrets.OHAI_GPG_PASSPHRASE }}
24+
pfx_certificate_base64: ${{ secrets.OHAI_PFX_CERTIFICATE_BASE64 }}
25+
pfx_passphrase: ${{ secrets.OHAI_PFX_PASSPHRASE }}
2126

2227
build-image:
2328
name: Build and Push container image

.goreleaser.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ builds:
4848
- AGENT_CONTROL_VERSION={{ .Version }}
4949
- NEWRELIC_INFRA_AGENT_VERSION={{ .Env.NEWRELIC_INFRA_AGENT_VERSION }}
5050
- NR_OTEL_COLLECTOR_VERSION={{ .Env.NR_OTEL_COLLECTOR_VERSION }}
51+
hooks:
52+
post:
53+
- cmd: ./build/scripts/windows-exec-sign/sign.sh
54+
env:
55+
- SKIP_WINDOWS_SIGN={{ if index .Env "SKIP_WINDOWS_SIGN"}}{{ .Env.SKIP_WINDOWS_SIGN }}{{ else }}{{ end }}
56+
- PFX_CERTIFICATE_BASE64={{ if index .Env "PFX_CERTIFICATE_BASE64"}}{{ .Env.PFX_CERTIFICATE_BASE64 }}{{ else }}{{ end }}
57+
- PFX_PASSPHRASE={{ if index .Env "PFX_PASSPHRASE"}}{{ .Env.PFX_PASSPHRASE }}{{ else }}{{ end }}
58+
- EXECUTABLE={{ .Path }}
5159

5260
# Linux builds for CLI
5361
- id: newrelic-agent-control-cli-linux
@@ -83,6 +91,13 @@ builds:
8391
# Wait for newrelic-agent-control to be ready (parallel executions of cargo-xwin can be problematic)
8492
- cmd: sh -c 'while [ ! -f target/x86_64-pc-windows-msvc/release/newrelic-agent-control.exe ]; do echo "Waiting for newrelic-agent-control-windows build to complete..."; sleep 5; done'
8593
output: true
94+
post:
95+
- cmd: ./build/scripts/windows-exec-sign/sign.sh
96+
env:
97+
- SKIP_WINDOWS_SIGN={{ if index .Env "SKIP_WINDOWS_SIGN"}}{{ .Env.SKIP_WINDOWS_SIGN }}{{ else }}{{ end }}
98+
- PFX_CERTIFICATE_BASE64={{ if index .Env "PFX_CERTIFICATE_BASE64"}}{{ .Env.PFX_CERTIFICATE_BASE64 }}{{ else }}{{ end }}
99+
- PFX_PASSPHRASE={{ if index .Env "PFX_PASSPHRASE"}}{{ .Env.PFX_PASSPHRASE }}{{ else }}{{ end }}
100+
- EXECUTABLE={{ .Path }}
86101

87102
archives:
88103
- id: linux
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM debian:bullseye
2+
3+
RUN apt-get update \
4+
&& apt-get -y install \
5+
openssl \
6+
libengine-pkcs11-openssl \
7+
gnutls-bin \
8+
xxd \
9+
osslsigncode
10+
11+
ADD cmd.sh /cmd.sh
12+
13+
CMD ["/cmd.sh"]
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Obtain the certificate from base64
5+
echo "$PFX_CERTIFICATE_BASE64" | base64 -d > ./certificate.pfx
6+
7+
# Sign the binary with the osslsigncode tool
8+
osslsigncode sign \
9+
-pkcs12 ./certificate.pfx \
10+
-pass "$PFX_PASSPHRASE" \
11+
-n "$PFX_CERTIFICATE_DESCRIPTION" \
12+
-t http://timestamp.digicert.com \
13+
-in "$EXECUTABLE" \
14+
-out "$EXECUTABLE.signed"
15+
16+
# Clean up the certificate file
17+
rm -f ./certificate.pfx
18+
19+
# Replace the unsigned binary by the signed one
20+
mv "$EXECUTABLE.signed" "$EXECUTABLE"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/bin/bash
2+
3+
CURRENT_DIR="$( dirname $( readlink -f ${BASH_SOURCE[0]} ) )"
4+
LOCAL_DIR="$CURRENT_DIR/../../../local/testing-pfx-cert"
5+
IMAGE_NAME="testing-credentials"
6+
7+
rm -rf $LOCAL_DIR && mkdir $LOCAL_DIR
8+
9+
docker build -t $IMAGE_NAME "$CURRENT_DIR/."
10+
11+
docker run --rm -v $LOCAL_DIR:/workdir -w /workdir $IMAGE_NAME bash -c '
12+
# Generate a private key
13+
openssl genrsa -out private.key 2048
14+
15+
# Generate a self-signed certificate (valid for 365 days)
16+
openssl req -new -x509 -key private.key -out certificate.crt -days 365 \
17+
-subj "/C=US/ST=TestST/L=TestL/O=TestO Org/OU=TestOrg Unit/CN=test.org.site"
18+
19+
PFX_PASSPHRASE="TestPassword123"
20+
PFX_FILE="certificate.pfx"
21+
22+
# Convert to PFX format
23+
openssl pkcs12 -export -out $PFX_FILE \
24+
-inkey private.key \
25+
-in certificate.crt \
26+
-passout pass:$PFX_PASSPHRASE
27+
28+
# Encode as base64
29+
base64 $PFX_FILE > certificate_pfx_base64
30+
'
31+
32+
echo "Testing pfx certificate generated:"
33+
echo "PFX_CERTIFICATE_BASE64: $LOCAL_DIR/certificate_pfx_base64"
34+
echo "PFX_PASSPHRASE: TestPassword123"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Exit with no error if signing should be skipped
5+
if [ -n "$SKIP_WINDOWS_SIGN" ]; then
6+
echo "Skipping Windows executable signing (SKIP_WINDOWS_SIGN is set)"
7+
exit 0
8+
fi
9+
10+
# Check that required env variables are set
11+
if [ -z "$EXECUTABLE" ] || [ -z "$PFX_CERTIFICATE_BASE64" ] || [ -z "$PFX_PASSPHRASE" ]; then
12+
echo "EXECUTABLE, PFX_CERTIFICATE_BASE64 and PFX_PASSPHRASE env variables are required"
13+
exit 1
14+
fi
15+
16+
PFX_CERTIFICATE_DESCRIPTION="New Relic"
17+
18+
# Build the docker image for windows signing
19+
CURRENT_DIR="$( dirname $( readlink -f ${BASH_SOURCE[0]} ) )"
20+
IMAGE_NAME="exec-windows-signer"
21+
docker build -t "$IMAGE_NAME" "$CURRENT_DIR/."
22+
23+
# Sign the binary
24+
EXEC_PARENT_DIR="$(dirname "$EXECUTABLE")"
25+
EXEC_FILE_NAME="$(basename "$EXECUTABLE")"
26+
docker run --rm \
27+
-v "$EXEC_PARENT_DIR:/workdir" \
28+
-w /workdir \
29+
-e PFX_CERTIFICATE_BASE64="$PFX_CERTIFICATE_BASE64" \
30+
-e PFX_PASSPHRASE="$PFX_PASSPHRASE" \
31+
-e PFX_CERTIFICATE_DESCRIPTION="$PFX_CERTIFICATE_DESCRIPTION" \
32+
-e EXECUTABLE="$EXEC_FILE_NAME" \
33+
"$IMAGE_NAME"
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env pwsh
2+
#
3+
# Verify Windows executable signatures
4+
#
5+
# This script verifies that Windows executables are properly signed
6+
# with valid Authenticode signatures.
7+
#
8+
# Usage:
9+
# verify-signature.ps1 -Executables <exe1>,<exe2>,...
10+
#
11+
# Example:
12+
# verify-signature.ps1 -Executables "./artifacts/dist/foo.exe","./artifacts/dist/bar.exe"
13+
#
14+
15+
param(
16+
[Parameter(Mandatory=$true)]
17+
[string[]]$Executables
18+
)
19+
20+
Write-Host "Verifying signatures for Windows executables"
21+
Write-Host "=============================================="
22+
Write-Host ""
23+
24+
$allValid = $true
25+
26+
foreach ($exePath in $Executables) {
27+
$exeName = Split-Path -Leaf $exePath
28+
29+
Write-Host "Checking: $exeName"
30+
Write-Host " Path: $exePath"
31+
32+
if (-not (Test-Path $exePath)) {
33+
Write-Host " ERROR: File not found!" -ForegroundColor Red
34+
$allValid = $false
35+
Write-Host ""
36+
continue
37+
}
38+
39+
$signature = Get-AuthenticodeSignature -FilePath $exePath
40+
41+
Write-Host " Status: $($signature.Status)"
42+
43+
if ($signature.SignerCertificate) {
44+
Write-Host " Signer: $($signature.SignerCertificate.Subject)"
45+
Write-Host " Thumbprint: $($signature.SignerCertificate.Thumbprint)"
46+
}
47+
48+
if ($signature.Status -ne 'Valid') {
49+
Write-Host " ERROR: Signature is not valid!" -ForegroundColor Red
50+
if ($signature.StatusMessage) {
51+
Write-Host " Reason: $($signature.StatusMessage)" -ForegroundColor Red
52+
}
53+
$allValid = $false
54+
} else {
55+
Write-Host " SUCCESS: Signature is valid" -ForegroundColor Green
56+
}
57+
58+
Write-Host ""
59+
}
60+
61+
Write-Host "=============================================="
62+
if (-not $allValid) {
63+
Write-Host "FAILED: One or more executables are missing or have invalid signatures" -ForegroundColor Red
64+
exit 1
65+
}
66+
67+
Write-Host "SUCCESS: All Windows executables are properly signed" -ForegroundColor Green
68+
exit 0

0 commit comments

Comments
 (0)