Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build: Sign windows builds via remote key storage (#15718) #15735

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 66 additions & 11 deletions .github/workflows/app-test-build-deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -184,24 +184,45 @@ jobs:
echo "both develop builds for edge"
echo 'variants=["release", "internal-release"]' >> $GITHUB_OUTPUT
echo 'type=develop' >> $GITHUB_OUTPUT
elif [ "${{ format('{0}', endsWith(github.ref, 'app-build-internal')) }}" = "true" ] ; then
echo "internal-release builds for app-build-internal suffixes"
elif [ "${{ format('{0}', contains(github.ref, 'app-build-internal')) }}" = "true" ] ; then

echo 'variants=["internal-release"]' >> $GITHUB_OUTPUT
echo 'type=develop' >> $GITHUB_OUTPUT
elif [ "${{ format('{0}', endsWith(github.ref, 'app-build')) }}" = "true" ] ; then
echo "release develop builds for app-build suffixes"
if [ "${{ format('{0}', contains(github.ref, 'as-release')) }}" = "true" ] ; then
echo "internal-release as-release builds for app-build-internal + as-release suffixes"
echo 'type=as-release' >> $GITHUB_OUTPUT
else
echo "internal-release develop builds for app-build-internal suffixes"
echo 'type=develop' >> $GITHUB_OUTPUT
fi
elif [ "${{ format('{0}', contains(github.ref, 'app-build')) }}" = "true" ] ; then
echo 'variants=["release"]' >> $GITHUB_OUTPUT
echo 'type=develop' >> $GITHUB_OUTPUT
elif [ "${{ format('{0}', endsWith(github.ref, 'app-build-both')) }}" = "true" ] ; then
echo "Both develop builds for app-build-both suffixes"
if [ "${{ format('{0}', contains(github.ref, 'as-release')) }}" = "true" ] ; then
echo "release as-release builds for app-build + as-release suffixes"
echo 'type=as-release' >> $GITHUB_OUTPUT
else
echo "release develop builds for app-build suffixes"
echo 'type=develop' >> $GITHUB_OUTPUT
fi
elif [ "${{ format('{0}', contains(github.ref, 'app-build-both')) }}" = "true" ] ; then

echo 'variants=["release", "internal-release"]' >> $GITHUB_OUTPUT
echo 'type=develop' >> $GITHUB_OUTPUT
if [ "${{ format('{0}', contains(github.ref, 'as-release')) }}" = "true" ] ; then
echo "Both as-release builds for app-build-both + as-release suffixes"
echo 'type=as-release' >> $GITHUB_OUTPUT
else
echo "Both develop builds for app-build-both + as-release suffixes"
echo 'type=develop' >> $GITHUB_OUTPUT
fi
else
echo "No build for ref ${{github.ref}} and event ${{github.event_type}}"
echo 'variants=[]' >> $GITHUB_OUTPUT
echo 'type=develop' >> $GITHUB_OUTPUT
fi

- name: set summary
run: |
echo 'Type: ${{steps.determine-build-type.outputs.type}} Variants: ${{steps.determine-build-type.outputs.variants}}' >> $GITHUB_STEP_SUMMARY

build-app:
needs: [determine-build-type]
if: needs.determine-build-type.outputs.variants != '[]'
Expand Down Expand Up @@ -276,15 +297,49 @@ jobs:
npm config set cache ${{ github.workspace }}/.npm-cache
yarn config set cache-folder ${{ github.workspace }}/.yarn-cache
make setup-js

- name: 'Configure Windows code signing environment'
if: startsWith(matrix.os, 'windows') && contains(needs.determine-build-type.outputs.type, 'release')
shell: bash
run: |
echo "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
echo "${{ secrets.WINDOWS_CSC_B64}}" | base64 --decode > /d/opentrons_labworks_inc.crt
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
echo "C:\Program Files\DigiCert\DigiCert Keylocker Tools" >> $GITHUB_PATH

- name: 'Setup Windows code signing helpers'
if: startsWith(matrix.os, 'windows') && contains(needs.determine-build-type.outputs.type, 'release')
shell: cmd
env:
SM_HOST: ${{ secrets.SM_HOST }}
SM_CLIENT_CERT_FILE: "D:\\Certificate_pkcs12.p12"
SM_CLIENT_CERT_PASSWORD: ${{secrets.SM_CLIENT_CERT_PASSWORD}}
SM_API_KEY: ${{secrets.SM_API_KEY}}
run: |
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/Keylockertools-windows-x64.msi/download -H "x-api-key:${{secrets.SM_API_KEY}}" -o Keylockertools-windows-x64.msi
msiexec /i Keylockertools-windows-x64.msi /quiet /qn
smksp_registrar.exe list
smctl.exe keypair ls
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
smksp_cert_sync.exe
smctl.exe healthcheck --all

# build the desktop app and deploy it
- name: 'build ${{matrix.variant}} app for ${{ matrix.os }}'
if: matrix.target == 'desktop'
timeout-minutes: 60
env:
OT_APP_MIXPANEL_ID: ${{ secrets.OT_APP_MIXPANEL_ID }}
OT_APP_INTERCOM_ID: ${{ secrets.OT_APP_INTERCOM_ID }}
WIN_CSC_LINK: ${{ secrets.OT_APP_CSC_WINDOWS }}
WIN_CSC_KEY_PASSWORD: ${{ secrets.OT_APP_CSC_KEY_WINDOWS }}
WINDOWS_SIGN: ${{ format('{0}', contains(needs.determine-build-type.outputs.type, 'release')) }}
SM_HOST: ${{secrets.SM_HOST}}
SM_CLIENT_CERT_FILE: "D:\\Certificate_pkcs12.p12"
SM_CLIENT_CERT_PASSWORD: ${{secrets.SM_CLIENT_CERT_PASSWORD}}
SM_API_KEY: ${{secrets.SM_API_KEY}}
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{secrets.SM_CODE_SIGNING_CERT_SHA1_HASH}}
SM_KEYPAIR_ALIAS: ${{secrets.SM_KEYPAIR_ALIAS}}
WINDOWS_CSC_FILEPATH: "D:\\opentrons_labworks_inc.crt"
CSC_LINK: ${{ secrets.OT_APP_CSC_MACOS }}
CSC_KEY_PASSWORD: ${{ secrets.OT_APP_CSC_KEY_MACOS }}
APPLE_ID: ${{ secrets.OT_APP_APPLE_ID }}
Expand Down
6 changes: 6 additions & 0 deletions app-shell/electron-builder.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
} = process.env
const DEV_MODE = process.env.NODE_ENV !== 'production'
const USE_PYTHON = process.env.NO_PYTHON !== 'true'
const WINDOWS_SIGN = process.env.WINDOWS_SIGN === 'true'
const project = process.env.OPENTRONS_PROJECT ?? 'robot-stack'

