mirror of https://github.com/tailscale/tailscale/
cmd/tailscale/web: add support for QNAP
Signed-off-by: Maisem Ali <maisem@tailscale.com>pull/2108/head
parent
8b11937eaf
commit
f944614c5c
@ -0,0 +1,57 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Redirecting...</title>
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
background-color: rgb(249, 247, 246);
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
line-height: 1.5;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
border: 4px rgba(112, 110, 109, 0.5) solid;
|
||||||
|
border-left-color: transparent;
|
||||||
|
border-radius: 9999px;
|
||||||
|
width: 4rem;
|
||||||
|
height: 4rem;
|
||||||
|
-webkit-animation: spin 700ms linear infinite;
|
||||||
|
animation: spin 800ms linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: rgb(112, 110, 109);
|
||||||
|
padding-left: 0.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head> <body>
|
||||||
|
<div class="spinner"></div>
|
||||||
|
<div class="label">Redirecting...</div>
|
||||||
|
</body>
|
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package groupmemeber verifies group membership of the provided user on the
|
||||||
|
// local system.
|
||||||
|
package groupmember
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotImplemented = errors.New("not implemented for GOOS=" + runtime.GOOS)
|
||||||
|
|
||||||
|
// IsMemberOfGroup verifies if the provided user is member of the provided
|
||||||
|
// system group.
|
||||||
|
// If verfication fails, an error is returned.
|
||||||
|
func IsMemberOfGroup(group, userName string) (bool, error) {
|
||||||
|
return isMemberOfGroup(group, userName)
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build cgo
|
||||||
|
|
||||||
|
package groupmember
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/user"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isMemberOfGroup(group, name string) (bool, error) {
|
||||||
|
u, err := user.Lookup(name)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
ugids, err := u.GroupIds()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
gid, err := getGroupID(group)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
for _, ugid := range ugids {
|
||||||
|
if gid == ugid {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupIDCache sync.Map // of string
|
||||||
|
|
||||||
|
func getGroupID(groupName string) (string, error) {
|
||||||
|
s, ok := groupIDCache.Load(groupName)
|
||||||
|
if ok {
|
||||||
|
return s.(string), nil
|
||||||
|
}
|
||||||
|
g, err := user.LookupGroup(groupName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
groupIDCache.Store(groupName, g.Gid)
|
||||||
|
return g.Gid, nil
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !cgo,!linux,!darwin
|
||||||
|
|
||||||
|
package groupmember
|
||||||
|
|
||||||
|
func isMemberOfGroup(group, name string) (bool, error) { return false, ErrNotImplemented }
|
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) 2021 Tailscale Inc & AUTHORS All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build !cgo
|
||||||
|
// +build linux darwin
|
||||||
|
|
||||||
|
package groupmember
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go4.org/mem"
|
||||||
|
"tailscale.com/version/distro"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isMemberOfGroup(group, name string) (bool, error) {
|
||||||
|
if distro.Get() == distro.Synology {
|
||||||
|
return isMemberOfGroupEtcGroup(group, name)
|
||||||
|
}
|
||||||
|
cmd := exec.Command("/usr/bin/env", "groups", name)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
groups := strings.Split(strings.TrimSpace(string(out)), " ")
|
||||||
|
for _, g := range groups {
|
||||||
|
if g == group {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isMemberOfGroupEtcGroup(group, name string) (bool, error) {
|
||||||
|
f, err := os.Open("/etc/group")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
s := bufio.NewScanner(f)
|
||||||
|
var agLine string
|
||||||
|
for s.Scan() {
|
||||||
|
if !mem.HasPrefix(mem.B(s.Bytes()), mem.S(fmt.Sprintf("%s:", group))) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
agLine = s.Text()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := s.Err(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if agLine == "" {
|
||||||
|
return false, fmt.Errorf("admin group not defined")
|
||||||
|
}
|
||||||
|
agEntry := strings.Split(agLine, ":")
|
||||||
|
if len(agEntry) < 4 {
|
||||||
|
return false, fmt.Errorf("malformed admin group entry")
|
||||||
|
}
|
||||||
|
agMembers := agEntry[3]
|
||||||
|
for _, m := range strings.Split(agMembers, ",") {
|
||||||
|
if m == name {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
Loading…
Reference in New Issue