mirror of
https://github.com/netbirdio/netbird.git
synced 2026-03-31 06:24:18 -04:00
Auto-update logic moved out of the UI into a dedicated updatemanager.Manager service that runs in the connection layer. The UI no longer polls or checks for updates independently. The update manager supports three modes driven by the management server's auto-update policy: No policy set by mgm: checks GitHub for the latest version and notifies the user (previous behavior, now centralized) mgm enforces update: the "About" menu triggers installation directly instead of just downloading the file — user still initiates the action mgm forces update: installation proceeds automatically without user interaction updateManager lifecycle is now owned by daemon, giving the daemon server direct control via a new TriggerUpdate RPC Introduces EngineServices struct to group external service dependencies passed to NewEngine, reducing its argument count from 11 to 4
188 lines
5.2 KiB
Go
188 lines
5.2 KiB
Go
package reposign
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
|
|
"github.com/netbirdio/netbird/client/internal/updater/downloader"
|
|
)
|
|
|
|
const (
|
|
artifactPubKeysFileName = "artifact-key-pub.pem"
|
|
artifactPubKeysSigFileName = "artifact-key-pub.pem.sig"
|
|
revocationFileName = "revocation-list.json"
|
|
revocationSignFileName = "revocation-list.json.sig"
|
|
|
|
keySizeLimit = 5 * 1024 * 1024 //5MB
|
|
signatureLimit = 1024
|
|
revocationLimit = 10 * 1024 * 1024
|
|
)
|
|
|
|
type ArtifactVerify struct {
|
|
rootKeys []PublicKey
|
|
keysBaseURL *url.URL
|
|
|
|
revocationList *RevocationList
|
|
}
|
|
|
|
func NewArtifactVerify(keysBaseURL string) (*ArtifactVerify, error) {
|
|
allKeys, err := loadEmbeddedPublicKeys()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return newArtifactVerify(keysBaseURL, allKeys)
|
|
}
|
|
|
|
func newArtifactVerify(keysBaseURL string, allKeys []PublicKey) (*ArtifactVerify, error) {
|
|
ku, err := url.Parse(keysBaseURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid keys base URL %q: %v", keysBaseURL, err)
|
|
}
|
|
|
|
a := &ArtifactVerify{
|
|
rootKeys: allKeys,
|
|
keysBaseURL: ku,
|
|
}
|
|
return a, nil
|
|
}
|
|
|
|
func (a *ArtifactVerify) Verify(ctx context.Context, version string, artifactFile string) error {
|
|
version = strings.TrimPrefix(version, "v")
|
|
|
|
revocationList, err := a.loadRevocationList(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load revocation list: %v", err)
|
|
}
|
|
a.revocationList = revocationList
|
|
|
|
artifactPubKeys, err := a.loadArtifactKeys(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load artifact keys: %v", err)
|
|
}
|
|
|
|
signature, err := a.loadArtifactSignature(ctx, version, artifactFile)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to download signature file for: %s, %v", filepath.Base(artifactFile), err)
|
|
}
|
|
|
|
artifactData, err := os.ReadFile(artifactFile)
|
|
if err != nil {
|
|
log.Errorf("failed to read artifact file: %v", err)
|
|
return fmt.Errorf("failed to read artifact file: %w", err)
|
|
}
|
|
|
|
if err := ValidateArtifact(artifactPubKeys, artifactData, *signature); err != nil {
|
|
return fmt.Errorf("failed to validate artifact: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (a *ArtifactVerify) loadRevocationList(ctx context.Context) (*RevocationList, error) {
|
|
downloadURL := a.keysBaseURL.JoinPath("keys", revocationFileName).String()
|
|
data, err := downloader.DownloadToMemory(ctx, downloadURL, revocationLimit)
|
|
if err != nil {
|
|
log.Debugf("failed to download revocation list '%s': %s", downloadURL, err)
|
|
return nil, err
|
|
}
|
|
|
|
downloadURL = a.keysBaseURL.JoinPath("keys", revocationSignFileName).String()
|
|
sigData, err := downloader.DownloadToMemory(ctx, downloadURL, signatureLimit)
|
|
if err != nil {
|
|
log.Debugf("failed to download revocation list '%s': %s", downloadURL, err)
|
|
return nil, err
|
|
}
|
|
|
|
signature, err := ParseSignature(sigData)
|
|
if err != nil {
|
|
log.Debugf("failed to parse revocation list signature: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
return ValidateRevocationList(a.rootKeys, data, *signature)
|
|
}
|
|
|
|
func (a *ArtifactVerify) loadArtifactKeys(ctx context.Context) ([]PublicKey, error) {
|
|
downloadURL := a.keysBaseURL.JoinPath("keys", artifactPubKeysFileName).String()
|
|
log.Debugf("starting downloading artifact keys from: %s", downloadURL)
|
|
data, err := downloader.DownloadToMemory(ctx, downloadURL, keySizeLimit)
|
|
if err != nil {
|
|
log.Debugf("failed to download artifact keys: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
downloadURL = a.keysBaseURL.JoinPath("keys", artifactPubKeysSigFileName).String()
|
|
log.Debugf("start downloading signature of artifact pub key from: %s", downloadURL)
|
|
sigData, err := downloader.DownloadToMemory(ctx, downloadURL, signatureLimit)
|
|
if err != nil {
|
|
log.Debugf("failed to download signature of public keys: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
signature, err := ParseSignature(sigData)
|
|
if err != nil {
|
|
log.Debugf("failed to parse signature of public keys: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
return ValidateArtifactKeys(a.rootKeys, data, *signature, a.revocationList)
|
|
}
|
|
|
|
func (a *ArtifactVerify) loadArtifactSignature(ctx context.Context, version string, artifactFile string) (*Signature, error) {
|
|
artifactFile = filepath.Base(artifactFile)
|
|
downloadURL := a.keysBaseURL.JoinPath("tag", "v"+version, artifactFile+".sig").String()
|
|
data, err := downloader.DownloadToMemory(ctx, downloadURL, signatureLimit)
|
|
if err != nil {
|
|
log.Debugf("failed to download artifact signature: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
signature, err := ParseSignature(data)
|
|
if err != nil {
|
|
log.Debugf("failed to parse artifact signature: %s", err)
|
|
return nil, err
|
|
}
|
|
|
|
return signature, nil
|
|
|
|
}
|
|
|
|
func loadEmbeddedPublicKeys() ([]PublicKey, error) {
|
|
files, err := embeddedCerts.ReadDir(embeddedCertsDir)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read embedded certs: %w", err)
|
|
}
|
|
|
|
var allKeys []PublicKey
|
|
for _, file := range files {
|
|
if file.IsDir() {
|
|
continue
|
|
}
|
|
|
|
data, err := embeddedCerts.ReadFile(embeddedCertsDir + "/" + file.Name())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read cert file %s: %w", file.Name(), err)
|
|
}
|
|
|
|
keys, err := parsePublicKeyBundle(data, tagRootPublic)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse cert %s: %w", file.Name(), err)
|
|
}
|
|
|
|
allKeys = append(allKeys, keys...)
|
|
}
|
|
|
|
if len(allKeys) == 0 {
|
|
return nil, fmt.Errorf("no valid public keys found in embedded certs")
|
|
}
|
|
|
|
return allKeys, nil
|
|
}
|