mirror of
https://github.com/safing/portbase
synced 2025-09-02 18:50:14 +00:00
Complete config revamp / specific parts have been moved to the portmaster repo
This commit is contained in:
parent
b727cee973
commit
ffc13d6e16
10 changed files with 327 additions and 539 deletions
57
config/get.go
Normal file
57
config/get.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/tevino/abool"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
validityFlag = abool.NewBool(true)
|
||||||
|
validityFlagLock sync.RWMutex
|
||||||
|
|
||||||
|
tableLock sync.RWMutex
|
||||||
|
|
||||||
|
stringTable map[string]string
|
||||||
|
intTable map[string]int
|
||||||
|
boolTable map[string]bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func getValidityFlag() *abool.AtomicBool {
|
||||||
|
validityFlagLock.RLock()
|
||||||
|
defer validityFlagLock.RUnlock()
|
||||||
|
return validityFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetValidityFlag() {
|
||||||
|
validityFlagLock.Lock()
|
||||||
|
defer validityFlagLock.Unlock()
|
||||||
|
validityFlag.SetTo(false)
|
||||||
|
validityFlag = abool.NewBool(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAsString returns a function that returns the wanted string with high performance.
|
||||||
|
func GetAsString(name string, fallback string) func() string {
|
||||||
|
valid := getValidityFlag()
|
||||||
|
value := findStringValue(name, fallback)
|
||||||
|
return func() string {
|
||||||
|
if !valid.IsSet() {
|
||||||
|
valid = getValidityFlag()
|
||||||
|
value = findStringValue(name, fallback)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAsInt returns a function that returns the wanted int with high performance.
|
||||||
|
func GetAsInt(name string, fallback int64) func() int64 {
|
||||||
|
valid := getValidityFlag()
|
||||||
|
value := findIntValue(name, fallback)
|
||||||
|
return func() int64 {
|
||||||
|
if !valid.IsSet() {
|
||||||
|
valid = getValidityFlag()
|
||||||
|
value = findIntValue(name, fallback)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
137
config/get_test.go
Normal file
137
config/get_test.go
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
|
||||||
|
err := SetConfig(`
|
||||||
|
{
|
||||||
|
"monkey": "1",
|
||||||
|
"elephant": 2
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SetDefaultConfig(`
|
||||||
|
{
|
||||||
|
"monkey": "0",
|
||||||
|
"snake": "0",
|
||||||
|
"elephant": 0
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
monkey := GetAsString("monkey", "none")
|
||||||
|
elephant := GetAsInt("elephant", -1)
|
||||||
|
if monkey() != "1" {
|
||||||
|
t.Fatalf("monkey should be 1, is %s", monkey())
|
||||||
|
}
|
||||||
|
if elephant() != 2 {
|
||||||
|
t.Fatalf("elephant should be 2, is %d", elephant())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SetConfig(`
|
||||||
|
{
|
||||||
|
"monkey": "3"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if monkey() != "3" {
|
||||||
|
t.Fatalf("monkey should be 0, is %s", monkey())
|
||||||
|
}
|
||||||
|
if elephant() != 0 {
|
||||||
|
t.Fatalf("elephant should be 0, is %d", elephant())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetAsStringCached(b *testing.B) {
|
||||||
|
// Setup
|
||||||
|
err := SetConfig(`
|
||||||
|
{
|
||||||
|
"monkey": "banana"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
monkey := GetAsString("monkey", "no banana")
|
||||||
|
|
||||||
|
// Reset timer for precise results
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
// Start benchmark
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
monkey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetAsStringRefetch(b *testing.B) {
|
||||||
|
// Setup
|
||||||
|
err := SetConfig(`
|
||||||
|
{
|
||||||
|
"monkey": "banana"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset timer for precise results
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
// Start benchmark
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
findStringValue("monkey", "no banana")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetAsIntCached(b *testing.B) {
|
||||||
|
// Setup
|
||||||
|
err := SetConfig(`
|
||||||
|
{
|
||||||
|
"monkey": 1
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
monkey := GetAsInt("monkey", -1)
|
||||||
|
|
||||||
|
// Reset timer for precise results
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
// Start benchmark
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
monkey()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetAsIntRefetch(b *testing.B) {
|
||||||
|
// Setup
|
||||||
|
err := SetConfig(`
|
||||||
|
{
|
||||||
|
"monkey": 1
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset timer for precise results
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
// Start benchmark
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
findIntValue("monkey", 1)
|
||||||
|
}
|
||||||
|
}
|
82
config/layers.go
Normal file
82
config/layers.go
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
configLock sync.RWMutex
|
||||||
|
|
||||||
|
userConfig = ""
|
||||||
|
defaultConfig = ""
|
||||||
|
|
||||||
|
// ErrInvalidJSON is returned by SetConfig and SetDefaultConfig if they receive invalid json.
|
||||||
|
ErrInvalidJSON = errors.New("json string invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetConfig sets the (prioritized) user defined config.
|
||||||
|
func SetConfig(json string) error {
|
||||||
|
if !gjson.Valid(json) {
|
||||||
|
return ErrInvalidJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
configLock.Lock()
|
||||||
|
defer configLock.Unlock()
|
||||||
|
userConfig = json
|
||||||
|
resetValidityFlag()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultConfig sets the (fallback) default config.
|
||||||
|
func SetDefaultConfig(json string) error {
|
||||||
|
if !gjson.Valid(json) {
|
||||||
|
return ErrInvalidJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
configLock.Lock()
|
||||||
|
defer configLock.Unlock()
|
||||||
|
defaultConfig = json
|
||||||
|
resetValidityFlag()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findValue find the correct value in the user or default config
|
||||||
|
func findValue(name string) (result gjson.Result) {
|
||||||
|
configLock.RLock()
|
||||||
|
defer configLock.RUnlock()
|
||||||
|
|
||||||
|
result = gjson.Get(userConfig, name)
|
||||||
|
if !result.Exists() {
|
||||||
|
result = gjson.Get(defaultConfig, name)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// findStringValue validates and return the value with the given name
|
||||||
|
func findStringValue(name string, fallback string) (value string) {
|
||||||
|
result := findValue(name)
|
||||||
|
if !result.Exists() {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
if result.Type != gjson.String {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// findIntValue validates and return the value with the given name
|
||||||
|
func findIntValue(name string, fallback int64) (value int64) {
|
||||||
|
result := findValue(name)
|
||||||
|
if !result.Exists() {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
if result.Type != gjson.Number {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return result.Int()
|
||||||
|
}
|
51
config/layers_test.go
Normal file
51
config/layers_test.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestLayers(t *testing.T) {
|
||||||
|
|
||||||
|
err := SetConfig("{invalid json")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SetDefaultConfig("{invalid json")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = SetConfig(`
|
||||||
|
{
|
||||||
|
"monkey": "banana",
|
||||||
|
"elephant": 3
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test missing values
|
||||||
|
|
||||||
|
missingString := GetAsString("missing", "fallback")
|
||||||
|
if missingString() != "fallback" {
|
||||||
|
t.Fatal("expected fallback value: fallback")
|
||||||
|
}
|
||||||
|
|
||||||
|
missingInt := GetAsInt("missing", -1)
|
||||||
|
if missingInt() != -1 {
|
||||||
|
t.Fatal("expected fallback value: -1")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test value mismatch
|
||||||
|
|
||||||
|
notString := GetAsString("elephant", "fallback")
|
||||||
|
if notString() != "fallback" {
|
||||||
|
t.Fatal("expected fallback value: fallback")
|
||||||
|
}
|
||||||
|
|
||||||
|
notInt := GetAsInt("monkey", -1)
|
||||||
|
if notInt() != -1 {
|
||||||
|
t.Fatal("expected fallback value: -1")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,96 +0,0 @@
|
||||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"github.com/Safing/safing-core/database"
|
|
||||||
|
|
||||||
datastore "github.com/ipfs/go-datastore"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SecurityLevelBoolean int8
|
|
||||||
|
|
||||||
func (slb SecurityLevelBoolean) IsSet() bool {
|
|
||||||
return int8(atomic.LoadInt32(securityLevel)) >= int8(slb)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (slb SecurityLevelBoolean) IsSetWithLevel(customSecurityLevel int8) bool {
|
|
||||||
return customSecurityLevel >= int8(slb) || int8(atomic.LoadInt32(securityLevel)) >= int8(slb)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (slb SecurityLevelBoolean) Level() int8 {
|
|
||||||
return int8(slb)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Configuration struct {
|
|
||||||
database.Base
|
|
||||||
|
|
||||||
// Security Config
|
|
||||||
EnforceCT SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Hardfail on Certificate Transparency
|
|
||||||
EnforceRevocation SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Hardfail on Certificate Revokation
|
|
||||||
DenyInsecureTLS SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Block TLS connections, that use insecure TLS versions, cipher suites, ...
|
|
||||||
DenyTLSWithoutSNI SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Block TLS connections that do not use SNI, connections without SNI cannot be verified as well as connections with SNI.
|
|
||||||
DoNotUseAssignedDNS SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Do not use DNS Servers assigned by DHCP
|
|
||||||
DoNotUseMDNS SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Do not use mDNS
|
|
||||||
DoNotForwardSpecialDomains SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Do not resolve special domains with assigned DNS Servers
|
|
||||||
AlwaysPromptAtNewProfile SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Always prompt user to review new profiles
|
|
||||||
DenyNetworkUntilProfileApproved SecurityLevelBoolean `json:",omitempty bson:",omitempty"` // Deny network communication until a new profile is actively approved by the user
|
|
||||||
|
|
||||||
// Generic Config
|
|
||||||
CompetenceLevel int8 `json:",omitempty bson:",omitempty"` // Select CompetenceLevel
|
|
||||||
Beta bool `json:",omitempty bson:",omitempty"` // Take part in Beta
|
|
||||||
PermanentVerdicts bool `json:",omitempty bson:",omitempty"` // As soon as work on a link is finished, leave it to the system for performance and stability
|
|
||||||
DNSServers []string `json:",omitempty bson:",omitempty"` // DNS Servers to use for name resolution. Please refer to the user guide for further help.
|
|
||||||
// regex: ^(DoH|DNS|TDNS)\|[A-Za-z0-9\.:\[\]]+(\|[A-Za-z0-9\.:]+)?$
|
|
||||||
DNSServerRetryRate int64 `json:",omitempty bson:",omitempty"` // Amount of seconds to wait until failing DNS Servers may be retried.
|
|
||||||
CountryBlacklist []string `json:",omitempty bson:",omitempty"` // Do not connect to servers in these countries
|
|
||||||
ASBlacklist []uint32 `json:",omitempty bson:",omitempty"` // Do not connect to server in these AS
|
|
||||||
|
|
||||||
LocalPort17Node bool `json:",omitempty bson:",omitempty"` // Serve as local Port17 Node
|
|
||||||
PublicPort17Node bool `json:",omitempty bson:",omitempty"` // Serve as public Port17 Node
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
configurationModel *Configuration // only use this as parameter for database.EnsureModel-like functions
|
|
||||||
configurationInstanceName = "config"
|
|
||||||
defaultConfigurationInstanceName = "default"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initConfigurationModel() {
|
|
||||||
database.RegisterModel(configurationModel, func() database.Model { return new(Configuration) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create saves Configuration with the provided name in the default namespace.
|
|
||||||
func (m *Configuration) Create(name string) error {
|
|
||||||
return m.CreateObject(&database.Me, name, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateInNamespace saves Configuration with the provided name in the provided namespace.
|
|
||||||
func (m *Configuration) CreateInNamespace(namespace *datastore.Key, name string) error {
|
|
||||||
return m.CreateObject(namespace, name, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save saves Configuration.
|
|
||||||
func (m *Configuration) Save() error {
|
|
||||||
return m.SaveObject(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConfiguration fetches Configuration with the provided name in the default namespace.
|
|
||||||
func GetConfiguration(name string) (*Configuration, error) {
|
|
||||||
return GetConfigurationFromNamespace(&database.Me, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConfigurationFromNamespace fetches Configuration with the provided name in the provided namespace.
|
|
||||||
func GetConfigurationFromNamespace(namespace *datastore.Key, name string) (*Configuration, error) {
|
|
||||||
object, err := database.GetAndEnsureModel(namespace, name, configurationModel)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
model, ok := object.(*Configuration)
|
|
||||||
if !ok {
|
|
||||||
return nil, database.NewMismatchError(object, configurationModel)
|
|
||||||
}
|
|
||||||
return model, nil
|
|
||||||
}
|
|
|
@ -1,238 +0,0 @@
|
||||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Safing/safing-core/database"
|
|
||||||
"github.com/Safing/safing-core/log"
|
|
||||||
"github.com/Safing/safing-core/modules"
|
|
||||||
)
|
|
||||||
|
|
||||||
// think about:
|
|
||||||
// config changes validation (e.g. if on in secure mode, must be on in fortress mode)
|
|
||||||
// config switches
|
|
||||||
// small codebase
|
|
||||||
// nice api
|
|
||||||
// be static as much as possible
|
|
||||||
|
|
||||||
const (
|
|
||||||
SecurityLevelOff int8 = 0
|
|
||||||
SecurityLevelDynamic int8 = 1
|
|
||||||
SecurityLevelSecure int8 = 2
|
|
||||||
SecurityLevelFortress int8 = 3
|
|
||||||
|
|
||||||
CompetenceLevelNone int8 = 0
|
|
||||||
CompetenceLevelBasic int8 = 1
|
|
||||||
CompetenceLevelPowerUser int8 = 2
|
|
||||||
CompetenceLevelExpert int8 = 3
|
|
||||||
|
|
||||||
StatusOk int8 = 0
|
|
||||||
StatusWarning int8 = 1
|
|
||||||
StatusError int8 = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
configurationModule *modules.Module
|
|
||||||
|
|
||||||
lastChange *int64
|
|
||||||
securityLevel *int32
|
|
||||||
|
|
||||||
lock sync.RWMutex
|
|
||||||
status *SystemStatus
|
|
||||||
currentConfig *Configuration
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
configurationModule = modules.Register("Configuration", 128)
|
|
||||||
|
|
||||||
initDefaultConfig()
|
|
||||||
initSystemStatusModel()
|
|
||||||
initConfigurationModel()
|
|
||||||
|
|
||||||
lastChangeValue := time.Now().Unix()
|
|
||||||
lastChange = &lastChangeValue
|
|
||||||
|
|
||||||
var securityLevelValue int32
|
|
||||||
securityLevel = &securityLevelValue
|
|
||||||
|
|
||||||
var err error
|
|
||||||
config, err := GetConfiguration(configurationInstanceName)
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("configuration: could not load configuration: %s", err)
|
|
||||||
loadedConfig := defaultConfig
|
|
||||||
config = &loadedConfig
|
|
||||||
err = config.Create(configurationInstanceName)
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("configuration: could not save new configuration: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
status, err = GetSystemStatus()
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("configuration: could not load status: %s", err)
|
|
||||||
status = &SystemStatus{
|
|
||||||
CurrentSecurityLevel: 1,
|
|
||||||
SelectedSecurityLevel: 1,
|
|
||||||
}
|
|
||||||
err = status.Create()
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("configuration: could not save new status: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("configuration: initial security level is [%s]", status.FmtSecurityLevel())
|
|
||||||
// atomic.StoreInt32(securityLevel, int32(status.CurrentSecurityLevel))
|
|
||||||
|
|
||||||
updateConfig(config)
|
|
||||||
|
|
||||||
go configChangeListener()
|
|
||||||
go statusChangeListener()
|
|
||||||
}
|
|
||||||
|
|
||||||
func configChangeListener() {
|
|
||||||
sub := database.NewSubscription()
|
|
||||||
sub.Subscribe(fmt.Sprintf("%s/Configuration:%s", database.Me.String(), configurationInstanceName))
|
|
||||||
for {
|
|
||||||
var receivedModel database.Model
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-configurationModule.Stop:
|
|
||||||
configurationModule.StopComplete()
|
|
||||||
return
|
|
||||||
case receivedModel = <-sub.Updated:
|
|
||||||
case receivedModel = <-sub.Created:
|
|
||||||
}
|
|
||||||
|
|
||||||
config, ok := database.SilentEnsureModel(receivedModel, configurationModel).(*Configuration)
|
|
||||||
if !ok {
|
|
||||||
log.Warning("configuration: received config update, but was not of type *Configuration")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
updateConfig(config)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateConfig(update *Configuration) {
|
|
||||||
new := &Configuration{}
|
|
||||||
|
|
||||||
if update.EnforceCT > 0 && update.EnforceCT < 4 {
|
|
||||||
new.EnforceCT = update.EnforceCT
|
|
||||||
} else {
|
|
||||||
new.EnforceCT = defaultConfig.EnforceCT
|
|
||||||
}
|
|
||||||
if update.EnforceRevocation > 0 && update.EnforceRevocation < 4 {
|
|
||||||
new.EnforceRevocation = update.EnforceRevocation
|
|
||||||
} else {
|
|
||||||
new.EnforceRevocation = defaultConfig.EnforceRevocation
|
|
||||||
}
|
|
||||||
if update.DenyInsecureTLS > 0 && update.DenyInsecureTLS < 4 {
|
|
||||||
new.DenyInsecureTLS = update.DenyInsecureTLS
|
|
||||||
} else {
|
|
||||||
new.DenyInsecureTLS = defaultConfig.DenyInsecureTLS
|
|
||||||
}
|
|
||||||
if update.DenyTLSWithoutSNI > 0 && update.DenyTLSWithoutSNI < 4 {
|
|
||||||
new.DenyTLSWithoutSNI = update.DenyTLSWithoutSNI
|
|
||||||
} else {
|
|
||||||
new.DenyTLSWithoutSNI = defaultConfig.DenyTLSWithoutSNI
|
|
||||||
}
|
|
||||||
if update.DoNotUseAssignedDNS > 0 && update.DoNotUseAssignedDNS < 4 {
|
|
||||||
new.DoNotUseAssignedDNS = update.DoNotUseAssignedDNS
|
|
||||||
} else {
|
|
||||||
new.DoNotUseAssignedDNS = defaultConfig.DoNotUseAssignedDNS
|
|
||||||
}
|
|
||||||
if update.DoNotUseMDNS > 0 && update.DoNotUseMDNS < 4 {
|
|
||||||
new.DoNotUseMDNS = update.DoNotUseMDNS
|
|
||||||
} else {
|
|
||||||
new.DoNotUseMDNS = defaultConfig.DoNotUseMDNS
|
|
||||||
}
|
|
||||||
if update.DoNotForwardSpecialDomains > 0 && update.DoNotForwardSpecialDomains < 4 {
|
|
||||||
new.DoNotForwardSpecialDomains = update.DoNotForwardSpecialDomains
|
|
||||||
} else {
|
|
||||||
new.DoNotForwardSpecialDomains = defaultConfig.DoNotForwardSpecialDomains
|
|
||||||
}
|
|
||||||
if update.AlwaysPromptAtNewProfile > 0 && update.AlwaysPromptAtNewProfile < 4 {
|
|
||||||
new.AlwaysPromptAtNewProfile = update.AlwaysPromptAtNewProfile
|
|
||||||
} else {
|
|
||||||
new.AlwaysPromptAtNewProfile = defaultConfig.AlwaysPromptAtNewProfile
|
|
||||||
}
|
|
||||||
if update.DenyNetworkUntilProfileApproved > 0 && update.DenyNetworkUntilProfileApproved < 4 {
|
|
||||||
new.DenyNetworkUntilProfileApproved = update.DenyNetworkUntilProfileApproved
|
|
||||||
} else {
|
|
||||||
new.DenyNetworkUntilProfileApproved = defaultConfig.DenyNetworkUntilProfileApproved
|
|
||||||
}
|
|
||||||
|
|
||||||
// generic configuration
|
|
||||||
if update.CompetenceLevel >= 0 && update.CompetenceLevel <= 3 {
|
|
||||||
new.CompetenceLevel = update.CompetenceLevel
|
|
||||||
} else {
|
|
||||||
new.CompetenceLevel = 3
|
|
||||||
// TODO: maybe notify user?
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(update.DNSServers) != 0 {
|
|
||||||
new.DNSServers = update.DNSServers
|
|
||||||
} else {
|
|
||||||
new.DNSServers = defaultConfig.DNSServers
|
|
||||||
}
|
|
||||||
|
|
||||||
if update.DNSServerRetryRate != 0 {
|
|
||||||
new.DNSServerRetryRate = update.DNSServerRetryRate
|
|
||||||
} else {
|
|
||||||
new.DNSServerRetryRate = defaultConfig.DNSServerRetryRate
|
|
||||||
}
|
|
||||||
if len(update.CountryBlacklist) != 0 {
|
|
||||||
new.CountryBlacklist = update.CountryBlacklist
|
|
||||||
} else {
|
|
||||||
new.CountryBlacklist = defaultConfig.CountryBlacklist
|
|
||||||
}
|
|
||||||
if len(update.ASBlacklist) != 0 {
|
|
||||||
new.ASBlacklist = update.ASBlacklist
|
|
||||||
} else {
|
|
||||||
new.ASBlacklist = defaultConfig.ASBlacklist
|
|
||||||
}
|
|
||||||
|
|
||||||
lock.Lock()
|
|
||||||
defer lock.Unlock()
|
|
||||||
|
|
||||||
// set new config and update timestamp
|
|
||||||
currentConfig = new
|
|
||||||
atomic.StoreInt64(lastChange, time.Now().UnixNano())
|
|
||||||
|
|
||||||
// update status with new values
|
|
||||||
// status.CurrentSecurityLevel = currentConfig.SecurityLevel
|
|
||||||
// status.Save()
|
|
||||||
|
|
||||||
// update atomic securityLevel
|
|
||||||
// atomic.StoreInt32(securityLevel, int32(currentConfig.SecurityLevel))
|
|
||||||
}
|
|
||||||
|
|
||||||
func statusChangeListener() {
|
|
||||||
sub := database.NewSubscription()
|
|
||||||
sub.Subscribe(fmt.Sprintf("%s/SystemStatus:%s", database.Me.String(), systemStatusInstanceName))
|
|
||||||
for {
|
|
||||||
var receivedModel database.Model
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-configurationModule.Stop:
|
|
||||||
configurationModule.StopComplete()
|
|
||||||
return
|
|
||||||
case receivedModel = <-sub.Updated:
|
|
||||||
case receivedModel = <-sub.Created:
|
|
||||||
}
|
|
||||||
|
|
||||||
status, ok := database.SilentEnsureModel(receivedModel, systemStatusModel).(*SystemStatus)
|
|
||||||
if !ok {
|
|
||||||
log.Warning("configuration: received system status update, but was not of type *SystemStatus")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.StoreInt32(securityLevel, int32(status.CurrentSecurityLevel))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfiguration(t *testing.T) {
|
|
||||||
|
|
||||||
config1 := Get()
|
|
||||||
fmt.Printf("%v", config1)
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
config1.Changed()
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
config1.Save()
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
config1.Changed()
|
|
||||||
time.Sleep(1 * time.Millisecond)
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Safing/safing-core/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultConfig Configuration
|
|
||||||
)
|
|
||||||
|
|
||||||
func initDefaultConfig() {
|
|
||||||
defaultConfig = Configuration{
|
|
||||||
// based on security level
|
|
||||||
EnforceCT: 3,
|
|
||||||
EnforceRevocation: 3,
|
|
||||||
DenyInsecureTLS: 2,
|
|
||||||
DenyTLSWithoutSNI: 2,
|
|
||||||
DoNotUseAssignedDNS: 3,
|
|
||||||
DoNotUseMDNS: 2,
|
|
||||||
DoNotForwardSpecialDomains: 2,
|
|
||||||
AlwaysPromptAtNewProfile: 3,
|
|
||||||
DenyNetworkUntilProfileApproved: 3,
|
|
||||||
|
|
||||||
// generic configuration
|
|
||||||
CompetenceLevel: 0,
|
|
||||||
PermanentVerdicts: true,
|
|
||||||
// Possible values: DNS, DoH (DNS over HTTPS - using Google's syntax: https://developers.google.com/speed/public-dns/docs/dns-over-https)
|
|
||||||
// DNSServers: []string{"DoH|dns.google.com:443|df:www.google.com"},
|
|
||||||
DNSServers: []string{"DNS|1.1.1.1:53", "DNS|1.0.0.1:53", "DNS|[2606:4700:4700::1111]:53", "DNS|[2606:4700:4700::1001]:53", "DNS|8.8.8.8:53", "DNS|8.8.4.4:53", "DNS|[2001:4860:4860::8888]:53", "DNS|[2001:4860:4860::8844]:53", "DNS|208.67.222.222:53", "DNS|208.67.220.220:53"},
|
|
||||||
// DNSServers: []string{"DNS|[2001:4860:4860::8888]:53", "DNS|[2001:4860:4860::8844]:53"},
|
|
||||||
// DNSServers: []string{"DoH|dns.google.com:443|df:www.google.com", "DNS|8.8.8.8:53", "DNS|8.8.4.4:53", "DNS|172.30.30.1:53", "DNS|172.20.30.2:53"},
|
|
||||||
// DNSServers: []string{"DNS|208.67.222.222:53", "DNS|208.67.220.220:53", "DNS|8.8.8.8:53", "DNS|8.8.4.4:53"},
|
|
||||||
// Amount of seconds to wait until failing DNS Servers may be retried.
|
|
||||||
DNSServerRetryRate: 120,
|
|
||||||
// CountryBlacklist []string
|
|
||||||
// ASBlacklist []uint32
|
|
||||||
LocalPort17Node: false,
|
|
||||||
PublicPort17Node: true,
|
|
||||||
}
|
|
||||||
err := defaultConfig.Create(defaultConfigurationInstanceName)
|
|
||||||
if err != nil {
|
|
||||||
log.Warningf("configuration: could not save default configuration: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Interface struct {
|
|
||||||
*Configuration
|
|
||||||
|
|
||||||
LastChange int64
|
|
||||||
ConfigLock sync.RWMutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func Get() *Interface {
|
|
||||||
lock.RLock()
|
|
||||||
defer lock.RUnlock()
|
|
||||||
return &Interface{
|
|
||||||
Configuration: currentConfig,
|
|
||||||
LastChange: atomic.LoadInt64(lastChange),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *Interface) RLock() {
|
|
||||||
lc.ConfigLock.RLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *Interface) RUnlock() {
|
|
||||||
lc.ConfigLock.RUnlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *Interface) Changed() bool {
|
|
||||||
lastGlobalChange := atomic.LoadInt64(lastChange)
|
|
||||||
if lc.LastChange != lastGlobalChange {
|
|
||||||
lc.ConfigLock.Lock()
|
|
||||||
lock.RLock()
|
|
||||||
lc.Configuration = currentConfig
|
|
||||||
lc.LastChange = lastGlobalChange
|
|
||||||
lock.RUnlock()
|
|
||||||
lc.ConfigLock.Unlock()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *Interface) SecurityLevel() int8 {
|
|
||||||
return int8(atomic.LoadInt32(securityLevel))
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package configuration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Safing/safing-core/database"
|
|
||||||
|
|
||||||
datastore "github.com/ipfs/go-datastore"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SystemStatus saves basic information about the current system status.
|
|
||||||
type SystemStatus struct {
|
|
||||||
database.Base
|
|
||||||
CurrentSecurityLevel int8
|
|
||||||
SelectedSecurityLevel int8
|
|
||||||
|
|
||||||
ThreatLevel int8 `json:",omitempty" bson:",omitempty"`
|
|
||||||
ThreatReason string `json:",omitempty" bson:",omitempty"`
|
|
||||||
|
|
||||||
PortmasterStatus int8 `json:",omitempty" bson:",omitempty"`
|
|
||||||
PortmasterStatusMsg string `json:",omitempty" bson:",omitempty"`
|
|
||||||
|
|
||||||
Port17Status int8 `json:",omitempty" bson:",omitempty"`
|
|
||||||
Port17StatusMsg string `json:",omitempty" bson:",omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
systemStatusModel *SystemStatus // only use this as parameter for database.EnsureModel-like functions
|
|
||||||
systemStatusInstanceName = "status"
|
|
||||||
)
|
|
||||||
|
|
||||||
func initSystemStatusModel() {
|
|
||||||
database.RegisterModel(systemStatusModel, func() database.Model { return new(SystemStatus) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create saves SystemStatus with the provided name in the default namespace.
|
|
||||||
func (m *SystemStatus) Create() error {
|
|
||||||
return m.CreateObject(&database.Me, systemStatusInstanceName, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateInNamespace saves SystemStatus with the provided name in the provided namespace.
|
|
||||||
func (m *SystemStatus) CreateInNamespace(namespace *datastore.Key) error {
|
|
||||||
return m.CreateObject(namespace, systemStatusInstanceName, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save saves SystemStatus.
|
|
||||||
func (m *SystemStatus) Save() error {
|
|
||||||
return m.SaveObject(m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FmtSecurityLevel returns the current security level as a string.
|
|
||||||
func (m *SystemStatus) FmtSecurityLevel() string {
|
|
||||||
var s string
|
|
||||||
switch m.CurrentSecurityLevel {
|
|
||||||
case SecurityLevelOff:
|
|
||||||
s = "Off"
|
|
||||||
case SecurityLevelDynamic:
|
|
||||||
s = "Dynamic"
|
|
||||||
case SecurityLevelSecure:
|
|
||||||
s = "Secure"
|
|
||||||
case SecurityLevelFortress:
|
|
||||||
s = "Fortress"
|
|
||||||
}
|
|
||||||
if m.CurrentSecurityLevel != m.SelectedSecurityLevel {
|
|
||||||
s += "*"
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSystemStatus fetches SystemStatus with the provided name in the default namespace.
|
|
||||||
func GetSystemStatus() (*SystemStatus, error) {
|
|
||||||
return GetSystemStatusFromNamespace(&database.Me)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSystemStatusFromNamespace fetches SystemStatus with the provided name in the provided namespace.
|
|
||||||
func GetSystemStatusFromNamespace(namespace *datastore.Key) (*SystemStatus, error) {
|
|
||||||
object, err := database.GetAndEnsureModel(namespace, systemStatusInstanceName, systemStatusModel)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
model, ok := object.(*SystemStatus)
|
|
||||||
if !ok {
|
|
||||||
return nil, database.NewMismatchError(object, systemStatusModel)
|
|
||||||
}
|
|
||||||
return model, nil
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue