Streamline configuration

This commit is contained in:
Daniel 2020-04-01 17:13:30 +02:00
parent 77d7a63bc3
commit 279ab67c7e
12 changed files with 354 additions and 191 deletions

View file

@ -8,6 +8,7 @@ import (
)
var (
CfgDevModeKey = "core/devMode"
defaultDevMode bool
)
@ -24,7 +25,7 @@ func logFlagOverrides() {
func registerConfig() error {
err := config.Register(&config.Option{
Name: "Development Mode",
Key: "core/devMode",
Key: CfgDevModeKey,
Description: "In Development Mode security restrictions are lifted/softened to enable easier access to Portmaster for debugging and testing purposes.",
OptType: config.OptTypeBool,
ExpertiseLevel: config.ExpertiseLevelDeveloper,

View file

@ -2,14 +2,16 @@ package firewall
import (
"github.com/safing/portbase/config"
"github.com/safing/portmaster/status"
)
var (
permanentVerdicts config.BoolOption
filterDNSByScope status.SecurityLevelOption
filterDNSByProfile status.SecurityLevelOption
promptTimeout config.IntOption
CfgOptionEnableFilterKey = "filter/enable"
CfgOptionPermanentVerdictsKey = "filter/permanentVerdicts"
permanentVerdicts config.BoolOption
CfgOptionPromptTimeoutKey = "filter/promptTimeout"
promptTimeout config.IntOption
devMode config.BoolOption
apiListenAddress config.StringOption
@ -18,7 +20,7 @@ var (
func registerConfig() error {
err := config.Register(&config.Option{
Name: "Permanent Verdicts",
Key: "firewall/permanentVerdicts",
Key: CfgOptionPermanentVerdictsKey,
Description: "With permanent verdicts, control of a connection is fully handed back to the OS after the initial decision. This brings a great performance increase, but makes it impossible to change the decision of a link later on.",
OptType: config.OptTypeBool,
ExpertiseLevel: config.ExpertiseLevelExpert,
@ -28,43 +30,11 @@ func registerConfig() error {
if err != nil {
return err
}
permanentVerdicts = config.Concurrent.GetAsBool("firewall/permanentVerdicts", true)
err = config.Register(&config.Option{
Name: "Filter DNS Responses by Server Scope",
Key: "firewall/filterDNSByScope",
Description: "This option will filter out DNS answers that are outside of the scope of the server. A server on the public Internet may not respond with a private LAN address.",
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelExpert,
ReleaseLevel: config.ReleaseLevelBeta,
ExternalOptType: "security level",
DefaultValue: 7,
ValidationRegex: "^(7|6|4)$",
})
if err != nil {
return err
}
filterDNSByScope = status.ConfigIsActiveConcurrent("firewall/filterDNSByScope")
err = config.Register(&config.Option{
Name: "Filter DNS Responses by Application Profile",
Key: "firewall/filterDNSByProfile",
Description: "This option will filter out DNS answers that an application would not be allowed to connect, based on its profile.",
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelExpert,
ReleaseLevel: config.ReleaseLevelBeta,
ExternalOptType: "security level",
DefaultValue: 7,
ValidationRegex: "^(7|6|4)$",
})
if err != nil {
return err
}
filterDNSByProfile = status.ConfigIsActiveConcurrent("firewall/filterDNSByProfile")
permanentVerdicts = config.Concurrent.GetAsBool(CfgOptionPermanentVerdictsKey, true)
err = config.Register(&config.Option{
Name: "Timeout for prompt notifications",
Key: "firewall/promptTimeout",
Key: CfgOptionPromptTimeoutKey,
Description: "Amount of time how long Portmaster will wait for a response when prompting about a connection via a notification. In seconds.",
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelUser,
@ -74,9 +44,9 @@ func registerConfig() error {
if err != nil {
return err
}
promptTimeout = config.Concurrent.GetAsInt("firewall/promptTimeout", 30)
promptTimeout = config.Concurrent.GetAsInt(CfgOptionPromptTimeoutKey, 60)
devMode = config.Concurrent.GetAsBool("firewall/permanentVerdicts", false)
devMode = config.Concurrent.GetAsBool("core/devMode", false)
apiListenAddress = config.GetAsString("api/listenAddress", "")
return nil

View file

@ -19,7 +19,7 @@ var (
func registerConfigUpdater() error {
return module.RegisterEventHook(
"config",
"config changed",
"config change",
"update global config profile",
updateGlobalConfigProfile,
)

View file

@ -10,35 +10,41 @@ var (
cfgIntOptions = make(map[string]config.IntOption)
cfgBoolOptions = make(map[string]config.BoolOption)
cfgOptionDefaultActionKey = "filter/mode"
CfgOptionDefaultActionKey = "filter/defaultAction"
cfgOptionDefaultAction config.StringOption
cfgOptionDisableAutoPermitKey = "filter/disableAutoPermit"
CfgOptionDisableAutoPermitKey = "filter/disableAutoPermit"
cfgOptionDisableAutoPermit config.IntOption // security level option
cfgOptionEndpointsKey = "filter/endpoints"
CfgOptionEndpointsKey = "filter/endpoints"
cfgOptionEndpoints config.StringArrayOption
cfgOptionServiceEndpointsKey = "filter/serviceEndpoints"
CfgOptionServiceEndpointsKey = "filter/serviceEndpoints"
cfgOptionServiceEndpoints config.StringArrayOption
cfgOptionBlockScopeLocalKey = "filter/blockLocal"
CfgOptionBlockScopeLocalKey = "filter/blockLocal"
cfgOptionBlockScopeLocal config.IntOption // security level option
cfgOptionBlockScopeLANKey = "filter/blockLAN"
CfgOptionBlockScopeLANKey = "filter/blockLAN"
cfgOptionBlockScopeLAN config.IntOption // security level option
cfgOptionBlockScopeInternetKey = "filter/blockInternet"
CfgOptionBlockScopeInternetKey = "filter/blockInternet"
cfgOptionBlockScopeInternet config.IntOption // security level option
cfgOptionBlockP2PKey = "filter/blockP2P"
CfgOptionBlockP2PKey = "filter/blockP2P"
cfgOptionBlockP2P config.IntOption // security level option
cfgOptionBlockInboundKey = "filter/blockInbound"
CfgOptionBlockInboundKey = "filter/blockInbound"
cfgOptionBlockInbound config.IntOption // security level option
cfgOptionEnforceSPNKey = "filter/enforceSPN"
CfgOptionEnforceSPNKey = "filter/enforceSPN"
cfgOptionEnforceSPN config.IntOption // security level option
CfgOptionRemoveOutOfScopeDNSKey = "filter/removeOutOfScopeDNS"
cfgOptionRemoveOutOfScopeDNS config.IntOption // security level option
CfgOptionRemoveBlockedDNSKey = "filter/removeBlockedDNS"
cfgOptionRemoveBlockedDNS config.IntOption // security level option
)
func registerConfiguration() error {
@ -48,7 +54,7 @@ func registerConfiguration() error {
// block - whitelist mode: everything is blocked unless permitted
err := config.Register(&config.Option{
Name: "Default Filter Action",
Key: cfgOptionDefaultActionKey,
Key: CfgOptionDefaultActionKey,
Description: `The default filter action when nothing else permits or blocks a connection.`,
OptType: config.OptTypeString,
DefaultValue: "permit",
@ -57,13 +63,13 @@ func registerConfiguration() error {
if err != nil {
return err
}
cfgOptionDefaultAction = config.Concurrent.GetAsString(cfgOptionDefaultActionKey, "permit")
cfgStringOptions[cfgOptionDefaultActionKey] = cfgOptionDefaultAction
cfgOptionDefaultAction = config.Concurrent.GetAsString(CfgOptionDefaultActionKey, "permit")
cfgStringOptions[CfgOptionDefaultActionKey] = cfgOptionDefaultAction
// Disable Auto Permit
err = config.Register(&config.Option{
Name: "Disable Auto Permit",
Key: cfgOptionDisableAutoPermitKey,
Key: CfgOptionDisableAutoPermitKey,
Description: "Auto Permit searches for a relation between an app and the destionation of a connection - if there is a correlation, the connection will be permitted. This setting is negated in order to provide a streamlined user experience, where higher settings are better.",
OptType: config.OptTypeInt,
ExternalOptType: "security level",
@ -73,13 +79,13 @@ func registerConfiguration() error {
if err != nil {
return err
}
cfgOptionDisableAutoPermit = config.Concurrent.GetAsInt(cfgOptionDisableAutoPermitKey, 4)
cfgIntOptions[cfgOptionDisableAutoPermitKey] = cfgOptionDisableAutoPermit
cfgOptionDisableAutoPermit = config.Concurrent.GetAsInt(CfgOptionDisableAutoPermitKey, 4)
cfgIntOptions[CfgOptionDisableAutoPermitKey] = cfgOptionDisableAutoPermit
// Endpoint Filter List
err = config.Register(&config.Option{
Name: "Endpoint Filter List",
Key: cfgOptionEndpointsKey,
Key: CfgOptionEndpointsKey,
Description: "Filter outgoing connections by matching the destination endpoint. Network Scope restrictions still apply.",
Help: `Format:
Permission:
@ -101,36 +107,36 @@ Examples:
- .example.com
+ 192.168.0.1/24`,
OptType: config.OptTypeStringArray,
DefaultValue: nil,
DefaultValue: []string{},
ExternalOptType: "endpoint list",
ValidationRegex: `^(+|-) [A-z0-9\.:-*/]+( [A-z0-9/]+)?$`,
ValidationRegex: `^(\+|\-) [A-z0-9\.:\-*/]+( [A-z0-9/]+)?$`,
})
if err != nil {
return err
}
cfgOptionEndpoints = config.Concurrent.GetAsStringArray(cfgOptionEndpointsKey, nil)
cfgStringArrayOptions[cfgOptionEndpointsKey] = cfgOptionEndpoints
cfgOptionEndpoints = config.Concurrent.GetAsStringArray(CfgOptionEndpointsKey, []string{})
cfgStringArrayOptions[CfgOptionEndpointsKey] = cfgOptionEndpoints
// Service Endpoint Filter List
err = config.Register(&config.Option{
Name: "Service Endpoint Filter List",
Key: cfgOptionServiceEndpointsKey,
Key: CfgOptionServiceEndpointsKey,
Description: "Filter incoming connections by matching the source endpoint. Network Scope restrictions and the inbound permission still apply. Also not that the implicit default action of this list is to always block.",
OptType: config.OptTypeStringArray,
DefaultValue: nil,
DefaultValue: []string{},
ExternalOptType: "endpoint list",
ValidationRegex: `^(+|-) [A-z0-9\.:-*/]+( [A-z0-9/]+)?$`,
ValidationRegex: `^(\+|\-) [A-z0-9\.:\-*/]+( [A-z0-9/]+)?$`,
})
if err != nil {
return err
}
cfgOptionServiceEndpoints = config.Concurrent.GetAsStringArray(cfgOptionServiceEndpointsKey, nil)
cfgStringArrayOptions[cfgOptionServiceEndpointsKey] = cfgOptionServiceEndpoints
cfgOptionServiceEndpoints = config.Concurrent.GetAsStringArray(CfgOptionServiceEndpointsKey, []string{})
cfgStringArrayOptions[CfgOptionServiceEndpointsKey] = cfgOptionServiceEndpoints
// Block Scope Local
err = config.Register(&config.Option{
Name: "Block Scope Local",
Key: cfgOptionBlockScopeLocalKey,
Key: CfgOptionBlockScopeLocalKey,
Description: "Block connections to your own device, ie. localhost.",
OptType: config.OptTypeInt,
ExternalOptType: "security level",
@ -140,13 +146,13 @@ Examples:
if err != nil {
return err
}
cfgOptionBlockScopeLocal = config.Concurrent.GetAsInt(cfgOptionBlockScopeLocalKey, 0)
cfgIntOptions[cfgOptionBlockScopeLocalKey] = cfgOptionBlockScopeLocal
cfgOptionBlockScopeLocal = config.Concurrent.GetAsInt(CfgOptionBlockScopeLocalKey, 0)
cfgIntOptions[CfgOptionBlockScopeLocalKey] = cfgOptionBlockScopeLocal
// Block Scope LAN
err = config.Register(&config.Option{
Name: "Block Scope LAN",
Key: cfgOptionBlockScopeLANKey,
Key: CfgOptionBlockScopeLANKey,
Description: "Block connections to the Local Area Network.",
OptType: config.OptTypeInt,
ExternalOptType: "security level",
@ -156,13 +162,13 @@ Examples:
if err != nil {
return err
}
cfgOptionBlockScopeLAN = config.Concurrent.GetAsInt(cfgOptionBlockScopeLANKey, 0)
cfgIntOptions[cfgOptionBlockScopeLANKey] = cfgOptionBlockScopeLAN
cfgOptionBlockScopeLAN = config.Concurrent.GetAsInt(CfgOptionBlockScopeLANKey, 0)
cfgIntOptions[CfgOptionBlockScopeLANKey] = cfgOptionBlockScopeLAN
// Block Scope Internet
err = config.Register(&config.Option{
Name: "Block Scope Internet",
Key: cfgOptionBlockScopeInternetKey,
Key: CfgOptionBlockScopeInternetKey,
Description: "Block connections to the Internet.",
OptType: config.OptTypeInt,
ExternalOptType: "security level",
@ -172,13 +178,13 @@ Examples:
if err != nil {
return err
}
cfgOptionBlockScopeInternet = config.Concurrent.GetAsInt(cfgOptionBlockScopeInternetKey, 0)
cfgIntOptions[cfgOptionBlockScopeInternetKey] = cfgOptionBlockScopeInternet
cfgOptionBlockScopeInternet = config.Concurrent.GetAsInt(CfgOptionBlockScopeInternetKey, 0)
cfgIntOptions[CfgOptionBlockScopeInternetKey] = cfgOptionBlockScopeInternet
// Block Peer to Peer Connections
err = config.Register(&config.Option{
Name: "Block Peer to Peer Connections",
Key: cfgOptionBlockP2PKey,
Key: CfgOptionBlockP2PKey,
Description: "Block peer to peer connections. These are connections that are established directly to an IP address on the Internet without resolving a domain name via DNS first.",
OptType: config.OptTypeInt,
ExternalOptType: "security level",
@ -188,13 +194,13 @@ Examples:
if err != nil {
return err
}
cfgOptionBlockP2P = config.Concurrent.GetAsInt(cfgOptionBlockP2PKey, 7)
cfgIntOptions[cfgOptionBlockP2PKey] = cfgOptionBlockP2P
cfgOptionBlockP2P = config.Concurrent.GetAsInt(CfgOptionBlockP2PKey, 7)
cfgIntOptions[CfgOptionBlockP2PKey] = cfgOptionBlockP2P
// Block Inbound Connections
err = config.Register(&config.Option{
Name: "Block Inbound Connections",
Key: cfgOptionBlockInboundKey,
Key: CfgOptionBlockInboundKey,
Description: "Block inbound connections to your device. This will usually only be the case if you are running a network service or are using peer to peer software.",
OptType: config.OptTypeInt,
ExternalOptType: "security level",
@ -204,13 +210,13 @@ Examples:
if err != nil {
return err
}
cfgOptionBlockInbound = config.Concurrent.GetAsInt(cfgOptionBlockInboundKey, 6)
cfgIntOptions[cfgOptionBlockInboundKey] = cfgOptionBlockInbound
cfgOptionBlockInbound = config.Concurrent.GetAsInt(CfgOptionBlockInboundKey, 6)
cfgIntOptions[CfgOptionBlockInboundKey] = cfgOptionBlockInbound
// Enforce SPN
err = config.Register(&config.Option{
Name: "Enforce SPN",
Key: cfgOptionEnforceSPNKey,
Key: CfgOptionEnforceSPNKey,
Description: "This setting enforces connections to be routed over the SPN. If this is not possible for any reason, connections will be blocked.",
OptType: config.OptTypeInt,
ReleaseLevel: config.ReleaseLevelExperimental,
@ -221,8 +227,44 @@ Examples:
if err != nil {
return err
}
cfgOptionEnforceSPN = config.Concurrent.GetAsInt(cfgOptionEnforceSPNKey, 0)
cfgIntOptions[cfgOptionEnforceSPNKey] = cfgOptionEnforceSPN
cfgOptionEnforceSPN = config.Concurrent.GetAsInt(CfgOptionEnforceSPNKey, 0)
cfgIntOptions[CfgOptionEnforceSPNKey] = cfgOptionEnforceSPN
// Filter Out-of-Scope DNS Records
err = config.Register(&config.Option{
Name: "Filter Out-of-Scope DNS Records",
Key: CfgOptionRemoveOutOfScopeDNSKey,
Description: "Filter DNS answers that are outside of the scope of the server. A server on the public Internet may not respond with a private LAN address.",
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelExpert,
ReleaseLevel: config.ReleaseLevelBeta,
ExternalOptType: "security level",
DefaultValue: 7,
ValidationRegex: "^(7|6|4)$",
})
if err != nil {
return err
}
cfgOptionRemoveOutOfScopeDNS = config.Concurrent.GetAsInt(CfgOptionRemoveOutOfScopeDNSKey, 7)
cfgIntOptions[CfgOptionRemoveOutOfScopeDNSKey] = cfgOptionEnforceSPN
// Filter DNS Records that would be blocked
err = config.Register(&config.Option{
Name: "Filter DNS Records that would be blocked",
Key: CfgOptionRemoveBlockedDNSKey,
Description: "Pre-filter DNS answers that an application would not be allowed to connect to.",
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelExpert,
ReleaseLevel: config.ReleaseLevelBeta,
ExternalOptType: "security level",
DefaultValue: 7,
ValidationRegex: "^(7|6|4)$",
})
if err != nil {
return err
}
cfgOptionRemoveBlockedDNS = config.Concurrent.GetAsInt(CfgOptionRemoveBlockedDNSKey, 7)
cfgIntOptions[CfgOptionRemoveBlockedDNSKey] = cfgOptionEnforceSPN
return nil
}

54
profile/find.go Normal file
View file

@ -0,0 +1,54 @@
package profile
import (
"github.com/safing/portbase/database/query"
"github.com/safing/portbase/log"
)
func FindOrCreateLocalProfileByPath(fullPath string) (profile *Profile, new bool, err error) {
// find local profile
it, err := profileDB.Query(
query.New(makeProfileKey(SourceLocal, "")).Where(
query.Where("LinkedPath", query.SameAs, fullPath),
),
)
if err != nil {
return nil, false, err
}
// get first result
r := <-it.Next
// cancel immediately
it.Cancel()
// return new if none was found
if r == nil {
profile = New()
profile.LinkedPath = fullPath
return profile, true, nil
}
// ensure its a profile
profile, err = EnsureProfile(r)
if err != nil {
return nil, false, err
}
// prepare config
err = profile.prepConfig()
if err != nil {
log.Warningf("profiles: profile %s has (partly) invalid configuration: %s", profile.ID, err)
}
// parse config
err = profile.parseConfig()
if err != nil {
log.Warningf("profiles: profile %s has (partly) invalid configuration: %s", profile.ID, err)
}
// mark active
markProfileActive(profile)
// return parsed profile
return profile, false, nil
}

View file

@ -1,6 +1,8 @@
package profile
import (
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
// module dependencies
@ -40,5 +42,10 @@ func start() error {
return err
}
err = updateGlobalConfigProfile(module.Ctx, nil)
if err != nil {
log.Warningf("profile: error during loading global profile from configuration: %s", err)
}
return nil
}

View file

@ -23,8 +23,9 @@ var (
type LayeredProfile struct {
lock sync.Mutex
localProfile *Profile
layers []*Profile
localProfile *Profile
layers []*Profile
revisionCounter uint64
validityFlag *abool.AtomicBool
validityFlagLock sync.Mutex
@ -32,13 +33,15 @@ type LayeredProfile struct {
securityLevel *uint32
DisableAutoPermit config.BoolOption
BlockScopeLocal config.BoolOption
BlockScopeLAN config.BoolOption
BlockScopeInternet config.BoolOption
BlockP2P config.BoolOption
BlockInbound config.BoolOption
EnforceSPN config.BoolOption
DisableAutoPermit config.BoolOption
BlockScopeLocal config.BoolOption
BlockScopeLAN config.BoolOption
BlockScopeInternet config.BoolOption
BlockP2P config.BoolOption
BlockInbound config.BoolOption
EnforceSPN config.BoolOption
RemoveOutOfScopeDNS config.BoolOption
RemoveBlockedDNS config.BoolOption
}
// NewLayeredProfile returns a new layered profile based on the given local profile.
@ -48,39 +51,48 @@ func NewLayeredProfile(localProfile *Profile) *LayeredProfile {
new := &LayeredProfile{
localProfile: localProfile,
layers: make([]*Profile, 0, len(localProfile.LinkedProfiles)+1),
revisionCounter: 0,
validityFlag: abool.NewBool(true),
globalValidityFlag: config.NewValidityFlag(),
securityLevel: &securityLevelVal,
}
new.DisableAutoPermit = new.wrapSecurityLevelOption(
cfgOptionDisableAutoPermitKey,
CfgOptionDisableAutoPermitKey,
cfgOptionDisableAutoPermit,
)
new.BlockScopeLocal = new.wrapSecurityLevelOption(
cfgOptionBlockScopeLocalKey,
CfgOptionBlockScopeLocalKey,
cfgOptionBlockScopeLocal,
)
new.BlockScopeLAN = new.wrapSecurityLevelOption(
cfgOptionBlockScopeLANKey,
CfgOptionBlockScopeLANKey,
cfgOptionBlockScopeLAN,
)
new.BlockScopeInternet = new.wrapSecurityLevelOption(
cfgOptionBlockScopeInternetKey,
CfgOptionBlockScopeInternetKey,
cfgOptionBlockScopeInternet,
)
new.BlockP2P = new.wrapSecurityLevelOption(
cfgOptionBlockP2PKey,
CfgOptionBlockP2PKey,
cfgOptionBlockP2P,
)
new.BlockInbound = new.wrapSecurityLevelOption(
cfgOptionBlockInboundKey,
CfgOptionBlockInboundKey,
cfgOptionBlockInbound,
)
new.EnforceSPN = new.wrapSecurityLevelOption(
cfgOptionEnforceSPNKey,
CfgOptionEnforceSPNKey,
cfgOptionEnforceSPN,
)
new.RemoveOutOfScopeDNS = new.wrapSecurityLevelOption(
CfgOptionRemoveOutOfScopeDNSKey,
cfgOptionRemoveOutOfScopeDNS,
)
new.RemoveBlockedDNS = new.wrapSecurityLevelOption(
CfgOptionRemoveBlockedDNSKey,
cfgOptionRemoveBlockedDNS,
)
// TODO: load referenced profiles
@ -100,9 +112,9 @@ func (lp *LayeredProfile) getValidityFlag() *abool.AtomicBool {
}
// Update checks for updated profiles and replaces any outdated profiles.
func (lp *LayeredProfile) Update() {
func (lp *LayeredProfile) Update() (revisionCounter uint64) {
lp.lock.Lock()
defer lp.lock.Lock()
defer lp.lock.Unlock()
var changed bool
for i, layer := range lp.layers {
@ -130,8 +142,14 @@ func (lp *LayeredProfile) Update() {
// get global config validity flag
lp.globalValidityFlag.Refresh()
// update cached data fields
lp.updateCaches()
// bump revision counter
lp.revisionCounter++
}
return lp.revisionCounter
}
func (lp *LayeredProfile) updateCaches() {
@ -156,12 +174,14 @@ func (lp *LayeredProfile) SecurityLevel() uint8 {
func (lp *LayeredProfile) DefaultAction() uint8 {
for _, layer := range lp.layers {
if layer.defaultAction > 0 {
log.Tracef("profile: default action by layer = %d", layer.defaultAction)
return layer.defaultAction
}
}
cfgLock.RLock()
defer cfgLock.RUnlock()
log.Tracef("profile: default action from global = %d", cfgDefaultAction)
return cfgDefaultAction
}
@ -199,38 +219,15 @@ func (lp *LayeredProfile) MatchServiceEndpoint(entity *intel.Entity) (result end
return cfgServiceEndpoints.Match(entity)
}
/*
func (lp *LayeredProfile) wrapSecurityLevelOption(configKey string, globalConfig config.IntOption) config.BoolOption {
valid := no
var activeAtLevels uint8
return func() bool {
if !valid.IsSet() {
valid = lp.getValidityFlag()
found := false
layerLoop:
for _, layer := range lp.layers {
layerLevel, ok := layer.configPerspective.GetAsInt(configKey)
if ok {
found = true
// TODO: add instead?
activeAtLevels = uint8(layerLevel)
break layerLoop
}
}
if !found {
activeAtLevels = uint8(globalConfig())
}
}
return activeAtLevels&max(
lp.SecurityLevel(), // layered profile security level
status.ActiveSecurityLevel(), // global security level
) > 0
}
// AddEndpoint adds an endpoint to the local endpoint list, saves the local profile and reloads the configuration.
func (lp *LayeredProfile) AddEndpoint(newEntry string) {
lp.localProfile.AddEndpoint(newEntry)
}
// AddServiceEndpoint adds a service endpoint to the local endpoint list, saves the local profile and reloads the configuration.
func (lp *LayeredProfile) AddServiceEndpoint(newEntry string) {
lp.localProfile.AddServiceEndpoint(newEntry)
}
*/
func (lp *LayeredProfile) wrapSecurityLevelOption(configKey string, globalConfig config.IntOption) config.BoolOption {
activeAtLevels := lp.wrapIntOption(configKey, globalConfig)

View file

@ -90,18 +90,13 @@ type Profile struct { //nolint:maligned // not worth the effort
}
func (profile *Profile) prepConfig() (err error) {
profile.Lock()
defer profile.Unlock()
// prepare configuration
profile.configPerspective, err = config.NewPerspective(profile.Config)
profile.oudated = abool.New()
return
}
func (profile *Profile) parseConfig() error {
profile.Lock()
defer profile.Unlock()
if profile.configPerspective == nil {
return errors.New("config not prepared")
}
@ -115,7 +110,7 @@ func (profile *Profile) parseConfig() error {
var err error
var lastErr error
action, ok := profile.configPerspective.GetAsString(cfgOptionDefaultActionKey)
action, ok := profile.configPerspective.GetAsString(CfgOptionBlockInboundKey)
if ok {
switch action {
case "permit":
@ -129,7 +124,7 @@ func (profile *Profile) parseConfig() error {
}
}
list, ok := profile.configPerspective.GetAsStringArray(cfgOptionEndpointsKey)
list, ok := profile.configPerspective.GetAsStringArray(CfgOptionEndpointsKey)
if ok {
profile.endpoints, err = endpoints.ParseEndpoints(list)
if err != nil {
@ -137,7 +132,7 @@ func (profile *Profile) parseConfig() error {
}
}
list, ok = profile.configPerspective.GetAsStringArray(cfgOptionServiceEndpointsKey)
list, ok = profile.configPerspective.GetAsStringArray(CfgOptionServiceEndpointsKey)
if ok {
profile.serviceEndpoints, err = endpoints.ParseEndpoints(list)
if err != nil {
@ -150,11 +145,18 @@ func (profile *Profile) parseConfig() error {
// New returns a new Profile.
func New() *Profile {
return &Profile{
profile := &Profile{
ID: uuid.NewV4().String(),
Source: SourceLocal,
Created: time.Now().Unix(),
Config: make(map[string]interface{}),
}
// create placeholders
_ = profile.prepConfig()
_ = profile.parseConfig()
return profile
}
// ScopedID returns the scoped ID (Source + ID) of the profile.
@ -192,6 +194,41 @@ func (profile *Profile) String() string {
return profile.Name
}
// AddEndpoint adds an endpoint to the endpoint list, saves the profile and reloads the configuration.
func (profile *Profile) AddEndpoint(newEntry string) {
profile.addEndpointyEntry(CfgOptionEndpointsKey, newEntry)
}
// AddServiceEndpoint adds a service endpoint to the endpoint list, saves the profile and reloads the configuration.
func (profile *Profile) AddServiceEndpoint(newEntry string) {
profile.addEndpointyEntry(CfgOptionServiceEndpointsKey, newEntry)
}
func (profile *Profile) addEndpointyEntry(cfgKey, newEntry string) {
profile.Lock()
// get, update, save endpoints list
endpointList, ok := profile.configPerspective.GetAsStringArray(cfgKey)
if !ok {
endpointList = make([]string, 0, 1)
}
endpointList = append(endpointList, newEntry)
profile.Config[cfgKey] = endpointList
// save without full reload
profile.internalSave = true
profile.Unlock()
err := profile.Save()
if err != nil {
log.Warningf("profile: failed to save profile after adding endpoint: %s", err)
}
// reload manually
err = profile.parseConfig()
if err != nil {
log.Warningf("profile: failed to parse profile config after adding endpoint: %s", err)
}
}
// GetProfile loads a profile from the database.
func GetProfile(source, id string) (*Profile, error) {
return GetProfileByScopedID(makeScopedID(source, id))
@ -217,6 +254,10 @@ func GetProfileByScopedID(scopedID string) (*Profile, error) {
return nil, err
}
// lock for prepping
profile.Lock()
defer profile.Unlock()
// prepare config
err = profile.prepConfig()
if err != nil {

View file

@ -9,35 +9,83 @@ import (
)
var (
configuredNameServers config.StringArrayOption
defaultNameServers = []string{
defaultNameServers = []string{
// Collection of default DNS Servers
// Default servers should be:
// Anycast:
// - Servers should be reachable from anywhere with reasonable latency.
// - Servers should be near to the user for geo-content to work correctly.
// Private:
// - Servers should not do any or only minimal logging.
// - Available logging data may not be used against the user, ie. unethically.
// Sadly, only a few services come close to fulfilling these requirements.
// For now, we have settled for two bigger and well known services: Cloudflare and Quad9.
// TODO: monitor situation and re-evaluate when new services become available
// TODO: explore other methods of making queries more private
// We encourage everyone who has the technical abilities to set their own preferred servers.
// Default 1: Cloudflare
"dot|1.1.1.1:853|cloudflare-dns.com", // Cloudflare
"dot|1.0.0.1:853|cloudflare-dns.com", // Cloudflare
// Default 2: Quad9
"dot|9.9.9.9:853|dns.quad9.net", // Quad9
"dot|149.112.112.112:853|dns.quad9.net", // Quad9
// Fallback 1: Cloudflare
"dns|1.1.1.1:53", // Cloudflare
"dns|1.0.0.1:53", // Cloudflare
// Fallback 2: Quad9
"dns|9.9.9.9:53", // Quad9
"dns|149.112.112.112:53", // Quad9
// Configuration:
// protocol: dns, dot
// : IP + Port
// parameters
// - `name=name`: human readable name for resolver
// - `verify=domain`: verify domain (dot only)
// - `blockedif=baredns`: how to detect if the dns service blocked something
// - `baredns`: NXDomain result, but without any other record in any section
// Possible future format:
// "dot://9.9.9.9:853?verify=dns.quad9.net&", // Quad9
// "dot|149.112.112.112:853|dns.quad9.net", // Quad9
// "dot://[2620:fe::fe]:853?verify=dns.quad9.net&name=Quad9" // Quad9
// "dot://[2620:fe::9]:853?verify=dns.quad9.net&name=Quad9" // Quad9
"dot|1.1.1.1:853|cloudflare-dns.com", // Cloudflare
"dot|1.0.0.1:853|cloudflare-dns.com", // Cloudflare
"dns|9.9.9.9:53", // Quad9
"dns|149.112.112.112:53", // Quad9
"dns|1.1.1.1:53", // Cloudflare
"dns|1.0.0.1:53", // Cloudflare
// "doh|cloudflare-dns.com/dns-query", // DoH still experimental
}
nameserverRetryRate config.IntOption
doNotUseMulticastDNS status.SecurityLevelOption
doNotUseAssignedNameservers status.SecurityLevelOption
doNotUseInsecureProtocols status.SecurityLevelOption
doNotResolveSpecialDomains status.SecurityLevelOption
doNotResolveTestDomains status.SecurityLevelOption
CfgOptionNameServersKey = "dns/nameservers"
configuredNameServers config.StringArrayOption
CfgOptionNameserverRetryRateKey = "dns/nameserverRetryRate"
nameserverRetryRate config.IntOption
CfgOptionNoMulticastDNSKey = "dns/noMulticastDNS"
noMulticastDNS status.SecurityLevelOption
CfgOptionNoAssignedNameserversKey = "dns/noAssignedNameservers"
noAssignedNameservers status.SecurityLevelOption
CfgOptionNoInsecureProtocolsKey = "dns/noInsecureProtocols"
noInsecureProtocols status.SecurityLevelOption
CfgOptionDontResolveSpecialDomainsKey = "dns/dontResolveSpecialDomains"
dontResolveSpecialDomains status.SecurityLevelOption
CfgOptionDontResolveTestDomainsKey = "dns/dontResolveTestDomains"
dontResolveTestDomains status.SecurityLevelOption
)
func prepConfig() error {
err := config.Register(&config.Option{
Name: "Nameservers (DNS)",
Key: "intel/nameservers",
Description: "Nameserver to use for resolving DNS requests.",
Name: "DNS Servers",
Key: CfgOptionNameServersKey,
Description: "DNS Servers to use for resolving DNS requests.",
OptType: config.OptTypeStringArray,
ExpertiseLevel: config.ExpertiseLevelExpert,
ReleaseLevel: config.ReleaseLevelStable,
@ -47,12 +95,12 @@ func prepConfig() error {
if err != nil {
return err
}
configuredNameServers = config.Concurrent.GetAsStringArray("intel/nameservers", defaultNameServers)
configuredNameServers = config.Concurrent.GetAsStringArray(CfgOptionNameServersKey, defaultNameServers)
err = config.Register(&config.Option{
Name: "Nameserver Retry Rate",
Key: "intel/nameserverRetryRate",
Description: "Rate at which to retry failed nameservers, in seconds.",
Name: "DNS Server Retry Rate",
Key: CfgOptionNameserverRetryRateKey,
Description: "Rate at which to retry failed DNS Servers, in seconds.",
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelExpert,
ReleaseLevel: config.ReleaseLevelStable,
@ -61,11 +109,11 @@ func prepConfig() error {
if err != nil {
return err
}
nameserverRetryRate = config.Concurrent.GetAsInt("intel/nameserverRetryRate", 0)
nameserverRetryRate = config.Concurrent.GetAsInt(CfgOptionNameserverRetryRateKey, 600)
err = config.Register(&config.Option{
Name: "Do not use Multicast DNS",
Key: "intel/doNotUseMulticastDNS",
Key: CfgOptionNoMulticastDNSKey,
Description: "Multicast DNS queries other devices in the local network",
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelExpert,
@ -77,11 +125,11 @@ func prepConfig() error {
if err != nil {
return err
}
doNotUseMulticastDNS = status.ConfigIsActiveConcurrent("intel/doNotUseMulticastDNS")
noMulticastDNS = status.ConfigIsActiveConcurrent(CfgOptionNoMulticastDNSKey)
err = config.Register(&config.Option{
Name: "Do not use assigned Nameservers",
Key: "intel/doNotUseAssignedNameservers",
Key: CfgOptionNoAssignedNameserversKey,
Description: "that were acquired by the network (dhcp) or system",
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelExpert,
@ -93,27 +141,27 @@ func prepConfig() error {
if err != nil {
return err
}
doNotUseAssignedNameservers = status.ConfigIsActiveConcurrent("intel/doNotUseAssignedNameservers")
noAssignedNameservers = status.ConfigIsActiveConcurrent(CfgOptionNoAssignedNameserversKey)
err = config.Register(&config.Option{
Name: "Do not resolve insecurely",
Key: "intel/doNotUseInsecureProtocols",
Key: CfgOptionNoInsecureProtocolsKey,
Description: "Do not resolve domains with insecure protocols, ie. plain DNS",
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelExpert,
ReleaseLevel: config.ReleaseLevelStable,
ExternalOptType: "security level",
DefaultValue: 4,
DefaultValue: 6,
ValidationRegex: "^(7|6|4)$",
})
if err != nil {
return err
}
doNotUseInsecureProtocols = status.ConfigIsActiveConcurrent("intel/doNotUseInsecureProtocols")
noInsecureProtocols = status.ConfigIsActiveConcurrent(CfgOptionNoInsecureProtocolsKey)
err = config.Register(&config.Option{
Name: "Do not resolve special domains",
Key: "intel/doNotResolveSpecialDomains",
Key: CfgOptionDontResolveSpecialDomainsKey,
Description: fmt.Sprintf("Do not resolve the special top level domains %s", formatScopeList(specialServiceScopes)),
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelExpert,
@ -125,11 +173,11 @@ func prepConfig() error {
if err != nil {
return err
}
doNotResolveSpecialDomains = status.ConfigIsActiveConcurrent("intel/doNotResolveSpecialDomains")
dontResolveSpecialDomains = status.ConfigIsActiveConcurrent(CfgOptionDontResolveSpecialDomainsKey)
err = config.Register(&config.Option{
Name: "Do not resolve test domains",
Key: "intel/doNotResolveTestDomains",
Key: CfgOptionDontResolveTestDomainsKey,
Description: fmt.Sprintf("Do not resolve the special testing top level domains %s", formatScopeList(localTestScopes)),
OptType: config.OptTypeInt,
ExpertiseLevel: config.ExpertiseLevelExpert,
@ -141,7 +189,7 @@ func prepConfig() error {
if err != nil {
return err
}
doNotResolveTestDomains = status.ConfigIsActiveConcurrent("intel/doNotResolveTestDomains")
dontResolveTestDomains = status.ConfigIsActiveConcurrent(CfgOptionDontResolveTestDomainsKey)
return nil
}

View file

@ -158,13 +158,14 @@ func deduplicateRequest(ctx context.Context, q *Query) (finishRequest func()) {
dupKey := fmt.Sprintf("%s%s", q.FQDN, q.QType.String())
dupReqLock.Lock()
defer dupReqLock.Unlock()
// get duplicate request waitgroup
wg, requestActive := dupReqMap[dupKey]
// someone else is already on it!
if requestActive {
dupReqLock.Unlock()
// log that we are waiting
log.Tracer(ctx).Tracef("intel: waiting for duplicate query for %s to complete", dupKey)
// wait
@ -182,6 +183,8 @@ func deduplicateRequest(ctx context.Context, q *Query) (finishRequest func()) {
// add to registry
dupReqMap[dupKey] = wg
dupReqLock.Unlock()
// return function to mark request as finished
return func() {
dupReqLock.Lock()
@ -222,7 +225,7 @@ resolveLoop:
rrCache, err = resolver.Conn.Query(ctx, q)
if err != nil {
// FIXME: check if we are online?
// TODO: check if we are online?
switch {
case errors.Is(err, ErrNotFound):

View file

@ -114,7 +114,7 @@ func domainInScope(dotPrefixedFQDN string, scopeList []string) bool {
}
// GetResolversInScope returns all resolvers that are in scope the resolve the given query and options.
func GetResolversInScope(ctx context.Context, q *Query) (selected []*Resolver) {
func GetResolversInScope(ctx context.Context, q *Query) (selected []*Resolver) { //nolint:gocognit // TODO
resolversLock.RLock()
defer resolversLock.RUnlock()
@ -226,13 +226,13 @@ func (q *Query) checkCompliance() error {
}
// special TLDs
if doNotResolveSpecialDomains(q.SecurityLevel) &&
if dontResolveSpecialDomains(q.SecurityLevel) &&
domainInScope(q.dotPrefixedFQDN, specialServiceScopes) {
return ErrSpecialDomainsDisabled
}
// testing TLDs
if doNotResolveTestDomains(q.SecurityLevel) &&
if dontResolveTestDomains(q.SecurityLevel) &&
domainInScope(q.dotPrefixedFQDN, localTestScopes) {
return ErrTestDomainsDisabled
}
@ -245,7 +245,7 @@ func (resolver *Resolver) checkCompliance(_ context.Context, q *Query) error {
return errSkip
}
if doNotUseInsecureProtocols(q.SecurityLevel) {
if noInsecureProtocols(q.SecurityLevel) {
switch resolver.ServerType {
case ServerTypeDNS:
return errInsecureProtocol
@ -260,13 +260,13 @@ func (resolver *Resolver) checkCompliance(_ context.Context, q *Query) error {
}
}
if doNotUseAssignedNameservers(q.SecurityLevel) {
if noAssignedNameservers(q.SecurityLevel) {
if resolver.Source == ServerSourceAssigned {
return errAssignedServer
}
}
if doNotUseMulticastDNS(q.SecurityLevel) {
if noMulticastDNS(q.SecurityLevel) {
if resolver.Source == ServerSourceMDNS {
return errMulticastDNS
}

View file

@ -52,7 +52,7 @@ func (resolver *Resolver) String() string {
}
// ResolverConn is an interface to implement different types of query backends.
type ResolverConn interface {
type ResolverConn interface { //nolint:go-lint // TODO
Query(ctx context.Context, q *Query) (*RRCache, error)
MarkFailed()
LastFail() time.Time