cmd/tailscale/web: restrict web access to synology admins.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
Maisem Ali 2021-05-30 10:48:22 +05:00 committed by Maisem Ali
parent 5088af68cf
commit 95e296fd96
3 changed files with 75 additions and 15 deletions

View File

@ -5,6 +5,7 @@
package cli
import (
"bufio"
"bytes"
"context"
_ "embed"
@ -15,11 +16,13 @@
"log"
"net/http"
"net/http/cgi"
"os"
"os/exec"
"runtime"
"strings"
"github.com/peterbourgon/ff/v2/ffcli"
"go4.org/mem"
"tailscale.com/client/tailscale"
"tailscale.com/ipn"
"tailscale.com/tailcfg"
@ -82,17 +85,63 @@ func runWeb(ctx context.Context, args []string) error {
return http.ListenAndServe(webArgs.listen, http.HandlerFunc(webHandler))
}
func auth() (string, error) {
// authorize checks whether the provided user has access to the web UI.
func authorize(name string) error {
if distro.Get() == distro.Synology {
cmd := exec.Command("/usr/syno/synoman/webman/modules/authenticate.cgi")
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("auth: %v: %s", err, out)
}
return string(out), nil
return authorizeSynology(name)
}
return nil
}
return "", nil
// authorizeSynology checks whether the provided user has access to the web UI
// by consulting the membership of the "administrators" group.
func authorizeSynology(name string) error {
f, err := os.Open("/etc/group")
if err != nil {
return err
}
defer f.Close()
s := bufio.NewScanner(f)
var agLine string
for s.Scan() {
if !mem.HasPrefix(mem.B(s.Bytes()), mem.S("administrators:")) {
continue
}
agLine = s.Text()
break
}
if err := s.Err(); err != nil {
return err
}
if agLine == "" {
return fmt.Errorf("admin group not defined")
}
agEntry := strings.Split(agLine, ":")
if len(agEntry) < 4 {
return fmt.Errorf("malformed admin group entry")
}
agMembers := agEntry[3]
for _, m := range strings.Split(agMembers, ",") {
if m == name {
return nil
}
}
return fmt.Errorf("not a member of administrators group")
}
// authenticate returns the name of the user accessing the web UI.
// Note: This is different from a tailscale user, and is typically the local
// user on the node.
func authenticate() (string, error) {
if distro.Get() != distro.Synology {
return "", nil
}
cmd := exec.Command("/usr/syno/synoman/webman/modules/authenticate.cgi")
out, err := cmd.CombinedOutput()
if err != nil {
return "", fmt.Errorf("auth: %v: %s", err, out)
}
return strings.TrimSpace(string(out)), nil
}
func synoTokenRedirect(w http.ResponseWriter, r *http.Request) bool {
@ -198,8 +247,13 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
return
}
user, err := auth()
user, err := authenticate()
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
if err := authorize(user); err != nil {
http.Error(w, err.Error(), http.StatusForbidden)
return
}

View File

@ -55,7 +55,7 @@ tailscale.com/cmd/tailscale dependencies: (generated by github.com/tailscale/dep
W tailscale.com/util/endian from tailscale.com/net/netns
L tailscale.com/util/lineread from tailscale.com/net/interfaces
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli
tailscale.com/version/distro from tailscale.com/cmd/tailscale/cli+
tailscale.com/wgengine/filter from tailscale.com/types/netmap
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305

View File

@ -11,6 +11,8 @@
"path/filepath"
"runtime"
"sync/atomic"
"tailscale.com/version/distro"
)
// AppSharedDir is a string set by the iOS or Android app on start
@ -26,11 +28,15 @@ func DefaultTailscaledSocket() string {
if runtime.GOOS == "darwin" {
return "/var/run/tailscaled.socket"
}
if runtime.GOOS == "linux" {
// TODO(crawshaw): does this path change with DSM7?
const synologySock = "/volume1/@appstore/Tailscale/var/tailscaled.sock" // SYNOPKG_PKGDEST in scripts/installer
if fi, err := os.Stat(filepath.Dir(synologySock)); err == nil && fi.IsDir() {
return synologySock
if distro.Get() == distro.Synology {
// TODO(maisem): be smarter about this. We can parse /etc/VERSION.
const dsm6Sock = "/var/packages/Tailscale/etc/tailscaled.sock"
const dsm7Sock = "/var/packages/Tailscale/var/tailscaled.sock"
if fi, err := os.Stat(dsm6Sock); err == nil && !fi.IsDir() {
return dsm6Sock
}
if fi, err := os.Stat(dsm7Sock); err == nil && !fi.IsDir() {
return dsm7Sock
}
}
if fi, err := os.Stat("/var/run"); err == nil && fi.IsDir() {