Stop macOS agent updates from spawning BTM entries
Unsigned darwin release tarballs replaced the Developer ID binary on each auto-update, so Background Task Management treated every release as a new identity and showed the generic executable icon. Sign those archives with a stable identifier, refuse signature downgrades, and attribute daemon/tray jobs to Probo Agent.app via AssociatedBundleIdentifiers. Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
8
.github/workflows/release-probo-agent.yaml
vendored
8
.github/workflows/release-probo-agent.yaml
vendored
@@ -126,6 +126,7 @@ jobs:
|
||||
LDFLAGS="-s -w -X 'main.version=${VERSION}'"
|
||||
|
||||
mkdir -p dist archives staging
|
||||
export COPYFILE_DISABLE=1
|
||||
|
||||
GOOS=darwin GOARCH=arm64 go build -ldflags "${LDFLAGS}" \
|
||||
-gcflags="-e" -o dist/probo-agent_arm64 ./cmd/probo-agent
|
||||
@@ -147,6 +148,13 @@ jobs:
|
||||
if [ -f cmd/probo-agent/CHANGELOG.md ]; then
|
||||
cp cmd/probo-agent/CHANGELOG.md "staging/${AGENT_DIR}/"
|
||||
fi
|
||||
# Match the PKG payload signing identity so auto-update does
|
||||
# not replace a Developer ID binary with an ad-hoc one (which
|
||||
# multiplies macOS Background Activity entries).
|
||||
codesign --force --options runtime --timestamp \
|
||||
--identifier com.probo.agent \
|
||||
--sign "${CODESIGN_IDENTITY}" "staging/${AGENT_DIR}/probo-agent"
|
||||
codesign --verify --verbose=2 "staging/${AGENT_DIR}/probo-agent"
|
||||
tar -czf "archives/${AGENT_DIR}.tar.gz" -C staging "${AGENT_DIR}"
|
||||
done
|
||||
|
||||
|
||||
@@ -151,12 +151,17 @@ fi
|
||||
|
||||
codesign_runtime() {
|
||||
local path="$1"
|
||||
codesign \
|
||||
--force \
|
||||
--options runtime \
|
||||
--timestamp \
|
||||
--sign "${CODESIGN_IDENTITY}" \
|
||||
"${path}"
|
||||
local identifier="${2:-}"
|
||||
local -a args=(
|
||||
--force
|
||||
--options runtime
|
||||
--timestamp
|
||||
--sign "${CODESIGN_IDENTITY}"
|
||||
)
|
||||
if [ -n "${identifier}" ]; then
|
||||
args+=(--identifier "${identifier}")
|
||||
fi
|
||||
codesign "${args[@]}" "${path}"
|
||||
codesign --verify --verbose=2 "${path}"
|
||||
}
|
||||
|
||||
@@ -391,7 +396,7 @@ RESOURCES="${STAGE}/Resources"
|
||||
mkdir -p "${PAYLOAD}/usr/local/bin" "${SCRIPTS}" "${RESOURCES}"
|
||||
|
||||
install -m 0755 "${BINARY}" "${PAYLOAD}/usr/local/bin/probo-agent"
|
||||
codesign_runtime "${PAYLOAD}/usr/local/bin/probo-agent"
|
||||
codesign_runtime "${PAYLOAD}/usr/local/bin/probo-agent" "com.probo.agent"
|
||||
|
||||
mkdir -p "${PAYLOAD}/Applications"
|
||||
build_probo_agent_app "${PAYLOAD}/Applications"
|
||||
|
||||
@@ -24,5 +24,9 @@
|
||||
<string>root</string>
|
||||
<key>GroupName</key>
|
||||
<string>wheel</string>
|
||||
<key>AssociatedBundleIdentifiers</key>
|
||||
<array>
|
||||
<string>com.probo.agent.url-handler</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -39,35 +40,14 @@ const (
|
||||
helperBinaryPath = "/Library/PrivilegedHelperTools/" + helperLabel
|
||||
)
|
||||
|
||||
const launchdPlistTmpl = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>{{xml .Label}}</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>{{xml .ExePath}}</string>
|
||||
<string>run</string>
|
||||
<string>--dir</string>
|
||||
<string>{{xml .Dir}}</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/probo-agent.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/probo-agent.log</string>
|
||||
<key>UserName</key>
|
||||
<string>root</string>
|
||||
<key>GroupName</key>
|
||||
<string>wheel</string>
|
||||
</dict>
|
||||
</plist>
|
||||
`
|
||||
var (
|
||||
//go:embed launchd.plist.tmpl
|
||||
launchdPlistTmpl string
|
||||
|
||||
launchdPlist = template.Must(
|
||||
template.New("plist").Funcs(template.FuncMap{"xml": xmlEscape}).Parse(launchdPlistTmpl),
|
||||
)
|
||||
)
|
||||
|
||||
func xmlEscape(v string) (string, error) {
|
||||
var sb strings.Builder
|
||||
@@ -126,11 +106,6 @@ func Install(cfg Config) error {
|
||||
cfg.Label = DefaultLabel
|
||||
}
|
||||
|
||||
tmpl, err := template.New("plist").Funcs(template.FuncMap{"xml": xmlEscape}).Parse(launchdPlistTmpl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot parse plist template: %w", err)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(plistPath), 0o755); err != nil {
|
||||
return fmt.Errorf("cannot ensure launch daemons directory: %w", err)
|
||||
}
|
||||
@@ -142,7 +117,7 @@ func Install(cfg Config) error {
|
||||
|
||||
defer func() { _ = f.Close() }()
|
||||
|
||||
if err := tmpl.Execute(f, cfg); err != nil {
|
||||
if err := launchdPlist.Execute(f, cfg); err != nil {
|
||||
return fmt.Errorf("cannot render plist: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,5 +16,9 @@
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>AssociatedBundleIdentifiers</key>
|
||||
<array>
|
||||
<string>com.probo.agent.url-handler</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
93
pkg/deviceagent/update/install_signature_darwin.go
Normal file
93
pkg/deviceagent/update/install_signature_darwin.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//go:build darwin
|
||||
|
||||
package update
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
)
|
||||
|
||||
// ensureSignatureCompatible refuses an update that would replace a
|
||||
// Developer ID-signed agent with a binary that lacks the same Apple
|
||||
// Team ID and code-signing Identifier. A mismatch on either field
|
||||
// creates a fresh Background Task Management identity.
|
||||
//
|
||||
// Machines already running an unsigned (ad-hoc) binary are allowed to
|
||||
// upgrade so they can recover onto a signed release.
|
||||
func ensureSignatureCompatible(
|
||||
ctx context.Context,
|
||||
logger *log.Logger,
|
||||
currentPath string,
|
||||
candidatePath string,
|
||||
) error {
|
||||
current, err := readCodeSigningIdentity(currentPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read current binary code signature: %w", err)
|
||||
}
|
||||
|
||||
candidate, err := readCodeSigningIdentity(candidatePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot read candidate binary code signature: %w", err)
|
||||
}
|
||||
|
||||
if current.Team == "" {
|
||||
logger.InfoCtx(
|
||||
ctx,
|
||||
"current agent binary has no Apple Team ID; allowing update",
|
||||
log.String("candidate_team_id", candidate.Team),
|
||||
log.String("candidate_identifier", candidate.Identifier),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return codeSigningIdentitiesCompatible(current, candidate)
|
||||
}
|
||||
|
||||
func readCodeSigningIdentity(path string) (codeSigningIdentity, error) {
|
||||
cmd := exec.Command("/usr/bin/codesign", "-d", "--verbose=4", path)
|
||||
out, err := cmd.CombinedOutput()
|
||||
output := string(out)
|
||||
|
||||
identity := parseCodeSigningIdentity(output)
|
||||
if identity.Team != "" {
|
||||
return identity, nil
|
||||
}
|
||||
|
||||
// Unsigned and ad-hoc binaries exit non-zero and report no Team ID.
|
||||
// Treat those as empty rather than failing the update path.
|
||||
if err != nil && !isUnsignedCodesignOutput(output) {
|
||||
return codeSigningIdentity{}, fmt.Errorf(
|
||||
"codesign -d %s: %w: %s",
|
||||
path,
|
||||
err,
|
||||
strings.TrimSpace(output),
|
||||
)
|
||||
}
|
||||
|
||||
return identity, nil
|
||||
}
|
||||
106
pkg/deviceagent/update/install_signature_identity.go
Normal file
106
pkg/deviceagent/update/install_signature_identity.go
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package update
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// codeSigningIdentity is the Apple code-signing identity fields that
|
||||
// Background Task Management uses to key a background item.
|
||||
type codeSigningIdentity struct {
|
||||
Team string
|
||||
Identifier string
|
||||
}
|
||||
|
||||
// codeSigningIdentitiesCompatible refuses an update that would change
|
||||
// the BTM identity of a Developer ID-signed agent. Both Team ID and
|
||||
// signing Identifier must match. Machines already running an unsigned
|
||||
// (ad-hoc) binary are allowed to upgrade onto a signed release.
|
||||
func codeSigningIdentitiesCompatible(
|
||||
current codeSigningIdentity,
|
||||
candidate codeSigningIdentity,
|
||||
) error {
|
||||
if current.Team == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if candidate.Team == "" {
|
||||
return fmt.Errorf(
|
||||
"candidate binary has no Apple Team ID (current Team ID %s); refusing signature downgrade",
|
||||
current.Team,
|
||||
)
|
||||
}
|
||||
|
||||
if candidate.Team != current.Team {
|
||||
return fmt.Errorf(
|
||||
"candidate Team ID %s does not match current Team ID %s",
|
||||
candidate.Team,
|
||||
current.Team,
|
||||
)
|
||||
}
|
||||
|
||||
if candidate.Identifier != current.Identifier {
|
||||
return fmt.Errorf(
|
||||
"candidate Identifier %q does not match current Identifier %q",
|
||||
candidate.Identifier,
|
||||
current.Identifier,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseCodeSigningIdentity(codesignOutput string) codeSigningIdentity {
|
||||
return codeSigningIdentity{
|
||||
Team: parseCodesignField(codesignOutput, "TeamIdentifier="),
|
||||
Identifier: parseCodesignField(codesignOutput, "Identifier="),
|
||||
}
|
||||
}
|
||||
|
||||
func parseCodesignField(codesignOutput string, prefix string) string {
|
||||
for line := range strings.SplitSeq(codesignOutput, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
after, ok := strings.CutPrefix(line, prefix)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
after = strings.TrimSpace(after)
|
||||
if after == "" || after == "not set" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return after
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func isUnsignedCodesignOutput(output string) bool {
|
||||
lower := strings.ToLower(output)
|
||||
|
||||
return strings.Contains(lower, "code object is not signed") ||
|
||||
strings.Contains(lower, "not signed at all") ||
|
||||
strings.Contains(lower, "teamidentifier=not set")
|
||||
}
|
||||
171
pkg/deviceagent/update/install_signature_identity_test.go
Normal file
171
pkg/deviceagent/update/install_signature_identity_test.go
Normal file
@@ -0,0 +1,171 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
package update
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseCodeSigningIdentity(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
out string
|
||||
want codeSigningIdentity
|
||||
}{
|
||||
{
|
||||
name: "developer id signed binary",
|
||||
out: "" +
|
||||
"Executable=/usr/local/bin/probo-agent\n" +
|
||||
"Identifier=com.probo.agent\n" +
|
||||
"Format=Mach-O thin (arm64)\n" +
|
||||
"TeamIdentifier=ABCD123456\n",
|
||||
want: codeSigningIdentity{
|
||||
Team: "ABCD123456",
|
||||
Identifier: "com.probo.agent",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "team identifier not set",
|
||||
out: "" +
|
||||
"Identifier=probo-agent\n" +
|
||||
"Signature=adhoc\n" +
|
||||
"TeamIdentifier=not set\n",
|
||||
want: codeSigningIdentity{
|
||||
Team: "",
|
||||
Identifier: "probo-agent",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unsigned output has empty fields",
|
||||
out: "code object is not signed at all\n",
|
||||
want: codeSigningIdentity{},
|
||||
},
|
||||
{
|
||||
name: "empty identifier value",
|
||||
out: "" +
|
||||
"Identifier=\n" +
|
||||
"TeamIdentifier=ABCD123456\n",
|
||||
want: codeSigningIdentity{
|
||||
Team: "ABCD123456",
|
||||
Identifier: "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(
|
||||
tc.name,
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got := parseCodeSigningIdentity(tc.out)
|
||||
assert.Equal(t, tc.want, got)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodeSigningIdentitiesCompatible(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
stable := codeSigningIdentity{
|
||||
Team: "ABCD123456",
|
||||
Identifier: "com.probo.agent",
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
current codeSigningIdentity
|
||||
candidate codeSigningIdentity
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "allows unsigned to signed upgrade",
|
||||
current: codeSigningIdentity{},
|
||||
candidate: codeSigningIdentity{
|
||||
Team: "ABCD123456",
|
||||
Identifier: "com.probo.agent",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "allows matching team and identifier",
|
||||
current: stable,
|
||||
candidate: stable,
|
||||
},
|
||||
{
|
||||
name: "refuses signed to unsigned downgrade",
|
||||
current: stable,
|
||||
candidate: codeSigningIdentity{
|
||||
Identifier: "com.probo.agent",
|
||||
},
|
||||
wantErr: "refusing signature downgrade",
|
||||
},
|
||||
{
|
||||
name: "refuses team mismatch",
|
||||
current: stable,
|
||||
candidate: codeSigningIdentity{
|
||||
Team: "OTHERTEAM1",
|
||||
Identifier: "com.probo.agent",
|
||||
},
|
||||
wantErr: "does not match current Team ID",
|
||||
},
|
||||
{
|
||||
name: "refuses same team with different identifier",
|
||||
current: stable,
|
||||
candidate: codeSigningIdentity{
|
||||
Team: "ABCD123456",
|
||||
Identifier: "com.probo.agent.other",
|
||||
},
|
||||
wantErr: "does not match current Identifier",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(
|
||||
tc.name,
|
||||
func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
err := codeSigningIdentitiesCompatible(tc.current, tc.candidate)
|
||||
if tc.wantErr == "" {
|
||||
assert.NoError(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tc.wantErr)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsUnsignedCodesignOutput(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert.True(t, isUnsignedCodesignOutput("code object is not signed at all"))
|
||||
assert.True(t, isUnsignedCodesignOutput("TeamIdentifier=not set\n"))
|
||||
assert.False(t, isUnsignedCodesignOutput("TeamIdentifier=ABCD123456\n"))
|
||||
}
|
||||
38
pkg/deviceagent/update/install_signature_other.go
Normal file
38
pkg/deviceagent/update/install_signature_other.go
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//go:build !darwin
|
||||
|
||||
package update
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.gearno.de/kit/log"
|
||||
)
|
||||
|
||||
func ensureSignatureCompatible(
|
||||
_ context.Context,
|
||||
_ *log.Logger,
|
||||
_ string,
|
||||
_ string,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
@@ -325,6 +325,10 @@ func (u *Updater) Apply(ctx context.Context, rel *Release) error {
|
||||
return fmt.Errorf("cannot extract archive: %w", err)
|
||||
}
|
||||
|
||||
if err := ensureSignatureCompatible(ctx, u.Logger, u.ExePath, extractedBinary); err != nil {
|
||||
return fmt.Errorf("cannot verify code signature compatibility: %w", err)
|
||||
}
|
||||
|
||||
if err := replaceBinary(u.ExePath, extractedBinary); err != nil {
|
||||
return fmt.Errorf("cannot replace agent binary: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user