mirror of
https://github.com/safing/portmaster
synced 2025-09-02 02:29:12 +00:00
Refactor status package to use portbase/runtime.
Refactor the status package to use portbase/runtime and make system status readonly. Also adapts the code base to the new portbase/notifications package.
This commit is contained in:
parent
52c4cfe11d
commit
a5e3f7ff37
22 changed files with 527 additions and 554 deletions
|
@ -9,7 +9,6 @@ import (
|
|||
"github.com/safing/portbase/dataroot"
|
||||
"github.com/safing/portbase/info"
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portbase/modules/subsystems"
|
||||
)
|
||||
|
||||
// Default Values (changeable for testing)
|
||||
|
@ -66,8 +65,5 @@ func globalPrep() error {
|
|||
// set api listen address
|
||||
api.SetDefaultAPIListenAddress(DefaultAPIListenAddress)
|
||||
|
||||
// set subsystem status dir
|
||||
subsystems.SetDatabaseKeySpace("core:status/subsystems")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -46,18 +46,16 @@ func prompt(conn *network.Connection, pkt packet.Packet) { //nolint:gocognit //
|
|||
// do not save response to profile
|
||||
saveResponse = false
|
||||
} else {
|
||||
// create new notification
|
||||
n = (¬ifications.Notification{
|
||||
ID: nID,
|
||||
Type: notifications.Prompt,
|
||||
Expires: time.Now().Add(nTTL).Unix(),
|
||||
})
|
||||
var (
|
||||
msg string
|
||||
actions []notifications.Action
|
||||
)
|
||||
|
||||
// add message and actions
|
||||
switch {
|
||||
case conn.Inbound:
|
||||
n.Message = fmt.Sprintf("Application %s wants to accept connections from %s (%d/%d)", conn.Process(), conn.Entity.IP.String(), conn.Entity.Protocol, conn.Entity.Port)
|
||||
n.AvailableActions = []*notifications.Action{
|
||||
msg = fmt.Sprintf("Application %s wants to accept connections from %s (%d/%d)", conn.Process(), conn.Entity.IP.String(), conn.Entity.Protocol, conn.Entity.Port)
|
||||
actions = []notifications.Action{
|
||||
{
|
||||
ID: permitServingIP,
|
||||
Text: "Permit",
|
||||
|
@ -68,8 +66,8 @@ func prompt(conn *network.Connection, pkt packet.Packet) { //nolint:gocognit //
|
|||
},
|
||||
}
|
||||
case conn.Entity.Domain == "": // direct connection
|
||||
n.Message = fmt.Sprintf("Application %s wants to connect to %s (%d/%d)", conn.Process(), conn.Entity.IP.String(), conn.Entity.Protocol, conn.Entity.Port)
|
||||
n.AvailableActions = []*notifications.Action{
|
||||
msg = fmt.Sprintf("Application %s wants to connect to %s (%d/%d)", conn.Process(), conn.Entity.IP.String(), conn.Entity.Protocol, conn.Entity.Port)
|
||||
actions = []notifications.Action{
|
||||
{
|
||||
ID: permitIP,
|
||||
Text: "Permit",
|
||||
|
@ -81,11 +79,11 @@ func prompt(conn *network.Connection, pkt packet.Packet) { //nolint:gocognit //
|
|||
}
|
||||
default: // connection to domain
|
||||
if pkt != nil {
|
||||
n.Message = fmt.Sprintf("Application %s wants to connect to %s (%s %d/%d)", conn.Process(), conn.Entity.Domain, conn.Entity.IP.String(), conn.Entity.Protocol, conn.Entity.Port)
|
||||
msg = fmt.Sprintf("Application %s wants to connect to %s (%s %d/%d)", conn.Process(), conn.Entity.Domain, conn.Entity.IP.String(), conn.Entity.Protocol, conn.Entity.Port)
|
||||
} else {
|
||||
n.Message = fmt.Sprintf("Application %s wants to connect to %s", conn.Process(), conn.Entity.Domain)
|
||||
msg = fmt.Sprintf("Application %s wants to connect to %s", conn.Process(), conn.Entity.Domain)
|
||||
}
|
||||
n.AvailableActions = []*notifications.Action{
|
||||
actions = []notifications.Action{
|
||||
{
|
||||
ID: permitDomainAll,
|
||||
Text: "Permit all",
|
||||
|
@ -100,8 +98,8 @@ func prompt(conn *network.Connection, pkt packet.Packet) { //nolint:gocognit //
|
|||
},
|
||||
}
|
||||
}
|
||||
// save new notification
|
||||
n.Save()
|
||||
|
||||
n = notifications.NotifyPrompt(nID, msg, actions...)
|
||||
}
|
||||
|
||||
// wait for response/timeout
|
||||
|
|
|
@ -47,11 +47,10 @@ func checkForConflictingService() error {
|
|||
// wait for a short duration for the other service to shut down
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// notify user
|
||||
(¬ifications.Notification{
|
||||
ID: "nameserver-stopped-conflicting-service",
|
||||
Message: fmt.Sprintf("Portmaster stopped a conflicting name service (pid %d) to gain required system integration.", pid),
|
||||
}).Save()
|
||||
notifications.NotifyInfo(
|
||||
"namserver-stopped-conflicting-service",
|
||||
fmt.Sprintf("Portmaster stopped a conflicting name service (pid %d) to gain required system integration.", pid),
|
||||
)
|
||||
|
||||
// restart via service-worker logic
|
||||
return fmt.Errorf("%w: stopped conflicting name service with pid %d", modules.ErrRestartNow, pid)
|
||||
|
|
|
@ -57,19 +57,19 @@ var (
|
|||
cfgOptionNameServersOrder = 0
|
||||
|
||||
CfgOptionNoAssignedNameserversKey = "dns/noAssignedNameservers"
|
||||
noAssignedNameservers status.SecurityLevelOption
|
||||
noAssignedNameservers status.SecurityLevelOptionFunc
|
||||
cfgOptionNoAssignedNameserversOrder = 1
|
||||
|
||||
CfgOptionNoMulticastDNSKey = "dns/noMulticastDNS"
|
||||
noMulticastDNS status.SecurityLevelOption
|
||||
noMulticastDNS status.SecurityLevelOptionFunc
|
||||
cfgOptionNoMulticastDNSOrder = 2
|
||||
|
||||
CfgOptionNoInsecureProtocolsKey = "dns/noInsecureProtocols"
|
||||
noInsecureProtocols status.SecurityLevelOption
|
||||
noInsecureProtocols status.SecurityLevelOptionFunc
|
||||
cfgOptionNoInsecureProtocolsOrder = 3
|
||||
|
||||
CfgOptionDontResolveSpecialDomainsKey = "dns/dontResolveSpecialDomains"
|
||||
dontResolveSpecialDomains status.SecurityLevelOption
|
||||
dontResolveSpecialDomains status.SecurityLevelOptionFunc
|
||||
cfgOptionDontResolveSpecialDomainsOrder = 16
|
||||
|
||||
CfgOptionNameserverRetryRateKey = "dns/nameserverRetryRate"
|
||||
|
@ -122,6 +122,7 @@ Parameters:
|
|||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "DNS Server Retry Rate",
|
||||
Key: CfgOptionNameserverRetryRateKey,
|
||||
Description: "Rate at which to retry failed DNS Servers, in seconds.",
|
||||
OptType: config.OptTypeInt,
|
||||
ExpertiseLevel: config.ExpertiseLevelExpert,
|
||||
|
@ -154,7 +155,7 @@ Parameters:
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
noMulticastDNS = status.ConfigIsActiveConcurrent(CfgOptionNoMulticastDNSKey)
|
||||
noMulticastDNS = status.SecurityLevelOption(CfgOptionNoMulticastDNSKey)
|
||||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Do not use assigned Nameservers",
|
||||
|
@ -173,7 +174,7 @@ Parameters:
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
noAssignedNameservers = status.ConfigIsActiveConcurrent(CfgOptionNoAssignedNameserversKey)
|
||||
noAssignedNameservers = status.SecurityLevelOption(CfgOptionNoAssignedNameserversKey)
|
||||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Do not resolve insecurely",
|
||||
|
@ -192,7 +193,7 @@ Parameters:
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
noInsecureProtocols = status.ConfigIsActiveConcurrent(CfgOptionNoInsecureProtocolsKey)
|
||||
noInsecureProtocols = status.SecurityLevelOption(CfgOptionNoInsecureProtocolsKey)
|
||||
|
||||
err = config.Register(&config.Option{
|
||||
Name: "Do not resolve special domains",
|
||||
|
@ -211,7 +212,7 @@ Parameters:
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dontResolveSpecialDomains = status.ConfigIsActiveConcurrent(CfgOptionDontResolveSpecialDomainsKey)
|
||||
dontResolveSpecialDomains = status.SecurityLevelOption(CfgOptionDontResolveSpecialDomainsKey)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
36
status/autopilot.go
Normal file
36
status/autopilot.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package status
|
||||
|
||||
import "context"
|
||||
|
||||
var runAutoPilot = make(chan struct{})
|
||||
|
||||
func triggerAutopilot() {
|
||||
select {
|
||||
case runAutoPilot <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func autoPilot(ctx context.Context) error {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-runAutoPilot:
|
||||
}
|
||||
|
||||
selected := SelectedSecurityLevel()
|
||||
mitigation := getHighestMitigationLevel()
|
||||
|
||||
active := SecurityLevelNormal
|
||||
if selected != SecurityLevelOff {
|
||||
active = selected
|
||||
} else if mitigation != SecurityLevelOff {
|
||||
active = mitigation
|
||||
}
|
||||
|
||||
setActiveLevel(active)
|
||||
|
||||
pushSystemStatus()
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"github.com/safing/portbase/config"
|
||||
)
|
||||
|
||||
// DisplayHintSecurityLevel is an external option hint for security levels.
|
||||
// It's meant to be used as a value for config.DisplayHintAnnotation.
|
||||
const DisplayHintSecurityLevel string = "security level"
|
||||
|
||||
// Security levels
|
||||
const (
|
||||
SecurityLevelOff uint8 = 0
|
||||
SecurityLevelNormal uint8 = 1
|
||||
SecurityLevelHigh uint8 = 2
|
||||
SecurityLevelExtreme uint8 = 4
|
||||
|
||||
SecurityLevelsNormalAndHigh uint8 = SecurityLevelNormal | SecurityLevelHigh
|
||||
SecurityLevelsNormalAndExtreme uint8 = SecurityLevelNormal | SecurityLevelExtreme
|
||||
SecurityLevelsHighAndExtreme uint8 = SecurityLevelHigh | SecurityLevelExtreme
|
||||
SecurityLevelsAll uint8 = SecurityLevelNormal | SecurityLevelHigh | SecurityLevelExtreme
|
||||
)
|
||||
|
||||
// SecurityLevelValues defines all possible security levels.
|
||||
var SecurityLevelValues = []config.PossibleValue{
|
||||
{
|
||||
Name: "Normal",
|
||||
Value: SecurityLevelsAll,
|
||||
},
|
||||
{
|
||||
Name: "High",
|
||||
Value: SecurityLevelsHighAndExtreme,
|
||||
},
|
||||
{
|
||||
Name: "Extreme",
|
||||
Value: SecurityLevelExtreme,
|
||||
},
|
||||
}
|
||||
|
||||
// AllSecurityLevelValues is like SecurityLevelValues but also includes Off.
|
||||
var AllSecurityLevelValues = append([]config.PossibleValue{
|
||||
{
|
||||
Name: "Off",
|
||||
Value: SecurityLevelOff,
|
||||
},
|
||||
},
|
||||
SecurityLevelValues...,
|
||||
)
|
||||
|
||||
// Status constants
|
||||
const (
|
||||
StatusOff uint8 = 0
|
||||
StatusError uint8 = 1
|
||||
StatusWarning uint8 = 2
|
||||
StatusOk uint8 = 3
|
||||
)
|
|
@ -1,59 +0,0 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/database/query"
|
||||
"github.com/safing/portbase/database/record"
|
||||
)
|
||||
|
||||
const (
|
||||
statusDBKey = "core:status/status"
|
||||
)
|
||||
|
||||
var (
|
||||
statusDB = database.NewInterface(nil)
|
||||
hook *database.RegisteredHook
|
||||
)
|
||||
|
||||
type statusHook struct {
|
||||
database.HookBase
|
||||
}
|
||||
|
||||
// UsesPrePut implements the Hook interface.
|
||||
func (sh *statusHook) UsesPrePut() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// PrePut implements the Hook interface.
|
||||
func (sh *statusHook) PrePut(r record.Record) (record.Record, error) {
|
||||
// record is already locked!
|
||||
|
||||
newStatus, err := EnsureSystemStatus(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// apply applicable settings
|
||||
if SelectedSecurityLevel() != newStatus.SelectedSecurityLevel {
|
||||
module.StartWorker("set selected security level", func(_ context.Context) error {
|
||||
setSelectedSecurityLevel(newStatus.SelectedSecurityLevel)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: allow setting of Gate17 status (on/off)
|
||||
|
||||
// return original status
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func initStatusHook() (err error) {
|
||||
hook, err = database.RegisterHook(query.New(statusDBKey), &statusHook{})
|
||||
return err
|
||||
}
|
||||
|
||||
func stopStatusHook() error {
|
||||
return hook.Cancel()
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"github.com/safing/portbase/config"
|
||||
)
|
||||
|
||||
type (
|
||||
// SecurityLevelOption defines the returned function by ConfigIsActive.
|
||||
SecurityLevelOption func(minSecurityLevel uint8) bool
|
||||
)
|
||||
|
||||
func max(a, b uint8) uint8 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// ConfigIsActive returns whether the given security level dependent config option is on or off.
|
||||
func ConfigIsActive(name string) SecurityLevelOption {
|
||||
activeAtLevel := config.GetAsInt(name, int64(SecurityLevelsAll))
|
||||
return func(minSecurityLevel uint8) bool {
|
||||
return uint8(activeAtLevel())&max(ActiveSecurityLevel(), minSecurityLevel) > 0
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigIsActiveConcurrent returns whether the given security level dependent config option is on or off and is concurrency safe.
|
||||
func ConfigIsActiveConcurrent(name string) SecurityLevelOption {
|
||||
activeAtLevel := config.Concurrent.GetAsInt(name, int64(SecurityLevelsAll))
|
||||
return func(minSecurityLevel uint8) bool {
|
||||
return uint8(activeAtLevel())&max(ActiveSecurityLevel(), minSecurityLevel) > 0
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
activeSecurityLevel = new(uint32)
|
||||
selectedSecurityLevel = new(uint32)
|
||||
)
|
||||
|
||||
// ActiveSecurityLevel returns the current security level.
|
||||
func ActiveSecurityLevel() uint8 {
|
||||
return uint8(atomic.LoadUint32(activeSecurityLevel))
|
||||
}
|
||||
|
||||
// SelectedSecurityLevel returns the selected security level.
|
||||
func SelectedSecurityLevel() uint8 {
|
||||
return uint8(atomic.LoadUint32(selectedSecurityLevel))
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
package status
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
|
||||
// only test for panics
|
||||
// TODO: write real tests
|
||||
ActiveSecurityLevel()
|
||||
SelectedSecurityLevel()
|
||||
option := ConfigIsActive("invalid")
|
||||
option(0)
|
||||
option = ConfigIsActiveConcurrent("invalid")
|
||||
option(0)
|
||||
|
||||
}
|
60
status/mitigation.go
Normal file
60
status/mitigation.go
Normal file
|
@ -0,0 +1,60 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
)
|
||||
|
||||
type knownThreats struct {
|
||||
sync.RWMutex
|
||||
// active threats and their recommended mitigation level
|
||||
list map[string]uint8
|
||||
}
|
||||
|
||||
var threats = &knownThreats{
|
||||
list: make(map[string]uint8),
|
||||
}
|
||||
|
||||
// SetMitigationLevel sets the mitigation level for id
|
||||
// to mitigation. If mitigation is SecurityLevelOff the
|
||||
// mitigation record will be removed. If mitigation is
|
||||
// an invalid level the call to SetMitigationLevel is a
|
||||
// no-op.
|
||||
func SetMitigationLevel(id string, mitigation uint8) {
|
||||
if !IsValidSecurityLevel(mitigation) {
|
||||
log.Warningf("tried to set invalid mitigation level %d for threat %s", mitigation, id)
|
||||
return
|
||||
}
|
||||
|
||||
defer triggerAutopilot()
|
||||
|
||||
threats.Lock()
|
||||
defer threats.Unlock()
|
||||
if mitigation == 0 {
|
||||
delete(threats.list, id)
|
||||
} else {
|
||||
threats.list[id] = mitigation
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteMitigationLevel deletes the mitigation level for id.
|
||||
func DeleteMitigationLevel(id string) {
|
||||
SetMitigationLevel(id, SecurityLevelOff)
|
||||
}
|
||||
|
||||
// getHighestMitigationLevel returns the highest mitigation
|
||||
// level set on a threat.
|
||||
func getHighestMitigationLevel() uint8 {
|
||||
threats.RLock()
|
||||
defer threats.RUnlock()
|
||||
|
||||
var level uint8
|
||||
for _, lvl := range threats.list {
|
||||
if lvl > level {
|
||||
level = lvl
|
||||
}
|
||||
}
|
||||
|
||||
return level
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"github.com/safing/portbase/database"
|
||||
"github.com/safing/portbase/log"
|
||||
"context"
|
||||
|
||||
"github.com/safing/portbase/modules"
|
||||
"github.com/safing/portmaster/netenv"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -11,56 +12,25 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
module = modules.Register("status", nil, start, stop, "base")
|
||||
module = modules.Register("status", nil, start, nil, "base")
|
||||
}
|
||||
|
||||
func start() error {
|
||||
err := initSystemStatus()
|
||||
module.StartWorker("auto-pilot", autoPilot)
|
||||
triggerAutopilot()
|
||||
|
||||
err := module.RegisterEventHook(
|
||||
"netenv",
|
||||
netenv.OnlineStatusChangedEvent,
|
||||
"update online status in system status",
|
||||
func(_ context.Context, _ interface{}) error {
|
||||
triggerAutopilot()
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = startNetEnvHooking()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status.Save()
|
||||
|
||||
return initStatusHook()
|
||||
}
|
||||
|
||||
func initSystemStatus() error {
|
||||
// load status from database
|
||||
r, err := statusDB.Get(statusDBKey)
|
||||
switch err {
|
||||
case nil:
|
||||
loadedStatus, err := EnsureSystemStatus(r)
|
||||
if err != nil {
|
||||
log.Criticalf("status: failed to unwrap system status: %s", err)
|
||||
} else {
|
||||
status = loadedStatus
|
||||
}
|
||||
case database.ErrNotFound:
|
||||
// create new status
|
||||
default:
|
||||
log.Criticalf("status: failed to load system status: %s", err)
|
||||
}
|
||||
|
||||
status.Lock()
|
||||
defer status.Unlock()
|
||||
|
||||
// load status into atomic getters
|
||||
atomicUpdateSelectedSecurityLevel(status.SelectedSecurityLevel)
|
||||
|
||||
// update status
|
||||
status.updateThreatMitigationLevel()
|
||||
status.autopilot()
|
||||
status.updateOnlineStatus()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stop() error {
|
||||
return stopStatusHook()
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/safing/portmaster/netenv"
|
||||
)
|
||||
|
||||
// startNetEnvHooking starts the listener for online status changes.
|
||||
func startNetEnvHooking() error {
|
||||
return module.RegisterEventHook(
|
||||
"netenv",
|
||||
netenv.OnlineStatusChangedEvent,
|
||||
"update online status in system status",
|
||||
func(_ context.Context, _ interface{}) error {
|
||||
status.Lock()
|
||||
status.updateOnlineStatus()
|
||||
status.Unlock()
|
||||
status.Save()
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func (s *SystemStatus) updateOnlineStatus() {
|
||||
s.OnlineStatus = netenv.GetOnlineStatus()
|
||||
s.CaptivePortal = netenv.GetCaptivePortal()
|
||||
}
|
93
status/provider.go
Normal file
93
status/provider.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/safing/portbase/database/record"
|
||||
"github.com/safing/portbase/runtime"
|
||||
"github.com/safing/portmaster/netenv"
|
||||
)
|
||||
|
||||
var (
|
||||
pushUpdate runtime.PushFunc
|
||||
)
|
||||
|
||||
func setupRuntimeProvider() (err error) {
|
||||
// register the system status getter
|
||||
//
|
||||
statusProvider := runtime.SimpleValueGetterFunc(func(_ string) ([]record.Record, error) {
|
||||
return []record.Record{buildSystemStatus()}, nil
|
||||
})
|
||||
pushUpdate, err = runtime.Register("system/status", statusProvider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// register the selected security level setter
|
||||
//
|
||||
levelProvider := runtime.SimpleValueSetterFunc(setSelectedSecurityLevel)
|
||||
_, err = runtime.Register("system/security-level", levelProvider)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// setSelectedSecurityLevel updates the selected security level
|
||||
func setSelectedSecurityLevel(r record.Record) (record.Record, error) {
|
||||
var upd *SelectedSecurityLevelRecord
|
||||
if r.IsWrapped() {
|
||||
upd = new(SelectedSecurityLevelRecord)
|
||||
if err := record.Unwrap(r, upd); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// TODO(ppacher): this can actually never happen
|
||||
// as we're write-only and ValueProvider.Set() should
|
||||
// only ever be called from the HTTP API (so r must be wrapped).
|
||||
// Though, make sure we handle the case as well ...
|
||||
var ok bool
|
||||
upd, ok = r.(*SelectedSecurityLevelRecord)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected *SelectedSecurityLevelRecord but got %T", r)
|
||||
}
|
||||
}
|
||||
|
||||
if !IsValidSecurityLevel(upd.SelectedSecurityLevel) {
|
||||
return nil, fmt.Errorf("invalid security level: %d", upd.SelectedSecurityLevel)
|
||||
}
|
||||
|
||||
if SelectedSecurityLevel() != upd.SelectedSecurityLevel {
|
||||
setSelectedLevel(upd.SelectedSecurityLevel)
|
||||
triggerAutopilot()
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// buildSystemStatus build a new system status record.
|
||||
func buildSystemStatus() *SystemStatusRecord {
|
||||
status := &SystemStatusRecord{
|
||||
ActiveSecurityLevel: ActiveSecurityLevel(),
|
||||
SelectedSecurityLevel: SelectedSecurityLevel(),
|
||||
ThreatMitigationLevel: getHighestMitigationLevel(),
|
||||
CaptivePortal: netenv.GetCaptivePortal(),
|
||||
OnlineStatus: netenv.GetOnlineStatus(),
|
||||
}
|
||||
|
||||
status.CreateMeta()
|
||||
status.SetKey("runtime:system/status")
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// pushSystemStatus pushes a new system status via
|
||||
// the runtime database.
|
||||
func pushSystemStatus() {
|
||||
if pushUpdate == nil {
|
||||
return
|
||||
}
|
||||
|
||||
pushUpdate(buildSystemStatus())
|
||||
}
|
42
status/records.go
Normal file
42
status/records.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/safing/portbase/database/record"
|
||||
"github.com/safing/portmaster/netenv"
|
||||
)
|
||||
|
||||
// SystemStatusRecord describes the overall status of the Portmaster.
|
||||
// It's a read-only record exposed via runtime:system/status.
|
||||
type SystemStatusRecord struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
// ActiveSecurityLevel holds the currently
|
||||
// active security level.
|
||||
ActiveSecurityLevel uint8
|
||||
// SelectedSecurityLevel holds the security level
|
||||
// as selected by the user.
|
||||
SelectedSecurityLevel uint8
|
||||
// ThreatMitigationLevel holds the security level
|
||||
// as selected by the auto-pilot.
|
||||
ThreatMitigationLevel uint8
|
||||
// OnlineStatus holds the current online status as
|
||||
// seen by the netenv package.
|
||||
OnlineStatus netenv.OnlineStatus
|
||||
// CaptivePortal holds all information about the captive
|
||||
// portal of the network the portmaster is currently
|
||||
// connected to, if any.
|
||||
CaptivePortal *netenv.CaptivePortal
|
||||
}
|
||||
|
||||
// SelectedSecurityLevelRecord is used as a dummy record.Record
|
||||
// to provide a simply runtime-configuration for the user.
|
||||
// It is write-only and exposed at runtime:system/security-level
|
||||
type SelectedSecurityLevelRecord struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
SelectedSecurityLevel uint8
|
||||
}
|
114
status/security_level.go
Normal file
114
status/security_level.go
Normal file
|
@ -0,0 +1,114 @@
|
|||
package status
|
||||
|
||||
import "github.com/safing/portbase/config"
|
||||
|
||||
type (
|
||||
// SecurityLevelOptionFunc can be called with a minimum security level
|
||||
// and returns whether or not a given security option is enabled or
|
||||
// not.
|
||||
// Use SecurityLevelOption() to get a SecurityLevelOptionFunc for a
|
||||
// specific option.
|
||||
SecurityLevelOptionFunc func(minSecurityLevel uint8) bool
|
||||
)
|
||||
|
||||
// DisplayHintSecurityLevel is an external option hint for security levels.
|
||||
// It's meant to be used as a value for config.DisplayHintAnnotation.
|
||||
const DisplayHintSecurityLevel string = "security level"
|
||||
|
||||
// Security levels
|
||||
const (
|
||||
SecurityLevelOff uint8 = 0
|
||||
SecurityLevelNormal uint8 = 1
|
||||
SecurityLevelHigh uint8 = 2
|
||||
SecurityLevelExtreme uint8 = 4
|
||||
|
||||
SecurityLevelsNormalAndHigh uint8 = SecurityLevelNormal | SecurityLevelHigh
|
||||
SecurityLevelsNormalAndExtreme uint8 = SecurityLevelNormal | SecurityLevelExtreme
|
||||
SecurityLevelsHighAndExtreme uint8 = SecurityLevelHigh | SecurityLevelExtreme
|
||||
SecurityLevelsAll uint8 = SecurityLevelNormal | SecurityLevelHigh | SecurityLevelExtreme
|
||||
)
|
||||
|
||||
// SecurityLevelValues defines all possible security levels.
|
||||
var SecurityLevelValues = []config.PossibleValue{
|
||||
{
|
||||
Name: "Normal",
|
||||
Value: SecurityLevelsAll,
|
||||
},
|
||||
{
|
||||
Name: "High",
|
||||
Value: SecurityLevelsHighAndExtreme,
|
||||
},
|
||||
{
|
||||
Name: "Extreme",
|
||||
Value: SecurityLevelExtreme,
|
||||
},
|
||||
}
|
||||
|
||||
// AllSecurityLevelValues is like SecurityLevelValues but also includes Off.
|
||||
var AllSecurityLevelValues = append([]config.PossibleValue{
|
||||
{
|
||||
Name: "Off",
|
||||
Value: SecurityLevelOff,
|
||||
},
|
||||
},
|
||||
SecurityLevelValues...,
|
||||
)
|
||||
|
||||
// IsValidSecurityLevel returns true if level is a valid,
|
||||
// single security level. Level is also invalid if it's a
|
||||
// bitmask with more that one security level set.
|
||||
func IsValidSecurityLevel(level uint8) bool {
|
||||
return level == SecurityLevelOff ||
|
||||
level == SecurityLevelNormal ||
|
||||
level == SecurityLevelHigh ||
|
||||
level == SecurityLevelExtreme
|
||||
}
|
||||
|
||||
// IsValidSecurityLevelMask returns true if level is a valid
|
||||
// security level mask. It's like IsValidSecurityLevel but
|
||||
// also allows bitmask combinations.
|
||||
func IsValidSecurityLevelMask(level uint8) bool {
|
||||
return level <= 7
|
||||
}
|
||||
|
||||
func max(a, b uint8) uint8 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// SecurityLevelOption returns a function to check if the option
|
||||
// identified by name is active at a given minimum security level.
|
||||
// The returned function is safe for concurrent use with configuration
|
||||
// updates.
|
||||
func SecurityLevelOption(name string) SecurityLevelOptionFunc {
|
||||
activeAtLevel := config.Concurrent.GetAsInt(name, int64(SecurityLevelsAll))
|
||||
return func(minSecurityLevel uint8) bool {
|
||||
return uint8(activeAtLevel())&max(ActiveSecurityLevel(), minSecurityLevel) > 0
|
||||
}
|
||||
}
|
||||
|
||||
// SecurityLevelString returns the given security level as a string.
|
||||
func SecurityLevelString(level uint8) string {
|
||||
switch level {
|
||||
case SecurityLevelOff:
|
||||
return "Off"
|
||||
case SecurityLevelNormal:
|
||||
return "Normal"
|
||||
case SecurityLevelHigh:
|
||||
return "High"
|
||||
case SecurityLevelExtreme:
|
||||
return "Extreme"
|
||||
case SecurityLevelsNormalAndHigh:
|
||||
return "Normal and High"
|
||||
case SecurityLevelsNormalAndExtreme:
|
||||
return "Normal and Extreme"
|
||||
case SecurityLevelsHighAndExtreme:
|
||||
return "High and Extreme"
|
||||
case SecurityLevelsAll:
|
||||
return "Normal, High and Extreme"
|
||||
default:
|
||||
return "INVALID"
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
)
|
||||
|
||||
// autopilot automatically adjusts the security level as needed.
|
||||
func (s *SystemStatus) autopilot() {
|
||||
// check if users is overruling
|
||||
if s.SelectedSecurityLevel > SecurityLevelOff {
|
||||
s.ActiveSecurityLevel = s.SelectedSecurityLevel
|
||||
atomicUpdateActiveSecurityLevel(s.SelectedSecurityLevel)
|
||||
return
|
||||
}
|
||||
|
||||
// update active security level
|
||||
switch s.ThreatMitigationLevel {
|
||||
case SecurityLevelOff:
|
||||
s.ActiveSecurityLevel = SecurityLevelNormal
|
||||
atomicUpdateActiveSecurityLevel(SecurityLevelNormal)
|
||||
case SecurityLevelNormal, SecurityLevelHigh, SecurityLevelExtreme:
|
||||
s.ActiveSecurityLevel = s.ThreatMitigationLevel
|
||||
atomicUpdateActiveSecurityLevel(s.ThreatMitigationLevel)
|
||||
default:
|
||||
log.Errorf("status: threat mitigation level is set to invalid value: %d", s.ThreatMitigationLevel)
|
||||
}
|
||||
}
|
||||
|
||||
// setSelectedSecurityLevel sets the selected security level.
|
||||
func setSelectedSecurityLevel(level uint8) {
|
||||
switch level {
|
||||
case SecurityLevelOff, SecurityLevelNormal, SecurityLevelHigh, SecurityLevelExtreme:
|
||||
status.Lock()
|
||||
|
||||
status.SelectedSecurityLevel = level
|
||||
atomicUpdateSelectedSecurityLevel(level)
|
||||
status.autopilot()
|
||||
|
||||
status.Unlock()
|
||||
status.Save()
|
||||
default:
|
||||
log.Errorf("status: tried to set selected security level to invalid value: %d", level)
|
||||
}
|
||||
}
|
||||
|
||||
func atomicUpdateActiveSecurityLevel(level uint8) {
|
||||
atomic.StoreUint32(activeSecurityLevel, uint32(level))
|
||||
}
|
||||
|
||||
func atomicUpdateSelectedSecurityLevel(level uint8) {
|
||||
atomic.StoreUint32(selectedSecurityLevel, uint32(level))
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
package status
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSet(t *testing.T) {
|
||||
|
||||
// only test for panics
|
||||
// TODO: write real tests
|
||||
setSelectedSecurityLevel(0)
|
||||
|
||||
}
|
30
status/state.go
Normal file
30
status/state.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
activeLevel = new(uint32)
|
||||
selectedLevel = new(uint32)
|
||||
)
|
||||
|
||||
func setActiveLevel(lvl uint8) {
|
||||
atomic.StoreUint32(activeLevel, uint32(lvl))
|
||||
}
|
||||
|
||||
func setSelectedLevel(lvl uint8) {
|
||||
atomic.StoreUint32(selectedLevel, uint32(lvl))
|
||||
}
|
||||
|
||||
// ActiveSecurityLevel returns the currently active security
|
||||
// level.
|
||||
func ActiveSecurityLevel() uint8 {
|
||||
return uint8(atomic.LoadUint32(activeLevel))
|
||||
}
|
||||
|
||||
// SelectedSecurityLevel returns the security level as selected
|
||||
// by the user.
|
||||
func SelectedSecurityLevel() uint8 {
|
||||
return uint8(atomic.LoadUint32(selectedLevel))
|
||||
}
|
113
status/status.go
113
status/status.go
|
@ -1,113 +0,0 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/safing/portmaster/netenv"
|
||||
|
||||
"github.com/safing/portbase/database/record"
|
||||
"github.com/safing/portbase/log"
|
||||
)
|
||||
|
||||
var (
|
||||
status *SystemStatus
|
||||
)
|
||||
|
||||
func init() {
|
||||
status = &SystemStatus{
|
||||
Threats: make(map[string]*Threat),
|
||||
}
|
||||
status.SetKey(statusDBKey)
|
||||
}
|
||||
|
||||
// SystemStatus saves basic information about the current system status.
|
||||
//nolint:maligned // TODO
|
||||
type SystemStatus struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
ActiveSecurityLevel uint8
|
||||
SelectedSecurityLevel uint8
|
||||
|
||||
OnlineStatus netenv.OnlineStatus
|
||||
CaptivePortal *netenv.CaptivePortal
|
||||
|
||||
ThreatMitigationLevel uint8
|
||||
Threats map[string]*Threat
|
||||
}
|
||||
|
||||
// SaveAsync saves the SystemStatus to the database asynchronously.
|
||||
func (s *SystemStatus) SaveAsync() {
|
||||
module.StartWorker("save system status", func(_ context.Context) error {
|
||||
s.Save()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Save saves the SystemStatus to the database.
|
||||
func (s *SystemStatus) Save() {
|
||||
err := statusDB.Put(s)
|
||||
if err != nil {
|
||||
log.Errorf("status: could not save status to database: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// EnsureSystemStatus ensures that the given record is of type SystemStatus and unwraps it, if needed.
|
||||
func EnsureSystemStatus(r record.Record) (*SystemStatus, error) {
|
||||
// unwrap
|
||||
if r.IsWrapped() {
|
||||
// only allocate a new struct, if we need it
|
||||
new := &SystemStatus{}
|
||||
err := record.Unwrap(r, new)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new, nil
|
||||
}
|
||||
|
||||
// or adjust type
|
||||
new, ok := r.(*SystemStatus)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("record not of type *SystemStatus, but %T", r)
|
||||
}
|
||||
return new, nil
|
||||
}
|
||||
|
||||
// FmtActiveSecurityLevel returns the current security level as a string.
|
||||
func FmtActiveSecurityLevel() string {
|
||||
status.Lock()
|
||||
mitigationLevel := status.ThreatMitigationLevel
|
||||
status.Unlock()
|
||||
active := ActiveSecurityLevel()
|
||||
s := FmtSecurityLevel(active)
|
||||
if mitigationLevel > 0 && active != mitigationLevel {
|
||||
s += "*"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// FmtSecurityLevel returns the given security level as a string.
|
||||
func FmtSecurityLevel(level uint8) string {
|
||||
switch level {
|
||||
case SecurityLevelOff:
|
||||
return "Off"
|
||||
case SecurityLevelNormal:
|
||||
return "Normal"
|
||||
case SecurityLevelHigh:
|
||||
return "High"
|
||||
case SecurityLevelExtreme:
|
||||
return "Extreme"
|
||||
case SecurityLevelsNormalAndHigh:
|
||||
return "Normal and High"
|
||||
case SecurityLevelsNormalAndExtreme:
|
||||
return "Normal and Extreme"
|
||||
case SecurityLevelsHighAndExtreme:
|
||||
return "High and Extreme"
|
||||
case SecurityLevelsAll:
|
||||
return "Normal, High and Extreme"
|
||||
default:
|
||||
return "INVALID"
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package status
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestStatus(t *testing.T) {
|
||||
|
||||
setSelectedSecurityLevel(SecurityLevelOff)
|
||||
if FmtActiveSecurityLevel() != "Normal" {
|
||||
t.Errorf("unexpected string representation: %s", FmtActiveSecurityLevel())
|
||||
}
|
||||
|
||||
setSelectedSecurityLevel(SecurityLevelNormal)
|
||||
AddOrUpdateThreat(&Threat{MitigationLevel: SecurityLevelHigh})
|
||||
if FmtActiveSecurityLevel() != "Normal*" {
|
||||
t.Errorf("unexpected string representation: %s", FmtActiveSecurityLevel())
|
||||
}
|
||||
|
||||
setSelectedSecurityLevel(SecurityLevelHigh)
|
||||
if FmtActiveSecurityLevel() != "High" {
|
||||
t.Errorf("unexpected string representation: %s", FmtActiveSecurityLevel())
|
||||
}
|
||||
|
||||
setSelectedSecurityLevel(SecurityLevelHigh)
|
||||
AddOrUpdateThreat(&Threat{MitigationLevel: SecurityLevelExtreme})
|
||||
if FmtActiveSecurityLevel() != "High*" {
|
||||
t.Errorf("unexpected string representation: %s", FmtActiveSecurityLevel())
|
||||
}
|
||||
|
||||
setSelectedSecurityLevel(SecurityLevelExtreme)
|
||||
if FmtActiveSecurityLevel() != "Extreme" {
|
||||
t.Errorf("unexpected string representation: %s", FmtActiveSecurityLevel())
|
||||
}
|
||||
|
||||
}
|
160
status/threat.go
160
status/threat.go
|
@ -1,73 +1,131 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/safing/portbase/log"
|
||||
"github.com/safing/portbase/notifications"
|
||||
)
|
||||
|
||||
// Threat describes a detected threat.
|
||||
// Threat represents a threat to the system.
|
||||
// A threat is basically a notification with strong
|
||||
// typed EventData. Use the methods expored on Threat
|
||||
// to manipulate the EventData field and push updates
|
||||
// of the notification.
|
||||
// Do not use EventData directly!
|
||||
type Threat struct {
|
||||
ID string // A unique ID chosen by reporting module (eg. modulePrefix-incident) to periodically check threat existence
|
||||
Name string // Descriptive (human readable) name for detected threat
|
||||
Description string // Simple description
|
||||
AdditionalData interface{} // Additional data a module wants to make available for the user
|
||||
MitigationLevel uint8 // Recommended Security Level to switch to for mitigation
|
||||
*notifications.Notification
|
||||
}
|
||||
|
||||
// ThreatPayload holds threat related information.
|
||||
type ThreatPayload struct {
|
||||
// MitigationLevel holds the recommended security
|
||||
// level to mitigate the threat.
|
||||
MitigationLevel uint8
|
||||
// Started holds the UNIX epoch timestamp in seconds
|
||||
// at which the threat has been detected the first time.
|
||||
Started int64
|
||||
// Ended holds the UNIX epoch timestamp in seconds
|
||||
// at which the threat has been detected the last time.
|
||||
Ended int64
|
||||
// TODO: add locking
|
||||
// Data may holds threat-specific data.
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
// AddOrUpdateThreat adds or updates a new threat in the system status.
|
||||
func AddOrUpdateThreat(new *Threat) {
|
||||
status.Lock()
|
||||
defer status.Unlock()
|
||||
// NewThreat returns a new threat. Note that the
|
||||
// threat only gets published once Publish is called.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// threat := NewThreat("portscan", "Someone is scanning you").
|
||||
// SetData(portscanResult).
|
||||
// SetMitigationLevel(SecurityLevelExtreme).
|
||||
// Publish()
|
||||
//
|
||||
// // Once you're done, delete the threat
|
||||
// threat.Delete().Publish()
|
||||
//
|
||||
func NewThreat(id, msg string) *Threat {
|
||||
t := &Threat{
|
||||
Notification: ¬ifications.Notification{
|
||||
EventID: id,
|
||||
Message: msg,
|
||||
Type: notifications.Warning,
|
||||
State: notifications.Active,
|
||||
},
|
||||
}
|
||||
t.threatData().Started = time.Now().Unix()
|
||||
|
||||
status.Threats[new.ID] = new
|
||||
status.updateThreatMitigationLevel()
|
||||
status.autopilot()
|
||||
|
||||
status.SaveAsync()
|
||||
return t
|
||||
}
|
||||
|
||||
// DeleteThreat deletes a threat from the system status.
|
||||
func DeleteThreat(id string) {
|
||||
status.Lock()
|
||||
defer status.Unlock()
|
||||
// SetData sets the data member of the threat payload.
|
||||
func (t *Threat) SetData(data interface{}) *Threat {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
delete(status.Threats, id)
|
||||
status.updateThreatMitigationLevel()
|
||||
status.autopilot()
|
||||
|
||||
status.SaveAsync()
|
||||
t.threatData().Data = data
|
||||
return t
|
||||
}
|
||||
|
||||
// GetThreats returns all threats who's IDs are prefixed by the given string, and also a locker for editing them.
|
||||
func GetThreats(idPrefix string) ([]*Threat, sync.Locker) {
|
||||
status.Lock()
|
||||
defer status.Unlock()
|
||||
// SetMitigationLevel sets the mitigation level of the
|
||||
// threat data.
|
||||
func (t *Threat) SetMitigationLevel(lvl uint8) *Threat {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
var exportedThreats []*Threat
|
||||
for id, threat := range status.Threats {
|
||||
if strings.HasPrefix(id, idPrefix) {
|
||||
exportedThreats = append(exportedThreats, threat)
|
||||
}
|
||||
}
|
||||
|
||||
return exportedThreats, &status.Mutex
|
||||
t.threatData().MitigationLevel = lvl
|
||||
return t
|
||||
}
|
||||
|
||||
func (s *SystemStatus) updateThreatMitigationLevel() {
|
||||
// get highest mitigationLevel
|
||||
var mitigationLevel uint8
|
||||
for _, threat := range s.Threats {
|
||||
switch threat.MitigationLevel {
|
||||
case SecurityLevelNormal, SecurityLevelHigh, SecurityLevelExtreme:
|
||||
if threat.MitigationLevel > mitigationLevel {
|
||||
mitigationLevel = threat.MitigationLevel
|
||||
}
|
||||
}
|
||||
// Delete sets the ended timestamp of the threat.
|
||||
func (t *Threat) Delete() *Threat {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.threatData().Ended = time.Now().Unix()
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Payload returns a copy of the threat payload.
|
||||
func (t *Threat) Payload() ThreatPayload {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
return *t.threatData() // creates a copy
|
||||
}
|
||||
|
||||
// Publish publishes the current threat.
|
||||
// Publish should always be called when changes to
|
||||
// the threat are recorded.
|
||||
func (t *Threat) Publish() *Threat {
|
||||
data := t.Payload()
|
||||
if data.Ended > 0 {
|
||||
DeleteMitigationLevel(t.EventID)
|
||||
} else {
|
||||
SetMitigationLevel(t.EventID, data.MitigationLevel)
|
||||
}
|
||||
|
||||
// set new ThreatMitigationLevel
|
||||
s.ThreatMitigationLevel = mitigationLevel
|
||||
t.Save()
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// threatData returns the threat payload associated with this
|
||||
// threat. If not data has been created yet a new ThreatPayload
|
||||
// is attached to t and returned. The caller must make sure to
|
||||
// hold appropriate locks when working with the returned payload.
|
||||
func (t *Threat) threatData() *ThreatPayload {
|
||||
if t.EventData == nil {
|
||||
t.EventData = new(ThreatPayload)
|
||||
}
|
||||
|
||||
payload, ok := t.EventData.(*ThreatPayload)
|
||||
if !ok {
|
||||
log.Warningf("unexpected type %T in thread notification payload", t.EventData)
|
||||
return new(ThreatPayload)
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue