-
-
Notifications
You must be signed in to change notification settings - Fork 275
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ability to upgrade systemd service files
- Loading branch information
Showing
4 changed files
with
266 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
[Unit] | ||
Description=Portmaster by Safing | ||
Documentation=https://safing.io | ||
Documentation=https://docs.safing.io | ||
Before=nss-lookup.target network.target shutdown.target | ||
After=systemd-networkd.service | ||
Conflicts=shutdown.target | ||
Conflicts=firewalld.service | ||
Wants=nss-lookup.target | ||
|
||
[Service] | ||
Type=simple | ||
Restart=on-failure | ||
RestartSec=10 | ||
LockPersonality=yes | ||
MemoryDenyWriteExecute=yes | ||
NoNewPrivileges=yes | ||
PrivateTmp=yes | ||
PIDFile=/opt/safing/portmaster/core-lock.pid | ||
Environment=LOGLEVEL=info | ||
Environment=PORTMASTER_ARGS= | ||
EnvironmentFile=-/etc/default/portmaster | ||
ProtectSystem=true | ||
#ReadWritePaths=/var/lib/portmaster | ||
#ReadWritePaths=/run/xtables.lock | ||
RestrictAddressFamilies=AF_UNIX AF_NETLINK AF_INET AF_INET6 | ||
RestrictNamespaces=yes | ||
# In future version portmaster will require access to user home | ||
# directories to verify application permissions. | ||
ProtectHome=read-only | ||
ProtectKernelTunables=yes | ||
ProtectKernelLogs=yes | ||
ProtectControlGroups=yes | ||
PrivateDevices=yes | ||
AmbientCapabilities=cap_chown cap_kill cap_net_admin cap_net_bind_service cap_net_broadcast cap_net_raw cap_sys_module cap_sys_ptrace cap_dac_override cap_fowner cap_fsetid cap_sys_resource cap_bpf cap_perfmon | ||
CapabilityBoundingSet=cap_chown cap_kill cap_net_admin cap_net_bind_service cap_net_broadcast cap_net_raw cap_sys_module cap_sys_ptrace cap_dac_override cap_fowner cap_fsetid cap_sys_resource cap_bpf cap_perfmon | ||
# SystemCallArchitectures=native | ||
# SystemCallFilter=@system-service @module | ||
# SystemCallErrorNumber=EPERM | ||
ExecStart=/opt/safing/portmaster/portmaster-start --data /opt/safing/portmaster core -- $PORTMASTER_ARGS | ||
ExecStopPost=-/opt/safing/portmaster/portmaster-start recover-iptables | ||
|
||
[Install] | ||
WantedBy=multi-user.target |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
//go:build !linux | ||
// +build !linux | ||
|
||
package updates | ||
|
||
func upgradeSystemIntegration() error { | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
package updates | ||
|
||
import ( | ||
"bytes" | ||
"crypto/sha256" | ||
_ "embed" | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"io/fs" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/tevino/abool" | ||
"golang.org/x/exp/slices" | ||
|
||
"github.com/safing/portbase/dataroot" | ||
"github.com/safing/portbase/log" | ||
"github.com/safing/portbase/utils/renameio" | ||
) | ||
|
||
var ( | ||
portmasterCoreServiceFilePath = "portmaster.service" | ||
portmasterNotifierServiceFilePath = "portmaster_notifier.desktop" | ||
backupExtension = ".backup" | ||
|
||
//go:embed assets/portmaster.service | ||
currentPortmasterCoreServiceFile []byte | ||
|
||
checkedSystemIntegration = abool.New() | ||
|
||
// ErrRequiresManualUpgrade is returned when a system integration file requires a manual upgrade. | ||
ErrRequiresManualUpgrade = errors.New("requires a manual upgrade") | ||
) | ||
|
||
func upgradeSystemIntegration() { | ||
// Check if we already checked the system integration. | ||
if !checkedSystemIntegration.SetToIf(false, true) { | ||
return | ||
} | ||
|
||
// Upgrade portmaster core systemd service. | ||
err := upgradeSystemIntegrationFile( | ||
"portmaster core systemd service", | ||
filepath.Join(dataroot.Root().Path, portmasterCoreServiceFilePath), | ||
0o0600, | ||
currentPortmasterCoreServiceFile, | ||
[]string{ | ||
"bc26dd37e6953af018ad3676ee77570070e075f2b9f5df6fa59d65651a481468", // Commit 19c76c7 on 2022-01-25 | ||
"cc0cb49324dfe11577e8c066dd95cc03d745b50b2153f32f74ca35234c3e8cb5", // Commit ef479e5 on 2022-01-24 | ||
"d08a3b5f3aee351f8e120e6e2e0a089964b94c9e9d0a9e5fa822e60880e315fd", // Commit b64735e on 2021-12-07 | ||
}, | ||
) | ||
if err != nil { | ||
log.Warningf("updates: %s", err) | ||
return | ||
} | ||
|
||
// Upgrade portmaster notifier systemd user service. | ||
// Permissions only! | ||
err = upgradeSystemIntegrationFile( | ||
"portmaster notifier systemd user service", | ||
filepath.Join(dataroot.Root().Path, portmasterNotifierServiceFilePath), | ||
0o0644, | ||
nil, // Do not update contents. | ||
nil, // Do not update contents. | ||
) | ||
if err != nil { | ||
log.Warningf("updates: %s", err) | ||
return | ||
} | ||
} | ||
|
||
// upgradeSystemIntegrationFile upgrades the file contents and permissions. | ||
// System integration files are not necessarily present and may also be | ||
// edited by third parties, such as the OS itself or other installers. | ||
// The supplied hashes must be sha256 hex-encoded. | ||
func upgradeSystemIntegrationFile( | ||
name string, | ||
filePath string, | ||
fileMode fs.FileMode, | ||
fileData []byte, | ||
permittedUpgradeHashes []string, | ||
) error { | ||
// Upgrade file contents. | ||
if len(fileData) > 0 { | ||
if err := upgradeSystemIntegrationFileContents(name, filePath, fileData, permittedUpgradeHashes); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
// Upgrade file permissions. | ||
if fileMode != 0 { | ||
if err := upgradeSystemIntegrationFilePermissions(name, filePath, fileMode); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// upgradeSystemIntegrationFileContents upgrades the file contents. | ||
// System integration files are not necessarily present and may also be | ||
// edited by third parties, such as the OS itself or other installers. | ||
// The supplied hashes must be sha256 hex-encoded. | ||
func upgradeSystemIntegrationFileContents( | ||
name string, | ||
filePath string, | ||
fileData []byte, | ||
permittedUpgradeHashes []string, | ||
) error { | ||
// Read existing file. | ||
existingFileData, err := os.ReadFile(filePath) | ||
if err != nil { | ||
if errors.Is(err, os.ErrNotExist) { | ||
return nil | ||
} | ||
return fmt.Errorf("failed to read %s at %s: %w", name, filePath, err) | ||
} | ||
|
||
// Check if file is already the current version. | ||
existingSum := sha256.Sum256(existingFileData) | ||
existingHexSum := hex.EncodeToString(existingSum[:]) | ||
currentSum := sha256.Sum256(fileData) | ||
currentHexSum := hex.EncodeToString(currentSum[:]) | ||
if existingHexSum == currentHexSum { | ||
log.Debugf("updates: %s at %s is up to date", name, filePath) | ||
return nil | ||
} | ||
|
||
// Check if we are allowed to upgrade from the existing file. | ||
if !slices.Contains[string](permittedUpgradeHashes, existingHexSum) { | ||
return fmt.Errorf("%s at %s %w, as it is not a previously published version and cannot be automatically upgraded - try installing again", name, filePath, ErrRequiresManualUpgrade) | ||
} | ||
|
||
// Start with upgrade! | ||
|
||
// Make backup of existing file. | ||
err = CopyFile(filePath, filePath+backupExtension) | ||
if err != nil { | ||
return fmt.Errorf( | ||
"failed to create backup of %s from %s to %s: %w", | ||
name, | ||
filePath, | ||
filePath+backupExtension, | ||
err, | ||
) | ||
} | ||
|
||
// Open destination file for writing. | ||
atomicDstFile, err := renameio.TempFile(registry.TmpDir().Path, filePath) | ||
if err != nil { | ||
return fmt.Errorf("failed to create tmp file to update %s at %s: %w", name, filePath, err) | ||
} | ||
defer atomicDstFile.Cleanup() //nolint:errcheck // ignore error for now, tmp dir will be cleaned later again anyway | ||
|
||
// Write file. | ||
_, err = io.Copy(atomicDstFile, bytes.NewReader(fileData)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Finalize file. | ||
err = atomicDstFile.CloseAtomicallyReplace() | ||
if err != nil { | ||
return fmt.Errorf("failed to finalize update of %s at %s: %w", name, filePath, err) | ||
} | ||
|
||
log.Warningf("updates: %s at %s was upgraded to %s", name, filePath, currentHexSum) | ||
return nil | ||
} | ||
|
||
// upgradeSystemIntegrationFilePermissions upgrades the file permissions. | ||
// System integration files are not necessarily present and may also be | ||
// edited by third parties, such as the OS itself or other installers. | ||
func upgradeSystemIntegrationFilePermissions( | ||
name string, | ||
filePath string, | ||
fileMode fs.FileMode, | ||
) error { | ||
// Get current file permissions. | ||
stat, err := os.Stat(filePath) | ||
if err != nil { | ||
if errors.Is(err, os.ErrNotExist) { | ||
return nil | ||
} | ||
return fmt.Errorf("failed to read %s file metadata at %s: %w", name, filePath, err) | ||
} | ||
|
||
// If permissions are as expected, do nothing. | ||
if stat.Mode().Perm() == fileMode { | ||
return nil | ||
} | ||
|
||
// Otherwise, set correct permissions. | ||
err = os.Chmod(filePath, fileMode) | ||
if err != nil { | ||
return fmt.Errorf("failed to update %s file permissions at %s: %w", name, filePath, err) | ||
} | ||
|
||
log.Warningf("updates: %s file permissions at %s updated to %v", name, filePath, fileMode) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters