Skip to content

Commit 3e119c3

Browse files
feat(windows): integrate msi based releases
Signed-off-by: Swarit Pandey <swarit@stepsecurity.io>
1 parent 09dc2a2 commit 3e119c3

13 files changed

Lines changed: 1010 additions & 19 deletions

File tree

.github/workflows/release.yml

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,40 @@ jobs:
8585
- name: Install cosign
8686
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
8787

88+
- name: Build MSIs (x64 + arm64)
89+
# WiX 4 runs on .NET, so we install it on the same ubuntu-latest
90+
# runner. Output: dist/stepsecurity-dev-machine-guard-<v>-x64.msi
91+
# and the arm64 counterpart. Both wrap the goreleaser-produced
92+
# .exe and embed it via CAB compression. No PowerShell anywhere.
93+
run: |
94+
dotnet tool install --global wix --version 4.0.5
95+
export PATH="$PATH:$HOME/.dotnet/tools"
96+
wix --version
97+
98+
version="${{ steps.version.outputs.version }}"
99+
100+
win_amd64_exe=$(find dist -type f -name '*.exe' -path '*windows_amd64*' | head -1)
101+
win_arm64_exe=$(find dist -type f -name '*.exe' -path '*windows_arm64*' | head -1)
102+
if [ ! -f "$win_amd64_exe" ] || [ ! -f "$win_arm64_exe" ]; then
103+
echo "::error::Windows .exe artifacts not found under dist/"
104+
find dist -type f -name '*.exe'
105+
exit 1
106+
fi
107+
108+
wix build packaging/windows/Product.wxs \
109+
-arch x64 \
110+
-d Arch=x64 \
111+
-d Version="$version" \
112+
-d BinaryPath="$PWD/$win_amd64_exe" \
113+
-out "dist/stepsecurity-dev-machine-guard-${version}-x64.msi"
114+
115+
wix build packaging/windows/Product.wxs \
116+
-arch arm64 \
117+
-d Arch=arm64 \
118+
-d Version="$version" \
119+
-d BinaryPath="$PWD/$win_arm64_exe" \
120+
-out "dist/stepsecurity-dev-machine-guard-${version}-arm64.msi"
121+
88122
- name: Locate binaries and packages
89123
id: binaries
90124
run: |
@@ -99,7 +133,10 @@ jobs:
99133
RPM_AMD64=$(find dist -type f -name '*-amd64.rpm' | head -1)
100134
RPM_ARM64=$(find dist -type f -name '*-arm64.rpm' | head -1)
101135
102-
for label in "darwin:${DARWIN}" "windows_amd64:${WIN_AMD64}" "windows_arm64:${WIN_ARM64}" "linux_amd64:${LINUX_AMD64}" "linux_arm64:${LINUX_ARM64}" "deb_amd64:${DEB_AMD64}" "deb_arm64:${DEB_ARM64}" "rpm_amd64:${RPM_AMD64}" "rpm_arm64:${RPM_ARM64}"; do
136+
MSI_X64=$(find dist -type f -name '*-x64.msi' | head -1)
137+
MSI_ARM64=$(find dist -type f -name '*-arm64.msi' | head -1)
138+
139+
for label in "darwin:${DARWIN}" "windows_amd64:${WIN_AMD64}" "windows_arm64:${WIN_ARM64}" "linux_amd64:${LINUX_AMD64}" "linux_arm64:${LINUX_ARM64}" "deb_amd64:${DEB_AMD64}" "deb_arm64:${DEB_ARM64}" "rpm_amd64:${RPM_AMD64}" "rpm_arm64:${RPM_ARM64}" "msi_x64:${MSI_X64}" "msi_arm64:${MSI_ARM64}"; do
103140
name="${label%%:*}"
104141
path="${label#*:}"
105142
if [ -z "$path" ] || [ ! -f "$path" ]; then
@@ -118,6 +155,8 @@ jobs:
118155
echo "deb_arm64=$DEB_ARM64" >> "$GITHUB_OUTPUT"
119156
echo "rpm_amd64=$RPM_AMD64" >> "$GITHUB_OUTPUT"
120157
echo "rpm_arm64=$RPM_ARM64" >> "$GITHUB_OUTPUT"
158+
echo "msi_x64=$MSI_X64" >> "$GITHUB_OUTPUT"
159+
echo "msi_arm64=$MSI_ARM64" >> "$GITHUB_OUTPUT"
121160
122161
- name: Sign artifacts with Sigstore
123162
shell: bash
@@ -156,6 +195,20 @@ jobs:
156195
"${{ steps.binaries.outputs.rpm_amd64 }}.bundle"
157196
sign_with_retry "${{ steps.binaries.outputs.rpm_arm64 }}" \
158197
"${{ steps.binaries.outputs.rpm_arm64 }}.bundle"
198+
sign_with_retry "${{ steps.binaries.outputs.msi_x64 }}" \
199+
"${{ steps.binaries.outputs.msi_x64 }}.bundle"
200+
sign_with_retry "${{ steps.binaries.outputs.msi_arm64 }}" \
201+
"${{ steps.binaries.outputs.msi_arm64 }}.bundle"
202+
203+
- name: Upload MSIs to draft release
204+
env:
205+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
206+
run: |
207+
gh release upload "${{ steps.release.outputs.tag }}" \
208+
"${{ steps.binaries.outputs.msi_x64 }}" \
209+
"${{ steps.binaries.outputs.msi_arm64 }}" \
210+
--clobber
211+
159212
- name: Upload cosign bundles
160213
env:
161214
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -170,6 +223,8 @@ jobs:
170223
"${{ steps.binaries.outputs.deb_arm64 }}.bundle" \
171224
"${{ steps.binaries.outputs.rpm_amd64 }}.bundle" \
172225
"${{ steps.binaries.outputs.rpm_arm64 }}.bundle" \
226+
"${{ steps.binaries.outputs.msi_x64 }}.bundle" \
227+
"${{ steps.binaries.outputs.msi_arm64 }}.bundle" \
173228
--clobber
174229
175230
- name: Attest build provenance
@@ -185,3 +240,5 @@ jobs:
185240
${{ steps.binaries.outputs.deb_arm64 }}
186241
${{ steps.binaries.outputs.rpm_amd64 }}
187242
${{ steps.binaries.outputs.rpm_arm64 }}
243+
${{ steps.binaries.outputs.msi_x64 }}
244+
${{ steps.binaries.outputs.msi_arm64 }}

