/
api.go
189 lines (157 loc) Β· 5.06 KB
/
api.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package firewall
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"strconv"
"strings"
"time"
"github.com/safing/portbase/api"
"github.com/safing/portbase/dataroot"
"github.com/safing/portbase/log"
"github.com/safing/portbase/utils"
"github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/process"
)
const (
deniedMsgUnidentified = `%wFailed to identify the requesting process.
You can enable the Development Mode to disable API authentication for development purposes.
If you are seeing this message in the Portmaster App, please restart the app or right-click and select "Reload".
In the future, this issue will be remediated automatically.`
deniedMsgSystem = `%wSystem access to the Portmaster API is not permitted.
You can enable the Development Mode to disable API authentication for development purposes.`
deniedMsgUnauthorized = `%wThe requesting process is not authorized to access the Portmaster API.
Checked process paths:
%s
The authorized root path is %s.
You can enable the Development Mode to disable API authentication for development purposes.`
)
var (
dataRoot *utils.DirStructure
apiPortSet bool
apiPort uint16
)
func prepAPIAuth() error {
dataRoot = dataroot.Root()
return api.SetAuthenticator(apiAuthenticator)
}
func startAPIAuth() {
var err error
_, apiPort, err = parseHostPort(apiListenAddress())
if err != nil {
log.Warningf("filter: failed to parse API address for improved api auth mechanism: %s", err)
return
}
apiPortSet = true
log.Tracef("filter: api port set to %d", apiPort)
}
func apiAuthenticator(ctx context.Context, s *http.Server, r *http.Request) (err error) {
if devMode() {
return nil
}
// get local IP/Port
localIP, localPort, err := parseHostPort(s.Addr)
if err != nil {
return fmt.Errorf("failed to get local IP/Port: %s", err)
}
// get remote IP/Port
remoteIP, remotePort, err := parseHostPort(r.RemoteAddr)
if err != nil {
return fmt.Errorf("failed to get remote IP/Port: %s", err)
}
log.Tracer(r.Context()).Tracef("filter: authenticating API request from %s", r.RemoteAddr)
// It is very important that this works, retry extensively (every 250ms for 5s)
var retry bool
for tries := 0; tries < 20; tries++ {
retry, err = authenticateAPIRequest(
ctx,
&packet.Info{
Inbound: false, // outbound as we are looking for the process of the source address
Version: packet.IPv4,
Protocol: packet.TCP,
Src: remoteIP, // source as in the process we are looking for
SrcPort: remotePort, // source as in the process we are looking for
Dst: localIP,
DstPort: localPort,
},
)
if !retry {
return err
}
// wait a little
time.Sleep(250 * time.Millisecond)
}
return err
}
func authenticateAPIRequest(ctx context.Context, pktInfo *packet.Info) (retry bool, err error) {
var procsChecked []string
// get process
proc, _, err := process.GetProcessByConnection(ctx, pktInfo)
if err != nil {
return true, fmt.Errorf("failed to get process: %s", err)
}
originalPid := proc.Pid
var previousPid int
// go up up to two levels, if we don't match
for i := 0; i < 5; i++ {
// check for eligible PID
switch proc.Pid {
case process.UnidentifiedProcessID, process.SystemProcessID:
break
default: // normal process
// check if the requesting process is in database root / updates dir
if strings.HasPrefix(proc.Path, dataRoot.Path) {
return false, nil
}
}
// add checked process to list
procsChecked = append(procsChecked, proc.Path)
if i < 4 {
// save previous PID
previousPid = proc.Pid
// get parent process
proc, err = process.GetOrFindProcess(ctx, proc.ParentPid)
if err != nil {
return true, fmt.Errorf("failed to get process: %s", err)
}
// abort if we are looping
if proc.Pid == previousPid {
// this also catches -1 pid loops
break
}
}
}
switch originalPid {
case process.UnidentifiedProcessID:
log.Tracer(ctx).Warningf("filter: denying api access: failed to identify process")
return true, fmt.Errorf(deniedMsgUnidentified, api.ErrAPIAccessDeniedMessage) //nolint:stylecheck // message for user
case process.SystemProcessID:
log.Tracer(ctx).Warningf("filter: denying api access: request by system")
return false, fmt.Errorf(deniedMsgSystem, api.ErrAPIAccessDeniedMessage) //nolint:stylecheck // message for user
default: // normal process
log.Tracer(ctx).Warningf("filter: denying api access to %s - also checked %s (trusted root is %s)", procsChecked[0], strings.Join(procsChecked[1:], " "), dataRoot.Path)
return false, fmt.Errorf( //nolint:stylecheck // message for user
deniedMsgUnauthorized,
api.ErrAPIAccessDeniedMessage,
strings.Join(procsChecked, "\n"),
dataRoot.Path,
)
}
}
func parseHostPort(address string) (net.IP, uint16, error) {
ipString, portString, err := net.SplitHostPort(address)
if err != nil {
return nil, 0, err
}
ip := net.ParseIP(ipString)
if ip == nil {
return nil, 0, errors.New("invalid IP address")
}
port, err := strconv.ParseUint(portString, 10, 16)
if err != nil {
return nil, 0, err
}
return ip, uint16(port), nil
}