mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
cmd/tailscale/web: restrict web access to synology admins.
Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
5088af68cf
commit
95e296fd96
@ -5,6 +5,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
@ -15,11 +16,13 @@
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/cgi"
|
"net/http/cgi"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/peterbourgon/ff/v2/ffcli"
|
"github.com/peterbourgon/ff/v2/ffcli"
|
||||||
|
"go4.org/mem"
|
||||||
"tailscale.com/client/tailscale"
|
"tailscale.com/client/tailscale"
|
||||||
"tailscale.com/ipn"
|
"tailscale.com/ipn"
|
||||||
"tailscale.com/tailcfg"
|
"tailscale.com/tailcfg"
|
||||||
@ -82,17 +85,63 @@ func runWeb(ctx context.Context, args []string) error {
|
|||||||
return http.ListenAndServe(webArgs.listen, http.HandlerFunc(webHandler))
|
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 {
|
if distro.Get() == distro.Synology {
|
||||||
cmd := exec.Command("/usr/syno/synoman/webman/modules/authenticate.cgi")
|
return authorizeSynology(name)
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("auth: %v: %s", err, out)
|
|
||||||
}
|
|
||||||
return string(out), nil
|
|
||||||
}
|
}
|
||||||
|
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 {
|
func synoTokenRedirect(w http.ResponseWriter, r *http.Request) bool {
|
||||||
@ -198,8 +247,13 @@ func webHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := auth()
|
user, err := authenticate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := authorize(user); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusForbidden)
|
http.Error(w, err.Error(), http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -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
|
W tailscale.com/util/endian from tailscale.com/net/netns
|
||||||
L tailscale.com/util/lineread from tailscale.com/net/interfaces
|
L tailscale.com/util/lineread from tailscale.com/net/interfaces
|
||||||
tailscale.com/version from tailscale.com/cmd/tailscale/cli+
|
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
|
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/blake2b from golang.org/x/crypto/nacl/box
|
||||||
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
|
golang.org/x/crypto/chacha20 from golang.org/x/crypto/chacha20poly1305
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"tailscale.com/version/distro"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AppSharedDir is a string set by the iOS or Android app on start
|
// AppSharedDir is a string set by the iOS or Android app on start
|
||||||
@ -26,11 +28,15 @@ func DefaultTailscaledSocket() string {
|
|||||||
if runtime.GOOS == "darwin" {
|
if runtime.GOOS == "darwin" {
|
||||||
return "/var/run/tailscaled.socket"
|
return "/var/run/tailscaled.socket"
|
||||||
}
|
}
|
||||||
if runtime.GOOS == "linux" {
|
if distro.Get() == distro.Synology {
|
||||||
// TODO(crawshaw): does this path change with DSM7?
|
// TODO(maisem): be smarter about this. We can parse /etc/VERSION.
|
||||||
const synologySock = "/volume1/@appstore/Tailscale/var/tailscaled.sock" // SYNOPKG_PKGDEST in scripts/installer
|
const dsm6Sock = "/var/packages/Tailscale/etc/tailscaled.sock"
|
||||||
if fi, err := os.Stat(filepath.Dir(synologySock)); err == nil && fi.IsDir() {
|
const dsm7Sock = "/var/packages/Tailscale/var/tailscaled.sock"
|
||||||
return synologySock
|
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() {
|
if fi, err := os.Stat("/var/run"); err == nil && fi.IsDir() {
|
||||||
|
Loading…
Reference in New Issue
Block a user