Makefile

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,42 @@ LDFLAGS := -s -w \
99
-X $(MODULE)/internal/buildinfo.ReleaseTag=$(TAG) \
1010
-X $(MODULE)/internal/buildinfo.ReleaseBranch=$(BRANCH)
1111

12-
.PHONY: build build-windows build-linux deploy-windows test lint clean smoke
12+
.PHONY: build build-windows build-windows-arm64 build-linux deploy-windows test lint clean smoke build-msi-amd64 build-msi-arm64
1313

1414
build:
1515
go build -trimpath -ldflags "$(LDFLAGS)" -o $(BINARY) ./cmd/stepsecurity-dev-machine-guard
1616

1717
build-windows:
1818
GOOS=windows GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o $(BINARY).exe ./cmd/stepsecurity-dev-machine-guard
1919

20+
build-windows-arm64:
21+
GOOS=windows GOARCH=arm64 go build -trimpath -ldflags "$(LDFLAGS)" -o $(BINARY)-arm64.exe ./cmd/stepsecurity-dev-machine-guard
22+
2023
build-linux:
2124
GOOS=linux GOARCH=amd64 go build -trimpath -ldflags "$(LDFLAGS)" -o $(BINARY)-linux ./cmd/stepsecurity-dev-machine-guard
2225

