mirror of
https://github.com/safing/portmaster
synced 2025-04-24 04:49:10 +00:00
Finish initial adaption of profiles
This commit is contained in:
parent
247c7dca00
commit
5533203fa1
26 changed files with 1299 additions and 814 deletions
9
profile/const.go
Normal file
9
profile/const.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package profile
|
||||
|
||||
// Platform identifiers
|
||||
const (
|
||||
PlatformAny = "any"
|
||||
PlatformLinux = "lin"
|
||||
PlatformWindows = "win"
|
||||
PlatformMac = "mac"
|
||||
)
|
6
profile/const_linux.go
Normal file
6
profile/const_linux.go
Normal file
|
@ -0,0 +1,6 @@
|
|||
package profile
|
||||
|
||||
// OS Identifier Prefix
|
||||
const (
|
||||
IdentifierPrefix = "lin:"
|
||||
)
|
21
profile/database.go
Normal file
21
profile/database.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"github.com/Safing/portbase/database"
|
||||
)
|
||||
|
||||
// core:profiles/user/12345-1234-125-1234-1235
|
||||
// core:profiles/special/default
|
||||
// /global
|
||||
// core:profiles/stamp/12334-1235-1234-5123-1234
|
||||
// core:profiles/identifier/base64
|
||||
|
||||
// Namespaces
|
||||
const (
|
||||
UserNamespace = "user"
|
||||
StampNamespace = "stamp"
|
||||
)
|
||||
|
||||
var (
|
||||
profileDB = database.NewInterface(nil)
|
||||
)
|
42
profile/domains.go
Normal file
42
profile/domains.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package profile
|
||||
|
||||
import "strings"
|
||||
|
||||
// Domains is a list of permitted or denied domains.
|
||||
type Domains map[string]*DomainDecision
|
||||
|
||||
// DomainDecision holds a decision about a domain.
|
||||
type DomainDecision struct {
|
||||
Permit bool
|
||||
Created int64
|
||||
IncludeSubdomains bool
|
||||
}
|
||||
|
||||
// IsSet returns whether the Domains object is "set".
|
||||
func (d Domains) IsSet() bool {
|
||||
if d != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckStatus checks if the given domain is governed in the list of domains and returns whether it is permitted.
|
||||
func (d Domains) CheckStatus(domain string) (permit, ok bool) {
|
||||
// check for exact domain
|
||||
dd, ok := d[domain]
|
||||
if ok {
|
||||
return dd.Permit, true
|
||||
}
|
||||
|
||||
// check if domain is a subdomain of any of the domains
|
||||
for key, dd := range d {
|
||||
if dd.IncludeSubdomains && strings.HasSuffix(domain, key) {
|
||||
preDottedKey := "." + key
|
||||
if strings.HasSuffix(domain, preDottedKey) {
|
||||
return dd.Permit, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, false
|
||||
}
|
145
profile/flags.go
Normal file
145
profile/flags.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/portmaster/status"
|
||||
)
|
||||
|
||||
// ProfileFlags are used to quickly add common attributes to profiles
|
||||
type ProfileFlags map[uint8]uint8
|
||||
|
||||
// Profile Flags
|
||||
const (
|
||||
// Profile Modes
|
||||
Prompt uint8 = 0 // Prompt first-seen connections
|
||||
Blacklist uint8 = 1 // Allow everything not explicitly denied
|
||||
Whitelist uint8 = 2 // Only allow everything explicitly allowed
|
||||
|
||||
// Network Locations
|
||||
Internet uint8 = 16 // Allow connections to the Internet
|
||||
LAN uint8 = 17 // Allow connections to the local area network
|
||||
Localhost uint8 = 18 // Allow connections on the local host
|
||||
|
||||
// Specials
|
||||
Related uint8 = 32 // If and before prompting, allow domains that are related to the program
|
||||
PeerToPeer uint8 = 33 // Allow program to directly communicate with peers, without resolving DNS first
|
||||
Service uint8 = 34 // Allow program to accept incoming connections
|
||||
Independent uint8 = 35 // Ignore profile settings coming from the Community
|
||||
RequireGate17 uint8 = 36 // Require all connections to go over Gate17
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrProfileFlagsParseFailed is returned if a an invalid flag is encountered while parsing
|
||||
ErrProfileFlagsParseFailed = errors.New("profiles: failed to parse flags")
|
||||
|
||||
sortedFlags = []uint8{
|
||||
Prompt,
|
||||
Blacklist,
|
||||
Whitelist,
|
||||
Internet,
|
||||
LAN,
|
||||
Localhost,
|
||||
Related,
|
||||
PeerToPeer,
|
||||
Service,
|
||||
Independent,
|
||||
RequireGate17,
|
||||
}
|
||||
|
||||
flagIDs = map[string]uint8{
|
||||
"Prompt": Prompt,
|
||||
"Blacklist": Blacklist,
|
||||
"Whitelist": Whitelist,
|
||||
"Internet": Internet,
|
||||
"LAN": LAN,
|
||||
"Localhost": Localhost,
|
||||
"Related": Related,
|
||||
"PeerToPeer": PeerToPeer,
|
||||
"Service": Service,
|
||||
"Independent": Independent,
|
||||
"RequireGate17": RequireGate17,
|
||||
}
|
||||
|
||||
flagNames = map[uint8]string{
|
||||
Prompt: "Prompt",
|
||||
Blacklist: "Blacklist",
|
||||
Whitelist: "Whitelist",
|
||||
Internet: "Internet",
|
||||
LAN: "LAN",
|
||||
Localhost: "Localhost",
|
||||
Related: "Related",
|
||||
PeerToPeer: "PeerToPeer",
|
||||
Service: "Service",
|
||||
Independent: "Independent",
|
||||
RequireGate17: "RequireGate17",
|
||||
}
|
||||
)
|
||||
|
||||
// FlagsFromNames creates ProfileFlags from a comma seperated list of flagnames (e.g. "System,Strict,Secure")
|
||||
// func FlagsFromNames(words []string) (*ProfileFlags, error) {
|
||||
// var flags ProfileFlags
|
||||
// for _, entry := range words {
|
||||
// flag, ok := flagIDs[entry]
|
||||
// if !ok {
|
||||
// return nil, ErrProfileFlagsParseFailed
|
||||
// }
|
||||
// flags = append(flags, flag)
|
||||
// }
|
||||
// return &flags, nil
|
||||
// }
|
||||
|
||||
// IsSet returns whether the ProfileFlags object is "set".
|
||||
func (pf ProfileFlags) IsSet() bool {
|
||||
if pf != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Has checks if a ProfileFlags object has a flag set in the given security level
|
||||
func (pf ProfileFlags) Has(flag, level uint8) bool {
|
||||
setting, ok := pf[flag]
|
||||
if ok && setting&level > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getLevelMarker(levels, level uint8) string {
|
||||
if levels&level > 0 {
|
||||
return "+"
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
|
||||
// String return a string representation of ProfileFlags
|
||||
func (pf ProfileFlags) String() string {
|
||||
var namedFlags []string
|
||||
for _, flag := range sortedFlags {
|
||||
levels, ok := pf[flag]
|
||||
if ok {
|
||||
s := flagNames[flag]
|
||||
if levels != status.SecurityLevelsAll {
|
||||
s += getLevelMarker(levels, status.SecurityLevelDynamic)
|
||||
s += getLevelMarker(levels, status.SecurityLevelSecure)
|
||||
s += getLevelMarker(levels, status.SecurityLevelFortress)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, flag := range pf {
|
||||
namedFlags = append(namedFlags, flagNames[flag])
|
||||
}
|
||||
return strings.Join(namedFlags, ", ")
|
||||
}
|
||||
|
||||
// Add adds a flag to the Flags with the given level.
|
||||
func (pf ProfileFlags) Add(flag, levels uint8) {
|
||||
pf[flag] = levels
|
||||
}
|
||||
|
||||
// Remove removes a flag from the Flags.
|
||||
func (pf ProfileFlags) Remove(flag uint8) {
|
||||
delete(pf, flag)
|
||||
}
|
54
profile/flags_test.go
Normal file
54
profile/flags_test.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProfileFlags(t *testing.T) {
|
||||
|
||||
// check if all IDs have a name
|
||||
for key, entry := range flagIDs {
|
||||
if _, ok := flagNames[entry]; !ok {
|
||||
t.Errorf("could not find entry for %s in flagNames", key)
|
||||
}
|
||||
}
|
||||
|
||||
// check if all names have an ID
|
||||
for key, entry := range flagNames {
|
||||
if _, ok := flagIDs[entry]; !ok {
|
||||
t.Errorf("could not find entry for %d in flagNames", key)
|
||||
}
|
||||
}
|
||||
|
||||
// // check Has
|
||||
// emptyFlags := ProfileFlags{}
|
||||
// for flag, name := range flagNames {
|
||||
// if !sortedFlags.Has(flag) {
|
||||
// t.Errorf("sortedFlags should have flag %s (%d)", name, flag)
|
||||
// }
|
||||
// if emptyFlags.Has(flag) {
|
||||
// t.Errorf("emptyFlags should not have flag %s (%d)", name, flag)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // check ProfileFlags creation from strings
|
||||
// var allFlagStrings []string
|
||||
// for _, flag := range *sortedFlags {
|
||||
// allFlagStrings = append(allFlagStrings, flagNames[flag])
|
||||
// }
|
||||
// newFlags, err := FlagsFromNames(allFlagStrings)
|
||||
// if err != nil {
|
||||
// t.Errorf("error while parsing flags: %s", err)
|
||||
// }
|
||||
// if newFlags.String() != sortedFlags.String() {
|
||||
// t.Errorf("parsed flags are not correct (or tests have not been updated to reflect the right number), expected %v, got %v", *sortedFlags, *newFlags)
|
||||
// }
|
||||
//
|
||||
// // check ProfileFlags Stringer
|
||||
// flagString := newFlags.String()
|
||||
// check := strings.Join(allFlagStrings, ",")
|
||||
// if flagString != check {
|
||||
// t.Errorf("flag string is not correct, expected %s, got %s", check, flagString)
|
||||
// }
|
||||
|
||||
}
|
76
profile/framework.go
Normal file
76
profile/framework.go
Normal file
|
@ -0,0 +1,76 @@
|
|||
package profile
|
||||
|
||||
// DEACTIVATED
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "os"
|
||||
// "path/filepath"
|
||||
// "regexp"
|
||||
// "strings"
|
||||
//
|
||||
// "github.com/Safing/portbase/log"
|
||||
// )
|
||||
//
|
||||
// type Framework struct {
|
||||
// // go hirarchy up
|
||||
// FindParent uint8 `json:",omitempty bson:",omitempty"`
|
||||
// // get path from parent, amount of levels to go up the tree (1 means parent, 2 means parent of parents, and so on)
|
||||
// MergeWithParent bool `json:",omitempty bson:",omitempty"`
|
||||
// // instead of getting the path of the parent, merge with it by presenting connections as if they were from that parent
|
||||
//
|
||||
// // go hirarchy down
|
||||
// Find string `json:",omitempty bson:",omitempty"`
|
||||
// // Regular expression for finding path elements
|
||||
// Build string `json:",omitempty bson:",omitempty"`
|
||||
// // Path definitions for building path
|
||||
// Virtual bool `json:",omitempty bson:",omitempty"`
|
||||
// // Treat resulting path as virtual, do not check if valid
|
||||
// }
|
||||
//
|
||||
// func (f *Framework) GetNewPath(command string, cwd string) (string, error) {
|
||||
// // "/usr/bin/python script"
|
||||
// // to
|
||||
// // "/path/to/script"
|
||||
// regex, err := regexp.Compile(f.Find)
|
||||
// if err != nil {
|
||||
// return "", fmt.Errorf("profiles(framework): failed to compile framework regex: %s", err)
|
||||
// }
|
||||
// matched := regex.FindAllStringSubmatch(command, -1)
|
||||
// if len(matched) == 0 || len(matched[0]) < 2 {
|
||||
// return "", fmt.Errorf("profiles(framework): regex \"%s\" for constructing path did not match command \"%s\"", f.Find, command)
|
||||
// }
|
||||
//
|
||||
// var lastError error
|
||||
// var buildPath string
|
||||
// for _, buildPath = range strings.Split(f.Build, "|") {
|
||||
//
|
||||
// buildPath = strings.Replace(buildPath, "{CWD}", cwd, -1)
|
||||
// for i := 1; i < len(matched[0]); i++ {
|
||||
// buildPath = strings.Replace(buildPath, fmt.Sprintf("{%d}", i), matched[0][i], -1)
|
||||
// }
|
||||
//
|
||||
// buildPath = filepath.Clean(buildPath)
|
||||
//
|
||||
// if !f.Virtual {
|
||||
// if !strings.HasPrefix(buildPath, "~/") && !filepath.IsAbs(buildPath) {
|
||||
// lastError = fmt.Errorf("constructed path \"%s\" from framework is not absolute", buildPath)
|
||||
// continue
|
||||
// }
|
||||
// if _, err := os.Stat(buildPath); os.IsNotExist(err) {
|
||||
// lastError = fmt.Errorf("constructed path \"%s\" does not exist", buildPath)
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// lastError = nil
|
||||
// break
|
||||
//
|
||||
// }
|
||||
//
|
||||
// if lastError != nil {
|
||||
// return "", fmt.Errorf("profiles(framework): failed to construct valid path, last error: %s", lastError)
|
||||
// }
|
||||
// log.Tracef("profiles(framework): transformed \"%s\" (%s) to \"%s\"", command, cwd, buildPath)
|
||||
// return buildPath, nil
|
||||
// }
|
30
profile/framework_test.go
Normal file
30
profile/framework_test.go
Normal file
|
@ -0,0 +1,30 @@
|
|||
package profile
|
||||
|
||||
// DEACTIVATED
|
||||
|
||||
// import (
|
||||
// "testing"
|
||||
// )
|
||||
//
|
||||
// func testGetNewPath(t *testing.T, f *Framework, command, cwd, expect string) {
|
||||
// newPath, err := f.GetNewPath(command, cwd)
|
||||
// if err != nil {
|
||||
// t.Errorf("GetNewPath failed: %s", err)
|
||||
// }
|
||||
// if newPath != expect {
|
||||
// t.Errorf("GetNewPath return unexpected result: got %s, expected %s", newPath, expect)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func TestFramework(t *testing.T) {
|
||||
// f1 := &Framework{
|
||||
// Find: "([^ ]+)$",
|
||||
// Build: "{CWD}/{1}",
|
||||
// }
|
||||
// testGetNewPath(t, f1, "/usr/bin/python bash", "/bin", "/bin/bash")
|
||||
// f2 := &Framework{
|
||||
// Find: "([^ ]+)$",
|
||||
// Build: "{1}|{CWD}/{1}",
|
||||
// }
|
||||
// testGetNewPath(t, f2, "/usr/bin/python /bin/bash", "/tmp", "/bin/bash")
|
||||
// }
|
101
profile/index/index.go
Normal file
101
profile/index/index.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"fmt"
|
||||
"errors"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/Safing/portbase/database/record"
|
||||
"github.com/Safing/portbase/utils"
|
||||
)
|
||||
|
||||
// ProfileIndex links an Identifier to Profiles
|
||||
type ProfileIndex struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
ID string
|
||||
UserProfiles []string
|
||||
StampProfiles []string
|
||||
}
|
||||
|
||||
func makeIndexRecordKey(id string) string {
|
||||
return fmt.Sprintf("core:profiles/index/%s", base64.RawURLEncoding.EncodeToString([]byte(id)))
|
||||
}
|
||||
|
||||
// NewIndex returns a new ProfileIndex.
|
||||
func NewIndex(id string) *ProfileIndex {
|
||||
return &ProfileIndex{
|
||||
ID: id,
|
||||
}
|
||||
}
|
||||
|
||||
// AddUserProfile adds a User Profile to the index.
|
||||
func (pi *ProfileIndex) AddUserProfile(id string) (changed bool) {
|
||||
if !utils.StringInSlice(pi.UserProfiles, id) {
|
||||
pi.UserProfiles = append(pi.UserProfiles, id)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// AddStampProfile adds a Stamp Profile to the index.
|
||||
func (pi *ProfileIndex) AddStampProfile(id string) (changed bool) {
|
||||
if !utils.StringInSlice(pi.StampProfiles, id) {
|
||||
pi.StampProfiles = append(pi.StampProfiles, id)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveUserProfile removes a profile from the index.
|
||||
func (pi *ProfileIndex) RemoveUserProfile(id string) {
|
||||
pi.UserProfiles = utils.RemoveFromStringSlice(pi.UserProfiles, id)
|
||||
}
|
||||
|
||||
// RemoveStampProfile removes a profile from the index.
|
||||
func (pi *ProfileIndex) RemoveStampProfile(id string) {
|
||||
pi.StampProfiles = utils.RemoveFromStringSlice(pi.StampProfiles, id)
|
||||
}
|
||||
|
||||
// GetIndex gets a ProfileIndex from the database.
|
||||
func GetIndex(id string) (*ProfileIndex, error) {
|
||||
key := makeIndexRecordKey(id)
|
||||
|
||||
r, err := indexDB.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// unwrap
|
||||
if r.IsWrapped() {
|
||||
// only allocate a new struct, if we need it
|
||||
new := &ProfileIndex{}
|
||||
err = record.Unwrap(r, new)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new, nil
|
||||
}
|
||||
|
||||
// or adjust type
|
||||
new, ok := r.(*ProfileIndex)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("record not of type *ProfileIndex, but %T", r)
|
||||
}
|
||||
return new, nil
|
||||
}
|
||||
|
||||
// Save saves the Identifiers to the database
|
||||
func (pi *ProfileIndex) Save() error {
|
||||
if pi.Key() == "" {
|
||||
if pi.ID != "" {
|
||||
pi.SetKey(makeIndexRecordKey(pi.ID))
|
||||
} else {
|
||||
return errors.New("missing identification Key")
|
||||
}
|
||||
}
|
||||
|
||||
return indexDB.Put(pi)
|
||||
}
|
101
profile/index/indexer.go
Normal file
101
profile/index/indexer.go
Normal file
|
@ -0,0 +1,101 @@
|
|||
package index
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/portbase/database"
|
||||
"github.com/Safing/portbase/database/query"
|
||||
"github.com/Safing/portbase/database/record"
|
||||
"github.com/Safing/portbase/log"
|
||||
"github.com/Safing/portbase/modules"
|
||||
|
||||
"github.com/Safing/portmaster/profile"
|
||||
)
|
||||
|
||||
// FIXME: listen for profile changes and update the index
|
||||
|
||||
var (
|
||||
indexDB = database.NewInterface(&database.Options{
|
||||
Local: true, // we want to access crownjewel records
|
||||
AlwaysMakeCrownjewel: true, // never sync the index
|
||||
})
|
||||
indexSub *database.Subscription
|
||||
|
||||
shutdownIndexer = make(chan struct{})
|
||||
)
|
||||
|
||||
func init() {
|
||||
modules.Register("profile:index", nil, start, stop, "database")
|
||||
}
|
||||
|
||||
func start() (err error) {
|
||||
indexSub, err = indexDB.Subscribe(query.New("core:profiles/user/"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func stop() error {
|
||||
close(shutdownIndexer)
|
||||
indexSub.Cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func indexer() {
|
||||
for {
|
||||
select {
|
||||
case <-shutdownIndexer:
|
||||
return
|
||||
case r := <-indexSub.Feed:
|
||||
prof := ensureProfile(r)
|
||||
if prof != nil {
|
||||
for _, id := range prof.Identifiers {
|
||||
if strings.HasPrefix(id, profile.IdentifierPrefix) {
|
||||
|
||||
// get Profile and ensure identifier is set
|
||||
pi, err := GetIndex(id)
|
||||
if err != nil {
|
||||
if err == database.ErrNotFound {
|
||||
pi = NewIndex(id)
|
||||
} else {
|
||||
log.Errorf("profile/index: could not save updated profile index: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if pi.AddUserProfile(prof.ID) {
|
||||
err := pi.Save()
|
||||
if err != nil {
|
||||
log.Errorf("profile/index: could not save updated profile index: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ensureProfile(r record.Record) *profile.Profile {
|
||||
// unwrap
|
||||
if r.IsWrapped() {
|
||||
// only allocate a new struct, if we need it
|
||||
new := &profile.Profile{}
|
||||
err := record.Unwrap(r, new)
|
||||
if err != nil {
|
||||
log.Errorf("profile/index: could not unwrap Profile: %s", err)
|
||||
return nil
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
// or adjust type
|
||||
new, ok := r.(*profile.Profile)
|
||||
if !ok {
|
||||
log.Errorf("profile/index: record not of type *Profile, but %T", r)
|
||||
return nil
|
||||
}
|
||||
return new
|
||||
}
|
17
profile/matcher/database.go
Normal file
17
profile/matcher/database.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package matcher
|
||||
|
||||
import (
|
||||
"github.com/Safing/portbase/database"
|
||||
)
|
||||
|
||||
// core:profiles/user/12345-1234-125-1234-1235
|
||||
// core:profiles/special/default
|
||||
// /global
|
||||
// core:profiles/stamp/12334-1235-1234-5123-1234
|
||||
// core:profiles/identifier/base64
|
||||
|
||||
var (
|
||||
profileDB = database.NewInterface(&database.Options{
|
||||
Local: true, // we want to access crownjewel records (indexes are)
|
||||
})
|
||||
)
|
23
profile/matcher/fingerprints.go
Normal file
23
profile/matcher/fingerprints.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package matcher
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/portmaster/process"
|
||||
"github.com/Safing/portmaster/profile"
|
||||
)
|
||||
|
||||
// CheckFingerprints checks what fingerprints match and returns the total score.
|
||||
func CheckFingerprints(proc *process.Process, prof *profile.Profile) (score int, err error) {
|
||||
// FIXME: kinda a dummy for now
|
||||
|
||||
for _, fp := range prof.Fingerprints {
|
||||
if strings.HasPrefix(fp, "fullpath:") {
|
||||
if fp[9:] == proc.Path {
|
||||
return 3, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
18
profile/matcher/identpath_linux.go
Normal file
18
profile/matcher/identpath_linux.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package matcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/portmaster/process"
|
||||
"github.com/Safing/portmaster/profile"
|
||||
)
|
||||
|
||||
// GetIdentificationPath returns the identifier for the given process (linux edition).
|
||||
func GetIdentificationPath(p *process.Process) string {
|
||||
splittedPath := strings.Split(p.Path, "/")
|
||||
if len(splittedPath) > 3 {
|
||||
return fmt.Sprintf("%s%s", profile.IdentifierPrefix, strings.Join(splittedPath[len(splittedPath)-3:len(splittedPath)], "/"))
|
||||
}
|
||||
return fmt.Sprintf("%s%s", profile.IdentifierPrefix, p.Path)
|
||||
}
|
25
profile/matcher/identpath_linux_test.go
Normal file
25
profile/matcher/identpath_linux_test.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package matcher
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Safing/portmaster/process"
|
||||
)
|
||||
|
||||
func TestGetIdentifierLinux(t *testing.T) {
|
||||
p := &process.Process{
|
||||
Path: "/usr/lib/firefox/firefox",
|
||||
}
|
||||
|
||||
if GetIdentificationPath(p) != "lin:lib/firefox/firefox" {
|
||||
t.Fatal("mismatch!")
|
||||
}
|
||||
|
||||
p = &process.Process{
|
||||
Path: "/opt/start",
|
||||
}
|
||||
|
||||
if GetIdentificationPath(p) != "lin:/opt/start" {
|
||||
t.Fatal("mismatch!")
|
||||
}
|
||||
}
|
61
profile/matcher/matcher.go
Normal file
61
profile/matcher/matcher.go
Normal file
|
@ -0,0 +1,61 @@
|
|||
package matcher
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/portbase/log"
|
||||
"github.com/Safing/portmaster/process"
|
||||
"github.com/Safing/portmaster/profile"
|
||||
"github.com/Safing/portmaster/profile/index"
|
||||
)
|
||||
|
||||
// GetProfileSet finds a local profile.
|
||||
func GetProfileSet(proc *process.Process) (set *profile.ProfileSet, err error) {
|
||||
|
||||
identPath := GetIdentificationPath(proc)
|
||||
pi, err := index.GetIndex(identPath)
|
||||
|
||||
var bestScore int
|
||||
var bestProfile *profile.Profile
|
||||
|
||||
for _, id := range pi.UserProfiles {
|
||||
prof, err := profile.GetUserProfile(id)
|
||||
if err != nil {
|
||||
log.Errorf("profile/matcher: failed to load profile: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
score, err := CheckFingerprints(proc, prof)
|
||||
if score > bestScore {
|
||||
bestScore = score
|
||||
bestProfile = prof
|
||||
}
|
||||
}
|
||||
|
||||
if bestProfile == nil {
|
||||
bestProfile = ProfileFromProcess(proc)
|
||||
}
|
||||
|
||||
// FIXME: fetch stamp profile
|
||||
set = profile.NewSet(bestProfile, nil)
|
||||
return set, nil
|
||||
}
|
||||
|
||||
// ProfileFromProcess creates an initial profile based on the given process.
|
||||
func ProfileFromProcess(proc *process.Process) *profile.Profile {
|
||||
new := profile.New()
|
||||
|
||||
splittedPath := strings.Split(proc.Path, "/")
|
||||
new.Name = strings.ToTitle(splittedPath[len(splittedPath)-1])
|
||||
|
||||
new.Identifiers = append(new.Identifiers, GetIdentificationPath(proc))
|
||||
new.Fingerprints = append(new.Fingerprints, fmt.Sprintf("fullpath:%s", proc.Path))
|
||||
|
||||
err := new.Save(profile.UserNamespace)
|
||||
if err != nil {
|
||||
log.Errorf("profile/matcher: could not save new profile: %s", new.Name)
|
||||
}
|
||||
|
||||
return new
|
||||
}
|
89
profile/ports.go
Normal file
89
profile/ports.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Ports is a list of permitted or denied ports
|
||||
type Ports map[string][]*Port
|
||||
|
||||
// IsSet returns whether the Ports object is "set".
|
||||
func (p Ports) IsSet() bool {
|
||||
if p != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckStatus returns whether listening/connecting to a certain port is allowed, and if this option is even set.
|
||||
func (p Ports) CheckStatus(listen bool, protocol string, port uint16) (permit, ok bool) {
|
||||
if p == nil {
|
||||
return false, false
|
||||
}
|
||||
if listen {
|
||||
protocol = "<" + protocol
|
||||
}
|
||||
portDefinitions, ok := p[protocol]
|
||||
if ok {
|
||||
for _, portD := range portDefinitions {
|
||||
if portD.Matches(port) {
|
||||
return portD.Permit, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, true
|
||||
}
|
||||
|
||||
func (p Ports) String() string {
|
||||
var s []string
|
||||
|
||||
for protocol, ports := range p {
|
||||
var portStrings []string
|
||||
for _, port := range ports {
|
||||
portStrings = append(portStrings, port.String())
|
||||
}
|
||||
|
||||
s = append(s, fmt.Sprintf("%s:[%s]", protocol, strings.Join(portStrings, ", ")))
|
||||
}
|
||||
|
||||
if len(s) == 0 {
|
||||
return "None"
|
||||
}
|
||||
return strings.Join(s, ", ")
|
||||
}
|
||||
|
||||
// Port represents a port range and a verdict.
|
||||
type Port struct {
|
||||
Permit bool
|
||||
Created int64
|
||||
Start uint16
|
||||
End uint16
|
||||
}
|
||||
|
||||
// Matches checks whether a port object matches the given port.
|
||||
func (p Port) Matches(port uint16) bool {
|
||||
if port >= p.Start && port <= p.End {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p Port) String() string {
|
||||
var s string
|
||||
|
||||
if p.Permit {
|
||||
s += "permit:"
|
||||
} else {
|
||||
s += "deny:"
|
||||
}
|
||||
|
||||
if p.Start == p.End {
|
||||
s += strconv.Itoa(int(p.Start))
|
||||
} else {
|
||||
s += fmt.Sprintf("%d-%d", p.Start, p.End)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
87
profile/profile.go
Normal file
87
profile/profile.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package profile
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/satori/go.uuid"
|
||||
|
||||
"github.com/Safing/portbase/database/record"
|
||||
"github.com/Safing/portmaster/status"
|
||||
)
|
||||
|
||||
// Profile is used to predefine a security profile for applications.
|
||||
type Profile struct {
|
||||
record.Base
|
||||
sync.Mutex
|
||||
|
||||
// Profile Metadata
|
||||
ID string
|
||||
Name string
|
||||
Description string
|
||||
Homepage string
|
||||
// Icon is a path to the icon and is either prefixed "f:" for filepath, "d:" for a database path or "e:" for the encoded data.
|
||||
Icon string
|
||||
|
||||
// Identification
|
||||
Identifiers []string
|
||||
Fingerprints []string
|
||||
|
||||
// The mininum security level to apply to connections made with this profile
|
||||
SecurityLevel uint8
|
||||
Flags ProfileFlags
|
||||
Domains Domains
|
||||
Ports Ports
|
||||
|
||||
StampProfileKey string
|
||||
|
||||
// If a Profile is declared as a Framework (i.e. an Interpreter and the likes), then the real process must be found
|
||||
// Framework *Framework `json:",omitempty bson:",omitempty"`
|
||||
|
||||
// When this Profile was approximately last used (for performance reasons not every single usage is saved)
|
||||
ApproxLastUsed int64
|
||||
}
|
||||
|
||||
func New() *Profile {
|
||||
return &Profile{}
|
||||
}
|
||||
|
||||
// Save saves the profile to the database
|
||||
func (profile *Profile) Save(namespace string) error {
|
||||
if profile.ID == "" {
|
||||
u, err := uuid.NewV4()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
profile.ID = u.String()
|
||||
}
|
||||
|
||||
if profile.Key() == "" {
|
||||
if namespace == "" {
|
||||
return fmt.Errorf("no key or namespace defined for profile %s", profile.String())
|
||||
}
|
||||
profile.SetKey(fmt.Sprintf("config:profiles/%s/%s", namespace, profile.ID))
|
||||
}
|
||||
|
||||
return profileDB.Put(profile)
|
||||
}
|
||||
|
||||
// String returns a string representation of the Profile.
|
||||
func (profile *Profile) String() string {
|
||||
return profile.Name
|
||||
}
|
||||
|
||||
// DetailedString returns a more detailed string representation of theProfile.
|
||||
func (profile *Profile) DetailedString() string {
|
||||
return fmt.Sprintf("%s(SL=%s Flags=[%s] Ports=[%s] #Domains=%d)", profile.Name, status.FmtSecurityLevel(profile.SecurityLevel), profile.Flags.String(), profile.Ports.String(), len(profile.Domains))
|
||||
}
|
||||
|
||||
// GetUserProfile loads a profile from the database.
|
||||
func GetUserProfile(ID string) (*Profile, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetStampProfile loads a profile from the database.
|
||||
func GetStampProfile(ID string) (*Profile, error) {
|
||||
return nil, nil
|
||||
}
|
121
profile/profileset.go
Normal file
121
profile/profileset.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package profile
|
||||
|
||||
var (
|
||||
emptyFlags = ProfileFlags{}
|
||||
emptyPorts = Ports{}
|
||||
)
|
||||
|
||||
// ProfileSet handles Profile chaining.
|
||||
type ProfileSet struct {
|
||||
Profiles [4]*Profile
|
||||
// Application
|
||||
// Global
|
||||
// Stamp
|
||||
// Default
|
||||
|
||||
Independent bool
|
||||
}
|
||||
|
||||
// NewSet returns a new profile set with given the profiles.
|
||||
func NewSet(user, stamp *Profile) *ProfileSet {
|
||||
new := &ProfileSet{
|
||||
Profiles: [4]*Profile{
|
||||
user, // Application
|
||||
nil, // Global
|
||||
stamp, // Stamp
|
||||
nil, // Default
|
||||
},
|
||||
}
|
||||
new.Update()
|
||||
return new
|
||||
}
|
||||
|
||||
// Update gets the new global and default profile and updates the independence status. It must be called when reusing a profile set for a series of calls.
|
||||
func (ps *ProfileSet) Update() {
|
||||
specialProfileLock.RLock()
|
||||
defer specialProfileLock.RUnlock()
|
||||
|
||||
// update profiles
|
||||
ps.Profiles[1] = globalProfile
|
||||
ps.Profiles[3] = defaultProfile
|
||||
|
||||
// update independence
|
||||
if ps.Flags().Has(Independent, ps.SecurityLevel()) {
|
||||
// Stamp profiles do not have the Independent flag
|
||||
ps.Independent = true
|
||||
} else {
|
||||
ps.Independent = false
|
||||
}
|
||||
}
|
||||
|
||||
// Flags returns the highest prioritized ProfileFlags configuration.
|
||||
func (ps *ProfileSet) Flags() ProfileFlags {
|
||||
|
||||
for _, profile := range ps.Profiles {
|
||||
if profile != nil {
|
||||
if profile.Flags.IsSet() {
|
||||
return profile.Flags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return emptyFlags
|
||||
}
|
||||
|
||||
// CheckDomainStatus checks if the given domain is governed in any the lists of domains and returns whether it is permitted.
|
||||
func (ps *ProfileSet) CheckDomainStatus(domain string) (permit, ok bool) {
|
||||
|
||||
for i, profile := range ps.Profiles {
|
||||
if i == 2 && ps.Independent {
|
||||
continue
|
||||
}
|
||||
|
||||
if profile != nil {
|
||||
if profile.Domains.IsSet() {
|
||||
permit, ok = profile.Domains.CheckStatus(domain)
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, false
|
||||
}
|
||||
|
||||
// Ports returns the highest prioritized Ports configuration.
|
||||
func (ps *ProfileSet) Ports() Ports {
|
||||
|
||||
for i, profile := range ps.Profiles {
|
||||
if i == 2 && ps.Independent {
|
||||
continue
|
||||
}
|
||||
|
||||
if profile != nil {
|
||||
if profile.Ports.IsSet() {
|
||||
return profile.Ports
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return emptyPorts
|
||||
}
|
||||
|
||||
// SecurityLevel returns the highest prioritized security level.
|
||||
func (ps *ProfileSet) SecurityLevel() uint8 {
|
||||
|
||||
for i, profile := range ps.Profiles {
|
||||
if i == 2 {
|
||||
// Stamp profiles do not have the SecurityLevel setting
|
||||
continue
|
||||
}
|
||||
|
||||
if profile != nil {
|
||||
if profile.SecurityLevel > 0 {
|
||||
return profile.SecurityLevel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
261
profile/sampledata.go
Normal file
261
profile/sampledata.go
Normal file
|
@ -0,0 +1,261 @@
|
|||
package profile
|
||||
|
||||
// DEACTIVATED
|
||||
|
||||
// import (
|
||||
// "runtime"
|
||||
//
|
||||
// "github.com/Safing/portbase/database"
|
||||
// "github.com/Safing/portbase/log"
|
||||
// )
|
||||
//
|
||||
// func init() {
|
||||
//
|
||||
// // Data here is for demo purposes, Profiles will be served over network soon™.
|
||||
//
|
||||
// log.Tracef("profiles: loading sample profiles for %s", runtime.GOOS)
|
||||
//
|
||||
// switch runtime.GOOS {
|
||||
// case "linux":
|
||||
//
|
||||
// log.Trace("profiles: loading linux sample profiles")
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Chromium",
|
||||
// Description: "Browser by Google",
|
||||
// Path: "/usr/lib/chromium-browser/chromium-browser",
|
||||
// Flags: []int8{User, Internet, LocalNet, Browser},
|
||||
// ConnectPorts: []uint16{80, 443},
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Evolution",
|
||||
// Description: "PIM solution by GNOME",
|
||||
// Path: "/usr/bin/evolution",
|
||||
// Flags: []int8{User, Internet, Gateway},
|
||||
// ConnectPorts: []uint16{25, 80, 143, 443, 465, 587, 993, 995},
|
||||
// SecurityLevel: 2,
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Evolution Calendar",
|
||||
// Description: "PIM solution by GNOME - Calendar",
|
||||
// Path: "/usr/lib/evolution/evolution-calendar-factory-subprocess",
|
||||
// Flags: []int8{User, Internet, Gateway},
|
||||
// ConnectPorts: []uint16{80, 443},
|
||||
// SecurityLevel: 2,
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Spotify",
|
||||
// Description: "Music streaming",
|
||||
// Path: "/usr/share/spotify/spotify",
|
||||
// ConnectPorts: []uint16{80, 443, 4070},
|
||||
// Flags: []int8{User, Internet, Strict},
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// (&Profile{
|
||||
// // flatpak edition
|
||||
// Name: "Spotify",
|
||||
// Description: "Music streaming",
|
||||
// Path: "/newroot/app/extra/share/spotify/spotify",
|
||||
// ConnectPorts: []uint16{80, 443, 4070},
|
||||
// Flags: []int8{User, Internet, Strict},
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Evince",
|
||||
// Description: "PDF Document Reader",
|
||||
// Path: "/usr/bin/evince",
|
||||
// Flags: []int8{},
|
||||
// SecurityLevel: 2,
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Ahavi",
|
||||
// Description: "mDNS service",
|
||||
// Path: "/usr/bin/avahi-daemon",
|
||||
// Flags: []int8{System, LocalNet, Service, Directconnect},
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Python 2.7 Framework",
|
||||
// Description: "Correctly handle python scripts",
|
||||
// Path: "/usr/bin/python2.7",
|
||||
// Framework: &Framework{
|
||||
// Find: "^[^ ]+ ([^ ]+)",
|
||||
// Build: "{1}|{CWD}/{1}",
|
||||
// },
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Python 3.5 Framework",
|
||||
// Description: "Correctly handle python scripts",
|
||||
// Path: "/usr/bin/python3.5",
|
||||
// Framework: &Framework{
|
||||
// Find: "^[^ ]+ ([^ ]+)",
|
||||
// Build: "{1}|{CWD}/{1}",
|
||||
// },
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "DHCP Client",
|
||||
// Description: "Client software for the DHCP protocol",
|
||||
// Path: "/sbin/dhclient",
|
||||
// Framework: &Framework{
|
||||
// FindParent: 1,
|
||||
// MergeWithParent: true,
|
||||
// },
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// // Default Profiles
|
||||
// // Until Profiles are distributed over the network, default profiles are activated when the Default Profile for "/" is missing.
|
||||
//
|
||||
// if ok, err := database.Has(ds.NewKey("/Data/Profiles/Profile_d-2f")); !ok || err != nil {
|
||||
//
|
||||
// log.Trace("profiles: loading linux default sample profiles")
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Default Base",
|
||||
// Description: "Default Profile for /",
|
||||
// Path: "/",
|
||||
// Flags: []int8{Internet, LocalNet, Strict},
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Installed Applications",
|
||||
// Description: "Default Profile for /usr/bin",
|
||||
// Path: "/usr/bin/",
|
||||
// Flags: []int8{Internet, LocalNet, Gateway},
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "System Binaries (/sbin)",
|
||||
// Description: "Default Profile for ~/Downloads",
|
||||
// Path: "/sbin/",
|
||||
// Flags: []int8{Internet, LocalNet, Directconnect, Service, System},
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "System Binaries (/usr/sbin)",
|
||||
// Description: "Default Profile for ~/Downloads",
|
||||
// Path: "/usr/sbin/",
|
||||
// Flags: []int8{Internet, LocalNet, Directconnect, Service, System},
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "System Tmp folder",
|
||||
// Description: "Default Profile for /tmp",
|
||||
// Path: "/tmp/",
|
||||
// Flags: []int8{}, // deny all
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "User Home",
|
||||
// Description: "Default Profile for ~/",
|
||||
// Path: "~/",
|
||||
// Flags: []int8{Internet, LocalNet, Gateway},
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "User Downloads",
|
||||
// Description: "Default Profile for ~/Downloads",
|
||||
// Path: "~/Downloads/",
|
||||
// Flags: []int8{}, // deny all
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "User Cache",
|
||||
// Description: "Default Profile for ~/.cache",
|
||||
// Path: "~/.cache/",
|
||||
// Flags: []int8{}, // deny all
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// }
|
||||
//
|
||||
// case "windows":
|
||||
//
|
||||
// log.Trace("profiles: loading windows sample profiles")
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Firefox",
|
||||
// Description: "Firefox Browser by Mozilla",
|
||||
// Path: "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
|
||||
// Flags: []int8{User, Internet, LocalNet, Browser},
|
||||
// ConnectPorts: []uint16{80, 443},
|
||||
// }).CreateInDist()
|
||||
//
|
||||
// // Default Profiles
|
||||
// // Until Profiles are distributed over the network, default profiles are activated when the Default Profile for "C" is missing.
|
||||
//
|
||||
// if ok, err := database.Has(ds.NewKey("/Data/Profiles/Profile:d-C")); !ok || err != nil {
|
||||
//
|
||||
// log.Trace("profiles: loading windows default sample profiles")
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Default Base",
|
||||
// Description: "Default Profile for C",
|
||||
// Path: "C",
|
||||
// Flags: []int8{Internet, LocalNet, Strict},
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Installed Applications",
|
||||
// Description: "Default Profile for C:\\Program Files",
|
||||
// Path: "C:\\Program Files\\",
|
||||
// Flags: []int8{Internet, LocalNet, Gateway},
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "Installed Applications (x86)",
|
||||
// Description: "Default Profile for C:\\Program Files (x86)",
|
||||
// Path: "C:\\Program Files (x86)\\",
|
||||
// Flags: []int8{Internet, LocalNet, Gateway},
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "System Applications (C:\\Windows\\System32)",
|
||||
// Description: "Default Profile for C:\\Windows\\System32",
|
||||
// Path: "C:\\Windows\\System32\\",
|
||||
// Flags: []int8{Internet, LocalNet, Directconnect, Service, System},
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "User Home",
|
||||
// Description: "Default Profile for ~/",
|
||||
// Path: "~/",
|
||||
// Flags: []int8{Internet, LocalNet, Gateway},
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "User Downloads",
|
||||
// Description: "Default Profile for ~/Downloads",
|
||||
// Path: "~/Downloads/",
|
||||
// Flags: []int8{}, // deny all
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
//
|
||||
// (&Profile{
|
||||
// Name: "User Cache",
|
||||
// Description: "Default Profile for ~/.cache",
|
||||
// Path: "~/.cache/",
|
||||
// Flags: []int8{}, // deny all
|
||||
// Default: true,
|
||||
// }).Create()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// }
|
12
profile/specialprofiles.go
Normal file
12
profile/specialprofiles.go
Normal file
|
@ -0,0 +1,12 @@
|
|||
package profile
|
||||
|
||||
import "sync"
|
||||
|
||||
var (
|
||||
globalProfile *Profile
|
||||
defaultProfile *Profile
|
||||
|
||||
specialProfileLock sync.RWMutex
|
||||
)
|
||||
|
||||
// FIXME: subscribe to changes and update profiles
|
|
@ -1,76 +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 profiles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Safing/safing-core/log"
|
||||
)
|
||||
|
||||
type Framework struct {
|
||||
// go hirarchy up
|
||||
FindParent uint8 `json:",omitempty bson:",omitempty"`
|
||||
// get path from parent, amount of levels to go up the tree (1 means parent, 2 means parent of parents, and so on)
|
||||
MergeWithParent bool `json:",omitempty bson:",omitempty"`
|
||||
// instead of getting the path of the parent, merge with it by presenting connections as if they were from that parent
|
||||
|
||||
// go hirarchy down
|
||||
Find string `json:",omitempty bson:",omitempty"`
|
||||
// Regular expression for finding path elements
|
||||
Build string `json:",omitempty bson:",omitempty"`
|
||||
// Path definitions for building path
|
||||
Virtual bool `json:",omitempty bson:",omitempty"`
|
||||
// Treat resulting path as virtual, do not check if valid
|
||||
}
|
||||
|
||||
func (f *Framework) GetNewPath(command string, cwd string) (string, error) {
|
||||
// "/usr/bin/python script"
|
||||
// to
|
||||
// "/path/to/script"
|
||||
regex, err := regexp.Compile(f.Find)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("profiles(framework): failed to compile framework regex: %s", err)
|
||||
}
|
||||
matched := regex.FindAllStringSubmatch(command, -1)
|
||||
if len(matched) == 0 || len(matched[0]) < 2 {
|
||||
return "", fmt.Errorf("profiles(framework): regex \"%s\" for constructing path did not match command \"%s\"", f.Find, command)
|
||||
}
|
||||
|
||||
var lastError error
|
||||
var buildPath string
|
||||
for _, buildPath = range strings.Split(f.Build, "|") {
|
||||
|
||||
buildPath = strings.Replace(buildPath, "{CWD}", cwd, -1)
|
||||
for i := 1; i < len(matched[0]); i++ {
|
||||
buildPath = strings.Replace(buildPath, fmt.Sprintf("{%d}", i), matched[0][i], -1)
|
||||
}
|
||||
|
||||
buildPath = filepath.Clean(buildPath)
|
||||
|
||||
if !f.Virtual {
|
||||
if !strings.HasPrefix(buildPath, "~/") && !filepath.IsAbs(buildPath) {
|
||||
lastError = fmt.Errorf("constructed path \"%s\" from framework is not absolute", buildPath)
|
||||
continue
|
||||
}
|
||||
if _, err := os.Stat(buildPath); os.IsNotExist(err) {
|
||||
lastError = fmt.Errorf("constructed path \"%s\" does not exist", buildPath)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
lastError = nil
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
if lastError != nil {
|
||||
return "", fmt.Errorf("profiles(framework): failed to construct valid path, last error: %s", lastError)
|
||||
}
|
||||
log.Tracef("profiles(framework): transformed \"%s\" (%s) to \"%s\"", command, cwd, buildPath)
|
||||
return buildPath, nil
|
||||
}
|
|
@ -1,30 +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 profiles
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testGetNewPath(t *testing.T, f *Framework, command, cwd, expect string) {
|
||||
newPath, err := f.GetNewPath(command, cwd)
|
||||
if err != nil {
|
||||
t.Errorf("GetNewPath failed: %s", err)
|
||||
}
|
||||
if newPath != expect {
|
||||
t.Errorf("GetNewPath return unexpected result: got %s, expected %s", newPath, expect)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFramework(t *testing.T) {
|
||||
f1 := &Framework{
|
||||
Find: "([^ ]+)$",
|
||||
Build: "{CWD}/{1}",
|
||||
}
|
||||
testGetNewPath(t, f1, "/usr/bin/python bash", "/bin", "/bin/bash")
|
||||
f2 := &Framework{
|
||||
Find: "([^ ]+)$",
|
||||
Build: "{1}|{CWD}/{1}",
|
||||
}
|
||||
testGetNewPath(t, f2, "/usr/bin/python /bin/bash", "/tmp", "/bin/bash")
|
||||
}
|
|
@ -1,265 +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 profiles
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
|
||||
datastore "github.com/ipfs/go-datastore"
|
||||
dsq "github.com/ipfs/go-datastore/query"
|
||||
|
||||
"github.com/Safing/safing-core/database"
|
||||
"github.com/Safing/safing-core/intel"
|
||||
"github.com/Safing/safing-core/log"
|
||||
)
|
||||
|
||||
// Profile is used to predefine a security profile for applications.
|
||||
type Profile struct {
|
||||
database.Base
|
||||
Name string
|
||||
Path string
|
||||
Description string `json:",omitempty bson:",omitempty"`
|
||||
Icon string `json:",omitempty bson:",omitempty"`
|
||||
// Icon is a path to the icon and is either prefixed "f:" for filepath, "d:" for database cache path or "c:"/"a:" for a the icon key to fetch it from a company / authoritative node and cache it in its own cache.
|
||||
|
||||
// TODO: Think more about using one profile for multiple paths
|
||||
// Refer string `json:",omitempty bson:",omitempty"`
|
||||
|
||||
// If a Profile is declared as a Framework (i.e. an Interpreter and the likes), then the real process must be found
|
||||
Framework *Framework `json:",omitempty bson:",omitempty"`
|
||||
// The format how to real process is to be found is yet to be defined.
|
||||
// Ideas:
|
||||
// - Regex for finding the executed script in the arguments, prepend working directory if path is not absolute
|
||||
// - Parent Process?
|
||||
// Use Cases:
|
||||
// - Interpreters (Python, Java, ...)
|
||||
// - Sandboxes (Flatpak, Snapd, Docker, ...)
|
||||
// - Subprocesses of main application process
|
||||
|
||||
SecurityLevel int8 `json:",omitempty bson:",omitempty"`
|
||||
// The mininum security level to apply to connections made with this profile
|
||||
Flags ProfileFlags
|
||||
|
||||
ClassificationBlacklist *intel.EntityClassification `json:",omitempty bson:",omitempty"`
|
||||
ClassificationWhitelist *intel.EntityClassification `json:",omitempty bson:",omitempty"`
|
||||
DomainWhitelistIsBlacklist bool `json:",omitempty bson:",omitempty"`
|
||||
DomainWhitelist []string `json:",omitempty bson:",omitempty"`
|
||||
|
||||
ConnectPorts []uint16 `json:",omitempty bson:",omitempty"`
|
||||
ListenPorts []uint16 `json:",omitempty bson:",omitempty"`
|
||||
|
||||
Default bool `json:",omitempty bson:",omitempty"`
|
||||
// This flag indicates that this profile is a default profile. If no profile is found for a process, the default profile with the longest matching prefix path is used.
|
||||
PromptUserToAdapt bool `json:",omitempty bson:",omitempty"`
|
||||
// This flag is only valid for default profiles and indicates that should this profile be used for a process, the user will be prompted to adapt it for the process and save a new profile.
|
||||
Authoritative bool `json:",omitempty bson:",omitempty"`
|
||||
// This flag indicates that this profile was loaded from an authoritative source - the Safing Community or the Company. Authoritative Profiles that have been modified can be reverted back to their original state.
|
||||
Locked bool `json:",omitempty bson:",omitempty"`
|
||||
// This flag indicates that this profile was locked by the company. This means that the profile may not be edited by the user. If an authoritative default profile is locked, it wins over non-authoritative profiles and the user will not be prompted to adapt the profile, should the PromptUserToAdapt flag be set.
|
||||
Modified bool `json:",omitempty bson:",omitempty"`
|
||||
// This flag indicates that this profile has been modified by the user. Non-modified authoritative profiles will be automatically overwritten with new versions.
|
||||
Orphaned bool `json:",omitempty bson:",omitempty"`
|
||||
// This flag indicates that the associated program (on path) does not exist (Either this entry was manually created, or the program has been uninstalled). Only valid for non-default profiles.
|
||||
|
||||
ApproxLastUsed int64 `json:",omitempty bson:",omitempty"`
|
||||
// When this Profile was approximately last used (for performance reasons not every single usage is saved)
|
||||
}
|
||||
|
||||
var profileModel *Profile // only use this as parameter for database.EnsureModel-like functions
|
||||
|
||||
func init() {
|
||||
database.RegisterModel(profileModel, func() database.Model { return new(Profile) })
|
||||
}
|
||||
|
||||
// Create saves Profile with the provided name in the Profiles namespace.
|
||||
func (m *Profile) Create() error {
|
||||
name := hex.EncodeToString([]byte(m.Path))
|
||||
if m.Default {
|
||||
name = "d-" + name
|
||||
}
|
||||
return m.CreateObject(&database.Profiles, name, m)
|
||||
}
|
||||
|
||||
// CreateInNamespace saves Profile with the provided name in the provided namespace.
|
||||
func (m *Profile) CreateInNamespace(namespace *datastore.Key) error {
|
||||
name := hex.EncodeToString([]byte(m.Path))
|
||||
if m.Default {
|
||||
name = "d-" + name
|
||||
}
|
||||
return m.CreateObject(namespace, name, m)
|
||||
}
|
||||
|
||||
// CreateInDist saves Profile with the (hash of the) path as the name in the Dist namespace.
|
||||
func (m *Profile) CreateInDist() error {
|
||||
return m.CreateInNamespace(&database.DistProfiles)
|
||||
}
|
||||
|
||||
// CreateInCompany saves Profile with the (hash of the) path as the name in the Company namespace.
|
||||
func (m *Profile) CreateInCompany() error {
|
||||
return m.CreateInNamespace(&database.CompanyProfiles)
|
||||
}
|
||||
|
||||
// Save saves Profile.
|
||||
func (m *Profile) Save() error {
|
||||
return m.SaveObject(m)
|
||||
}
|
||||
|
||||
// String returns a string representation of Profile.
|
||||
func (m *Profile) String() string {
|
||||
if m.Default {
|
||||
return "[D] " + m.Name
|
||||
}
|
||||
return m.Name
|
||||
}
|
||||
|
||||
// GetProfile fetches Profile with the provided name from the default namespace.
|
||||
func GetProfile(name string) (*Profile, error) {
|
||||
return GetProfileFromNamespace(&database.Profiles, name)
|
||||
}
|
||||
|
||||
// GetProfileFromNamespace fetches Profile with the provided name from the provided namespace.
|
||||
func GetProfileFromNamespace(namespace *datastore.Key, name string) (*Profile, error) {
|
||||
object, err := database.GetAndEnsureModel(namespace, name, profileModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
model, ok := object.(*Profile)
|
||||
if !ok {
|
||||
return nil, database.NewMismatchError(object, profileModel)
|
||||
}
|
||||
return model, nil
|
||||
}
|
||||
|
||||
// GetActiveProfileByPath fetches Profile with the (hash of the) path as the name from the default namespace.
|
||||
func GetActiveProfileByPath(path string) (*Profile, error) {
|
||||
return GetProfileFromNamespace(&database.Profiles, hex.EncodeToString([]byte(path)))
|
||||
// TODO: check for locked authoritative default profiles
|
||||
}
|
||||
|
||||
// FindProfileByPath looks for a Profile first in the Company namespace and then in the Dist namespace. Should no Profile be available it searches for a Default Profile.
|
||||
func FindProfileByPath(path, homeDir string) (profile *Profile, err error) {
|
||||
name := hex.EncodeToString([]byte(path))
|
||||
var homeName string
|
||||
slashedHomeDir := strings.TrimRight(homeDir, "/") + "/"
|
||||
if homeDir != "" && strings.HasPrefix(path, slashedHomeDir) {
|
||||
homeName = hex.EncodeToString([]byte("~/" + path[len(slashedHomeDir):]))
|
||||
}
|
||||
|
||||
// check for available company profiles
|
||||
profile, err = GetProfileFromNamespace(&database.CompanyProfiles, name)
|
||||
if err != database.ErrNotFound {
|
||||
if err == nil {
|
||||
return profile.Activate()
|
||||
}
|
||||
return
|
||||
}
|
||||
if homeName != "" {
|
||||
profile, err = GetProfileFromNamespace(&database.CompanyProfiles, homeName)
|
||||
if err != database.ErrNotFound {
|
||||
if err == nil {
|
||||
return profile.Activate()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check for available dist profiles
|
||||
profile, err = GetProfileFromNamespace(&database.DistProfiles, name)
|
||||
if err != database.ErrNotFound {
|
||||
if err == nil {
|
||||
return profile.Activate()
|
||||
}
|
||||
return
|
||||
}
|
||||
if homeName != "" {
|
||||
profile, err = GetProfileFromNamespace(&database.DistProfiles, homeName)
|
||||
if err != database.ErrNotFound {
|
||||
if err == nil {
|
||||
return profile.Activate()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// search for best-matching default profile
|
||||
err = nil
|
||||
profile, _ = SearchForDefaultProfile(name, homeName, len(slashedHomeDir)-2, &database.Profiles)
|
||||
return
|
||||
}
|
||||
|
||||
func (m *Profile) Activate() (*Profile, error) {
|
||||
return m, m.Create()
|
||||
}
|
||||
|
||||
func SearchForDefaultProfile(matchKey, matchHomeKey string, addHomeDirLen int, namespace *datastore.Key) (*Profile, int) {
|
||||
|
||||
// log.Tracef("profiles: searching for default profile with %s", matchKey)
|
||||
|
||||
query := dsq.Query{
|
||||
Prefix: namespace.ChildString("Profile:d-").String(),
|
||||
}
|
||||
|
||||
// filter := LongestMatch{
|
||||
// Offset: len(query.Prefix),
|
||||
// Longest: 0,
|
||||
// Match: hex.EncodeToString([]byte(path)),
|
||||
// }
|
||||
// query.Filters = []dsq.Filter{
|
||||
// filter,
|
||||
// }
|
||||
|
||||
prefixOffset := len(query.Prefix)
|
||||
longest := 0
|
||||
var longestMatch interface{}
|
||||
|
||||
currentLongestIsHomeBased := false
|
||||
currentLength := 0
|
||||
|
||||
iterator, err := database.Query(query)
|
||||
if err != nil {
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
for entry, ok := iterator.NextSync(); ok; entry, ok = iterator.NextSync() {
|
||||
// log.Tracef("profiles: checking %s for default profile", entry.Key)
|
||||
// TODO: prioritize locked profiles
|
||||
prefix := entry.Key[prefixOffset:]
|
||||
// skip directly if current longest match is longer than the key
|
||||
// log.Tracef("profiles: comparing %s to %s", matchKey, prefix)
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(matchKey, prefix):
|
||||
currentLength = len(prefix)
|
||||
currentLongestIsHomeBased = false
|
||||
case strings.HasPrefix(matchHomeKey, prefix):
|
||||
currentLength = len(prefix) + addHomeDirLen
|
||||
currentLongestIsHomeBased = true
|
||||
default:
|
||||
continue
|
||||
}
|
||||
// longest wins, if a root-based and home-based tie, root-based wins.
|
||||
if currentLength > longest || (currentLongestIsHomeBased && currentLength == longest) {
|
||||
longest = currentLength
|
||||
longestMatch = entry.Value
|
||||
// log.Tracef("profiles: found new longest (%d) default profile match: %s", currentLength, entry.Key)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if longestMatch == nil {
|
||||
return nil, 0
|
||||
}
|
||||
matched, ok := longestMatch.(database.Model)
|
||||
if !ok {
|
||||
log.Warningf("profiles: matched default profile is not of type database.Model")
|
||||
return nil, 0
|
||||
}
|
||||
|
||||
profile, ok := database.SilentEnsureModel(matched, profileModel).(*Profile)
|
||||
if !ok {
|
||||
log.Warningf("profiles: matched default profile is not of type *Profile")
|
||||
return nil, 0
|
||||
}
|
||||
return profile, longest
|
||||
}
|
|
@ -1,115 +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 profiles
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ProfileFlags are used to quickly add common attributes to profiles
|
||||
type ProfileFlags []int8
|
||||
|
||||
const (
|
||||
// Who?
|
||||
// System apps must be run by system user, else deny
|
||||
System int8 = iota + 1
|
||||
// Admin apps must be run by user with admin privileges, else deny
|
||||
Admin
|
||||
// User apps must be run by user (identified by having an active safing UI), else deny
|
||||
User
|
||||
|
||||
// Where?
|
||||
// Internet apps may connect to the Internet, if unset, all connections to the Internet are denied
|
||||
Internet
|
||||
// LocalNet apps may connect to the local network (i.e. private IP address spaces), if unset, all connections to the local network are denied
|
||||
LocalNet
|
||||
|
||||
// How?
|
||||
// Strict apps may only connect to domains that are related to themselves
|
||||
Strict
|
||||
// Gateway apps will connect to user-defined servers
|
||||
Gateway
|
||||
// Browser apps connect to multitudes of different servers and require special handling
|
||||
Browser
|
||||
// Directconnect apps may connect to any IP without dns association (e.g. P2P apps, network analysis tools)
|
||||
Directconnect
|
||||
// Service apps may accept incoming connections
|
||||
Service
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrProfileFlagsParseFailed is returned if a an invalid flag is encountered while parsing
|
||||
ErrProfileFlagsParseFailed = errors.New("profiles: failed to parse flags")
|
||||
|
||||
sortedFlags = &ProfileFlags{
|
||||
System,
|
||||
Admin,
|
||||
User,
|
||||
Internet,
|
||||
LocalNet,
|
||||
Strict,
|
||||
Gateway,
|
||||
Service,
|
||||
Directconnect,
|
||||
Browser,
|
||||
}
|
||||
|
||||
flagIDs = map[string]int8{
|
||||
"System": System,
|
||||
"Admin": Admin,
|
||||
"User": User,
|
||||
"Internet": Internet,
|
||||
"LocalNet": LocalNet,
|
||||
"Strict": Strict,
|
||||
"Gateway": Gateway,
|
||||
"Service": Service,
|
||||
"Directconnect": Directconnect,
|
||||
"Browser": Browser,
|
||||
}
|
||||
|
||||
flagNames = map[int8]string{
|
||||
System: "System",
|
||||
Admin: "Admin",
|
||||
User: "User",
|
||||
Internet: "Internet",
|
||||
LocalNet: "LocalNet",
|
||||
Strict: "Strict",
|
||||
Gateway: "Gateway",
|
||||
Service: "Service",
|
||||
Directconnect: "Directconnect",
|
||||
Browser: "Browser",
|
||||
}
|
||||
)
|
||||
|
||||
// FlagsFromNames creates ProfileFlags from a comma seperated list of flagnames (e.g. "System,Strict,Secure")
|
||||
func FlagsFromNames(words []string) (*ProfileFlags, error) {
|
||||
var flags ProfileFlags
|
||||
for _, entry := range words {
|
||||
flag, ok := flagIDs[entry]
|
||||
if !ok {
|
||||
return nil, ErrProfileFlagsParseFailed
|
||||
}
|
||||
flags = append(flags, flag)
|
||||
}
|
||||
return &flags, nil
|
||||
}
|
||||
|
||||
// Has checks if a ProfileFlags object has a flag
|
||||
func (pf *ProfileFlags) Has(searchFlag int8) bool {
|
||||
for _, flag := range *pf {
|
||||
if flag == searchFlag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// String return a string representation of ProfileFlags
|
||||
func (pf *ProfileFlags) String() string {
|
||||
var namedFlags []string
|
||||
for _, flag := range *pf {
|
||||
namedFlags = append(namedFlags, flagNames[flag])
|
||||
}
|
||||
return strings.Join(namedFlags, ",")
|
||||
}
|
|
@ -1,65 +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 profiles
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProfileFlags(t *testing.T) {
|
||||
|
||||
// check if SYSTEM is 1
|
||||
if System != 1 {
|
||||
t.Errorf("System ist first const and must be 1")
|
||||
}
|
||||
if Admin != 2 {
|
||||
t.Errorf("Admin ist second const and must be 2")
|
||||
}
|
||||
|
||||
// check if all IDs have a name
|
||||
for key, entry := range flagIDs {
|
||||
if _, ok := flagNames[entry]; !ok {
|
||||
t.Errorf("could not find entry for %s in flagNames", key)
|
||||
}
|
||||
}
|
||||
|
||||
// check if all names have an ID
|
||||
for key, entry := range flagNames {
|
||||
if _, ok := flagIDs[entry]; !ok {
|
||||
t.Errorf("could not find entry for %d in flagNames", key)
|
||||
}
|
||||
}
|
||||
|
||||
// check Has
|
||||
emptyFlags := ProfileFlags{}
|
||||
for flag, name := range flagNames {
|
||||
if !sortedFlags.Has(flag) {
|
||||
t.Errorf("sortedFlags should have flag %s (%d)", name, flag)
|
||||
}
|
||||
if emptyFlags.Has(flag) {
|
||||
t.Errorf("emptyFlags should not have flag %s (%d)", name, flag)
|
||||
}
|
||||
}
|
||||
|
||||
// check ProfileFlags creation from strings
|
||||
var allFlagStrings []string
|
||||
for _, flag := range *sortedFlags {
|
||||
allFlagStrings = append(allFlagStrings, flagNames[flag])
|
||||
}
|
||||
newFlags, err := FlagsFromNames(allFlagStrings)
|
||||
if err != nil {
|
||||
t.Errorf("error while parsing flags: %s", err)
|
||||
}
|
||||
if newFlags.String() != sortedFlags.String() {
|
||||
t.Errorf("parsed flags are not correct (or tests have not been updated to reflect the right number), expected %v, got %v", *sortedFlags, *newFlags)
|
||||
}
|
||||
|
||||
// check ProfileFlags Stringer
|
||||
flagString := newFlags.String()
|
||||
check := strings.Join(allFlagStrings, ",")
|
||||
if flagString != check {
|
||||
t.Errorf("flag string is not correct, expected %s, got %s", check, flagString)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,263 +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 profiles
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
|
||||
ds "github.com/ipfs/go-datastore"
|
||||
|
||||
"github.com/Safing/safing-core/database"
|
||||
"github.com/Safing/safing-core/log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
||||
// Data here is for demo purposes, Profiles will be served over network soon™.
|
||||
|
||||
log.Tracef("profiles: loading sample profiles for %s", runtime.GOOS)
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
|
||||
log.Trace("profiles: loading linux sample profiles")
|
||||
|
||||
(&Profile{
|
||||
Name: "Chromium",
|
||||
Description: "Browser by Google",
|
||||
Path: "/usr/lib/chromium-browser/chromium-browser",
|
||||
Flags: []int8{User, Internet, LocalNet, Browser},
|
||||
ConnectPorts: []uint16{80, 443},
|
||||
}).CreateInDist()
|
||||
|
||||
(&Profile{
|
||||
Name: "Evolution",
|
||||
Description: "PIM solution by GNOME",
|
||||
Path: "/usr/bin/evolution",
|
||||
Flags: []int8{User, Internet, Gateway},
|
||||
ConnectPorts: []uint16{25, 80, 143, 443, 465, 587, 993, 995},
|
||||
SecurityLevel: 2,
|
||||
}).CreateInDist()
|
||||
|
||||
(&Profile{
|
||||
Name: "Evolution Calendar",
|
||||
Description: "PIM solution by GNOME - Calendar",
|
||||
Path: "/usr/lib/evolution/evolution-calendar-factory-subprocess",
|
||||
Flags: []int8{User, Internet, Gateway},
|
||||
ConnectPorts: []uint16{80, 443},
|
||||
SecurityLevel: 2,
|
||||
}).CreateInDist()
|
||||
|
||||
(&Profile{
|
||||
Name: "Spotify",
|
||||
Description: "Music streaming",
|
||||
Path: "/usr/share/spotify/spotify",
|
||||
ConnectPorts: []uint16{80, 443, 4070},
|
||||
Flags: []int8{User, Internet, Strict},
|
||||
}).CreateInDist()
|
||||
|
||||
(&Profile{
|
||||
// flatpak edition
|
||||
Name: "Spotify",
|
||||
Description: "Music streaming",
|
||||
Path: "/newroot/app/extra/share/spotify/spotify",
|
||||
ConnectPorts: []uint16{80, 443, 4070},
|
||||
Flags: []int8{User, Internet, Strict},
|
||||
}).CreateInDist()
|
||||
|
||||
(&Profile{
|
||||
Name: "Evince",
|
||||
Description: "PDF Document Reader",
|
||||
Path: "/usr/bin/evince",
|
||||
Flags: []int8{},
|
||||
SecurityLevel: 2,
|
||||
}).CreateInDist()
|
||||
|
||||
(&Profile{
|
||||
Name: "Ahavi",
|
||||
Description: "mDNS service",
|
||||
Path: "/usr/bin/avahi-daemon",
|
||||
Flags: []int8{System, LocalNet, Service, Directconnect},
|
||||
}).CreateInDist()
|
||||
|
||||
(&Profile{
|
||||
Name: "Python 2.7 Framework",
|
||||
Description: "Correctly handle python scripts",
|
||||
Path: "/usr/bin/python2.7",
|
||||
Framework: &Framework{
|
||||
Find: "^[^ ]+ ([^ ]+)",
|
||||
Build: "{1}|{CWD}/{1}",
|
||||
},
|
||||
}).CreateInDist()
|
||||
|
||||
(&Profile{
|
||||
Name: "Python 3.5 Framework",
|
||||
Description: "Correctly handle python scripts",
|
||||
Path: "/usr/bin/python3.5",
|
||||
Framework: &Framework{
|
||||
Find: "^[^ ]+ ([^ ]+)",
|
||||
Build: "{1}|{CWD}/{1}",
|
||||
},
|
||||
}).CreateInDist()
|
||||
|
||||
(&Profile{
|
||||
Name: "DHCP Client",
|
||||
Description: "Client software for the DHCP protocol",
|
||||
Path: "/sbin/dhclient",
|
||||
Framework: &Framework{
|
||||
FindParent: 1,
|
||||
MergeWithParent: true,
|
||||
},
|
||||
}).CreateInDist()
|
||||
|
||||
// Default Profiles
|
||||
// Until Profiles are distributed over the network, default profiles are activated when the Default Profile for "/" is missing.
|
||||
|
||||
if ok, err := database.Has(ds.NewKey("/Data/Profiles/Profile_d-2f")); !ok || err != nil {
|
||||
|
||||
log.Trace("profiles: loading linux default sample profiles")
|
||||
|
||||
(&Profile{
|
||||
Name: "Default Base",
|
||||
Description: "Default Profile for /",
|
||||
Path: "/",
|
||||
Flags: []int8{Internet, LocalNet, Strict},
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "Installed Applications",
|
||||
Description: "Default Profile for /usr/bin",
|
||||
Path: "/usr/bin/",
|
||||
Flags: []int8{Internet, LocalNet, Gateway},
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "System Binaries (/sbin)",
|
||||
Description: "Default Profile for ~/Downloads",
|
||||
Path: "/sbin/",
|
||||
Flags: []int8{Internet, LocalNet, Directconnect, Service, System},
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "System Binaries (/usr/sbin)",
|
||||
Description: "Default Profile for ~/Downloads",
|
||||
Path: "/usr/sbin/",
|
||||
Flags: []int8{Internet, LocalNet, Directconnect, Service, System},
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "System Tmp folder",
|
||||
Description: "Default Profile for /tmp",
|
||||
Path: "/tmp/",
|
||||
Flags: []int8{}, // deny all
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "User Home",
|
||||
Description: "Default Profile for ~/",
|
||||
Path: "~/",
|
||||
Flags: []int8{Internet, LocalNet, Gateway},
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "User Downloads",
|
||||
Description: "Default Profile for ~/Downloads",
|
||||
Path: "~/Downloads/",
|
||||
Flags: []int8{}, // deny all
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "User Cache",
|
||||
Description: "Default Profile for ~/.cache",
|
||||
Path: "~/.cache/",
|
||||
Flags: []int8{}, // deny all
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
}
|
||||
|
||||
case "windows":
|
||||
|
||||
log.Trace("profiles: loading windows sample profiles")
|
||||
|
||||
(&Profile{
|
||||
Name: "Firefox",
|
||||
Description: "Firefox Browser by Mozilla",
|
||||
Path: "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
|
||||
Flags: []int8{User, Internet, LocalNet, Browser},
|
||||
ConnectPorts: []uint16{80, 443},
|
||||
}).CreateInDist()
|
||||
|
||||
// Default Profiles
|
||||
// Until Profiles are distributed over the network, default profiles are activated when the Default Profile for "C" is missing.
|
||||
|
||||
if ok, err := database.Has(ds.NewKey("/Data/Profiles/Profile:d-C")); !ok || err != nil {
|
||||
|
||||
log.Trace("profiles: loading windows default sample profiles")
|
||||
|
||||
(&Profile{
|
||||
Name: "Default Base",
|
||||
Description: "Default Profile for C",
|
||||
Path: "C",
|
||||
Flags: []int8{Internet, LocalNet, Strict},
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "Installed Applications",
|
||||
Description: "Default Profile for C:\\Program Files",
|
||||
Path: "C:\\Program Files\\",
|
||||
Flags: []int8{Internet, LocalNet, Gateway},
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "Installed Applications (x86)",
|
||||
Description: "Default Profile for C:\\Program Files (x86)",
|
||||
Path: "C:\\Program Files (x86)\\",
|
||||
Flags: []int8{Internet, LocalNet, Gateway},
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "System Applications (C:\\Windows\\System32)",
|
||||
Description: "Default Profile for C:\\Windows\\System32",
|
||||
Path: "C:\\Windows\\System32\\",
|
||||
Flags: []int8{Internet, LocalNet, Directconnect, Service, System},
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "User Home",
|
||||
Description: "Default Profile for ~/",
|
||||
Path: "~/",
|
||||
Flags: []int8{Internet, LocalNet, Gateway},
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "User Downloads",
|
||||
Description: "Default Profile for ~/Downloads",
|
||||
Path: "~/Downloads/",
|
||||
Flags: []int8{}, // deny all
|
||||
Default: true,
|
||||
}).Create()
|
||||
|
||||
(&Profile{
|
||||
Name: "User Cache",
|
||||
Description: "Default Profile for ~/.cache",
|
||||
Path: "~/.cache/",
|
||||
Flags: []int8{}, // deny all
|
||||
Default: true,
|
||||
}).Create()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue