Improve app-auth request flow and implement review suggestions

This commit is contained in:
Patrick Pacher 2022-07-27 15:14:52 +02:00
parent 6aa13c2335
commit d49232a37b
No known key found for this signature in database
GPG key ID: E8CD2DA160925A6D
3 changed files with 66 additions and 45 deletions

View file

@ -3,12 +3,9 @@ package core
import ( import (
"context" "context"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"net"
"net/http" "net/http"
"net/url" "net/url"
"strconv"
"time" "time"
"github.com/safing/portbase/api" "github.com/safing/portbase/api"
@ -19,7 +16,6 @@ import (
"github.com/safing/portbase/rng" "github.com/safing/portbase/rng"
"github.com/safing/portbase/utils/debug" "github.com/safing/portbase/utils/debug"
"github.com/safing/portmaster/compat" "github.com/safing/portmaster/compat"
"github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/process" "github.com/safing/portmaster/process"
"github.com/safing/portmaster/resolver" "github.com/safing/portmaster/resolver"
"github.com/safing/portmaster/status" "github.com/safing/portmaster/status"
@ -72,7 +68,7 @@ func registerAPIEndpoints() error {
Read: api.PermitAnyone, Read: api.PermitAnyone,
BelongsTo: module, BelongsTo: module,
StructFunc: authorizeApp, StructFunc: authorizeApp,
Name: "Call to request an authentication token", Name: "Request an authentication token with a given set of permissions. The user will be prompted to either authorize or deny the request. Used for external or third-party tool integrations.",
Parameters: []api.Parameter{ Parameters: []api.Parameter{
{ {
Method: http.MethodGet, Method: http.MethodGet,
@ -154,7 +150,10 @@ func debugInfo(ar *api.Request) (data []byte, err error) {
return di.Bytes(), nil return di.Bytes(), nil
} }
func getPermission(p string) api.Permission { // getSavePermission returns the requested api.Permission from p.
// It only allows "user" and "admin" as external processes should
// never be able to request "self".
func getSavePermission(p string) api.Permission {
switch p { switch p {
case "user": case "user":
return api.PermitUser return api.PermitUser
@ -166,26 +165,13 @@ func getPermission(p string) api.Permission {
} }
func getMyProfile(ar *api.Request) (interface{}, error) { func getMyProfile(ar *api.Request) (interface{}, error) {
// get remote IP/Port proc, err := process.GetProcessByRequestOrigin(ar)
remoteIP, remotePort, err := parseHostPort(ar.RemoteAddr)
if err != nil {
return nil, fmt.Errorf("failed to get remote IP/Port: %w", err)
}
pkt := &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
}
proc, _, err := process.GetProcessByConnection(ar.Context(), pkt)
if err != nil { if err != nil {
return nil, err return nil, err
} }
localProfile := proc.Profile().LocalProfile() localProfile := proc.Profile().LocalProfile()
return map[string]interface{}{ return map[string]interface{}{
"profile": localProfile.ID, "profile": localProfile.ID,
"source": localProfile.Source, "source": localProfile.Source,
@ -193,25 +179,6 @@ func getMyProfile(ar *api.Request) (interface{}, error) {
}, nil }, nil
} }
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
}
func authorizeApp(ar *api.Request) (interface{}, error) { func authorizeApp(ar *api.Request) (interface{}, error) {
appName := ar.Request.URL.Query().Get("app-name") appName := ar.Request.URL.Query().Get("app-name")
readPermStr := ar.Request.URL.Query().Get("read") readPermStr := ar.Request.URL.Query().Get("read")
@ -226,21 +193,25 @@ func authorizeApp(ar *api.Request) (interface{}, error) {
} }
} }
if getPermission(readPermStr) <= api.NotSupported { // convert the requested read and write permissions to their api.Permission
// value. This ensures only "user" or "admin" permissions can be requested.
if getSavePermission(readPermStr) <= api.NotSupported {
return nil, fmt.Errorf("invalid read permission") return nil, fmt.Errorf("invalid read permission")
} }
if getPermission(writePermStr) <= api.NotSupported { if getSavePermission(writePermStr) <= api.NotSupported {
return nil, fmt.Errorf("invalid read permission") return nil, fmt.Errorf("invalid read permission")
} }
// appIcon := ar.Request.URL.Query().Get("app-icon") proc, err := process.GetProcessByRequestOrigin(ar)
// TODO(ppacher): get the guessed mime-type from appIcon and make sure it's an image if err != nil {
return nil, fmt.Errorf("failed to identify requesting process: %w", err)
}
n := notifications.Notification{ n := notifications.Notification{
Type: notifications.Prompt, Type: notifications.Prompt,
EventID: "core:authorize-app-" + time.Now().String(), EventID: "core:authorize-app-" + time.Now().String(),
Title: "An app requests access to the Portmaster", Title: "An app requests access to the Portmaster",
Message: "Allow " + appName + " to query and modify the Portmaster?", Message: "Allow " + appName + " (" + proc.Profile().LocalProfile().Name + ") to query and modify the Portmaster?\n\nBinary: " + proc.Path,
ShowOnSystem: true, ShowOnSystem: true,
Expires: time.Now().Add(time.Minute).UnixNano(), Expires: time.Now().Add(time.Minute).UnixNano(),
AvailableActions: []*notifications.Action{ AvailableActions: []*notifications.Action{

View file

@ -1,10 +1,14 @@
package netutils package netutils
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"strconv"
) )
var errInvalidIP = errors.New("invalid IP address")
// IPFromAddr extracts or parses the IP address contained in the given address. // IPFromAddr extracts or parses the IP address contained in the given address.
func IPFromAddr(addr net.Addr) (net.IP, error) { func IPFromAddr(addr net.Addr) (net.IP, error) {
// Convert addr to IP if needed. // Convert addr to IP if needed.
@ -28,3 +32,23 @@ func IPFromAddr(addr net.Addr) (net.IP, error) {
return ip, nil return ip, nil
} }
} }
// ParseHostPort parses a <ip>:port formatted address.
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, errInvalidIP
}
port, err := strconv.ParseUint(portString, 10, 16)
if err != nil {
return nil, 0, err
}
return ip, uint16(port), nil
}

View file

@ -6,7 +6,9 @@ import (
"net" "net"
"time" "time"
"github.com/safing/portbase/api"
"github.com/safing/portbase/log" "github.com/safing/portbase/log"
"github.com/safing/portmaster/network/netutils"
"github.com/safing/portmaster/network/packet" "github.com/safing/portmaster/network/packet"
"github.com/safing/portmaster/network/state" "github.com/safing/portmaster/network/state"
"github.com/safing/portmaster/profile" "github.com/safing/portmaster/profile"
@ -94,3 +96,27 @@ func GetNetworkHost(ctx context.Context, remoteIP net.IP) (process *Process, err
return networkHost, nil return networkHost, nil
} }
// GetProcessByRequestOrigin returns the process that initiated the API request ar.
func GetProcessByRequestOrigin(ar *api.Request) (*Process, error) {
// get remote IP/Port
remoteIP, remotePort, err := netutils.ParseHostPort(ar.RemoteAddr)
if err != nil {
return nil, fmt.Errorf("failed to get remote IP/Port: %w", err)
}
pkt := &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
}
proc, _, err := GetProcessByConnection(ar.Context(), pkt)
if err != nil {
return nil, err
}
return proc, nil
}