26+
# MSI builds. Require WiX 4 on PATH: `dotnet tool install --global wix --version 4.0.5`.
27+
# Output: dist/stepsecurity-dev-machine-guard-<version>-{x64,arm64}.msi
28+
# Reads Version from internal/buildinfo so MajorUpgrade semantics line up
29+
# with whatever the binary reports as `--version`.
30+
build-msi-amd64: build-windows
31+
mkdir -p dist
32+
wix build packaging/windows/Product.wxs \
33+
-arch x64 \
34+
-d Arch=x64 \
35+
-d Version=$(VERSION) \
36+
-d BinaryPath=$(CURDIR)/$(BINARY).exe \
37+
-out dist/stepsecurity-dev-machine-guard-$(VERSION)-x64.msi
38+
39+
build-msi-arm64: build-windows-arm64
40+
mkdir -p dist
41+
wix build packaging/windows/Product.wxs \
42+
-arch arm64 \
43+
-d Arch=arm64 \
44+
-d Version=$(VERSION) \
45+
-d BinaryPath=$(CURDIR)/$(BINARY)-arm64.exe \
46+
-out dist/stepsecurity-dev-machine-guard-$(VERSION)-arm64.msi
47+
2348
deploy-windows:
2449
@bash scripts/deploy-windows.sh $(DEPLOY_ARGS)
2550

@@ -30,7 +55,8 @@ lint:
3055
golangci-lint run ./...
3156

3257
clean:
33-
rm -f $(BINARY) $(BINARY).exe $(BINARY)-linux
58+
rm -f $(BINARY) $(BINARY).exe $(BINARY)-arm64.exe $(BINARY)-linux
59+
rm -rf dist/
3460

3561
smoke: build
3662
bash tests/test_smoke_go.sh

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
469469
- [Changelog](CHANGELOG.md)
470470
- [Scan Coverage](SCAN_COVERAGE.md) — full catalog of detections
471471
- [Release Process](docs/release-process.md) — how releases are signed and verified
472+
- [Deploying via SCCM](docs/deploying-via-sccm.md) — Windows fleet rollout via Microsoft Configuration Manager (signed MSI, no PowerShell)
472473
- [Versioning](VERSIONING.md) — why the version starts at 1.8.1
473474
- [Security Policy](SECURITY.md) — reporting vulnerabilities
474475
- [Code of Conduct](CODE_OF_CONDUCT.md)

cmd/stepsecurity-dev-machine-guard/main.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,38 @@ func main() {
113113

114114
switch cfg.Command {
115115
case "configure":
116-
if err := config.RunConfigure(); err != nil {
117-
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
118-
os.Exit(1)
116+
// Non-interactive path: any explicit config flag, an explicit
117+
// --non-interactive, OR the DMG_API_KEY env var route configure
118+
// through the no-prompt code path. This is how MSI/SCCM/Intune
119+
// custom actions drive configuration — they can't talk to stdin.
120+
opts := config.NonInteractiveOptions{
121+
FromFile: cfg.ConfigFromFile,
122+
CustomerID: cfg.ConfigCustomerID,
123+
APIEndpoint: cfg.ConfigAPIEndpoint,
124+
APIKey: cfg.ConfigAPIKey,
125+
ScanFrequency: cfg.ConfigScanFrequency,
126+
}
127+
if opts.APIKey == "" {
128+
// Env-var fallback keeps the secret off the msiexec command
129+
// line (which lands in AppEnforce.log on every endpoint).
130+
opts.APIKey = os.Getenv("DMG_API_KEY")
131+
}
132+
// Only forward --search-dirs to configure when the user actually
133+
// passed it on this invocation. (cli.Parse defaults SearchDirs to
134+
// ["$HOME"] for the scan path, which we must not persist here.)
135+
if len(cfg.SearchDirs) > 0 && !(len(cfg.SearchDirs) == 1 && cfg.SearchDirs[0] == "$HOME") {
136+
opts.SearchDirs = cfg.SearchDirs
137+
}
138+
if cfg.NonInteractive || opts.HasAny() {
139+
if err := config.RunConfigureNonInteractive(opts); err != nil {
140+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
141+
os.Exit(1)
142+
}
143+
} else {
144+
if err := config.RunConfigure(); err != nil {
145+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
146+
os.Exit(1)
147+
}
119148
}
120149

121150
case "configure show":

0 commit comments

Comments
 (0)