// this will generate either
Expand Down Expand Up @@ -72,6 +73,11 @@ module.exports = async () => ({
target: ['nsis'],
publisherName: 'Opentrons Labworks Inc.',
icon: project === 'robot-stack' ? 'build/icon.ico' : 'build/three.ico',
forceCodeSigning: WINDOWS_SIGN,
rfc3161TimeStampServer: 'http://timestamp.digicert.com',
sign: 'scripts/windows-custom-sign.js',
signDlls: true,
signingHashAlgorithms: ['sha256'],
},
nsis: {
oneClick: false,
Expand Down
62 changes: 62 additions & 0 deletions app-shell/scripts/windows-custom-sign.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// from https://github.com/electron-userland/electron-builder/issues/7605

'use strict'

const { execSync } = require('node:child_process')

exports.default = async configuration => {
const signCmd = `smctl sign --keypair-alias="${String(
process.env.SM_KEYPAIR_ALIAS
)}" --input "${String(configuration.path)}" --certificate="${String(
process.env.WINDOWS_CSC_FILEPATH
)}" --exit-non-zero-on-fail --failfast --verbose`
console.log(signCmd)
try {
const signProcess = execSync(signCmd, {
stdio: 'pipe',
})
console.log(`Sign success!`)
console.log(
`Sign stdout: ${signProcess?.stdout?.toString() ?? '<no output>'}`
)
console.log(
`Sign stderr: ${signProcess?.stderr?.toString() ?? '<no output>'}`
)
console.log(`Sign code: ${signProcess.code}`)
} catch (err) {
console.error(`Exception running sign: ${err.status}!
Process stdout:
${err?.stdout?.toString() ?? '<no output>'}
-------------
Process stderr:
${err?.stdout?.toString() ?? '<no output>'}
-------------
`)
throw err
}
const verifyCmd = `smctl sign verify --fingerprint="${String(
process.env.SM_CODE_SIGNING_CERT_SHA1_HASH
)}" --input="${String(configuration.path)}" --verbose`
console.log(verifyCmd)
try {
const verifyProcess = execSync(verifyCmd, { stdio: 'pipe' })
console.log(`Verify success!`)
console.log(
`Verify stdout: ${verifyProcess?.stdout?.toString() ?? '<no output>'}`
)
console.log(
`Verify stderr: ${verifyProcess?.stderr?.toString() ?? '<no output>'}`
)
} catch (err) {
console.error(`
Exception running verification: ${err.status}!
Process stdout:
${err?.stdout?.toString() ?? '<no output>'}
--------------
Process stderr:
${err?.stderr?.toString() ?? '<no output>'}
--------------
`)
throw err
}
}
Loading