mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 11:05:45 +00:00
client/web: add new readonly mode
The new read-only mode is only accessible when running `tailscale web` by passing a new `-readonly` flag. This new mode is identical to the existing login mode with two exceptions: - the management client in tailscaled is not started (though if it is already running, it is left alone) - the client does not prompt the user to login or switch to the management client. Instead, a message is shown instructing the user to use other means to manage the device. Updates #10979 Signed-off-by: Will Norris <will@tailscale.com>
This commit is contained in:
parent
9f0eaa4464
commit
128c99d4ae
@ -162,7 +162,19 @@ function LoginPopoverContent({
|
||||
</div>
|
||||
{!auth.canManageNode && (
|
||||
<>
|
||||
{!auth.viewerIdentity ? (
|
||||
{auth.serverMode === "readonly" ? (
|
||||
<p className="text-gray-500 text-xs">
|
||||
This web interface is running in read-only mode.{" "}
|
||||
<a
|
||||
href="https://tailscale.com/s/web-client-read-only"
|
||||
className="text-blue-700"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Learn more →
|
||||
</a>
|
||||
</p>
|
||||
) : !auth.viewerIdentity ? (
|
||||
// User is not connected over Tailscale.
|
||||
// These states are only possible on the login client.
|
||||
<>
|
||||
|
@ -12,7 +12,7 @@ export enum AuthType {
|
||||
export type AuthResponse = {
|
||||
authNeeded?: AuthType
|
||||
canManageNode: boolean
|
||||
serverMode: "login" | "manage"
|
||||
serverMode: "login" | "readonly" | "manage"
|
||||
viewerIdentity?: {
|
||||
loginName: string
|
||||
nodeName: string
|
||||
|
@ -95,6 +95,14 @@ type Server struct {
|
||||
// In this mode, API calls are authenticated via platform auth.
|
||||
LoginServerMode ServerMode = "login"
|
||||
|
||||
// ReadOnlyServerMode is identical to LoginServerMode,
|
||||
// but does not present a login button to switch to manage mode,
|
||||
// even if the management client is running and reachable.
|
||||
//
|
||||
// This is designed for platforms where the device is configured by other means,
|
||||
// such as Home Assistant's declarative YAML configuration.
|
||||
ReadOnlyServerMode ServerMode = "readonly"
|
||||
|
||||
// ManageServerMode serves a management client for editing tailscale
|
||||
// settings of a node.
|
||||
//
|
||||
@ -154,7 +162,7 @@ type ServerOpts struct {
|
||||
// and not the lifespan of the web server.
|
||||
func NewServer(opts ServerOpts) (s *Server, err error) {
|
||||
switch opts.Mode {
|
||||
case LoginServerMode, ManageServerMode:
|
||||
case LoginServerMode, ReadOnlyServerMode, ManageServerMode:
|
||||
// valid types
|
||||
case "":
|
||||
return nil, fmt.Errorf("must specify a Mode")
|
||||
@ -207,10 +215,14 @@ func NewServer(opts ServerOpts) (s *Server, err error) {
|
||||
// The client is secured by limiting the interface it listens on,
|
||||
// or by authenticating requests before they reach the web client.
|
||||
csrfProtect := csrf.Protect(s.csrfKey(), csrf.Secure(false))
|
||||
if s.mode == LoginServerMode {
|
||||
switch s.mode {
|
||||
case LoginServerMode:
|
||||
s.apiHandler = csrfProtect(http.HandlerFunc(s.serveLoginAPI))
|
||||
metric = "web_login_client_initialization"
|
||||
} else {
|
||||
case ReadOnlyServerMode:
|
||||
s.apiHandler = csrfProtect(http.HandlerFunc(s.serveLoginAPI))
|
||||
metric = "web_readonly_client_initialization"
|
||||
case ManageServerMode:
|
||||
s.apiHandler = csrfProtect(http.HandlerFunc(s.serveAPI))
|
||||
metric = "web_client_initialization"
|
||||
}
|
||||
@ -483,7 +495,7 @@ func (s *Server) serveAPIAuth(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// First verify platform auth.
|
||||
// If platform auth is needed, this should happen first.
|
||||
if s.mode == LoginServerMode {
|
||||
if s.mode == LoginServerMode || s.mode == ReadOnlyServerMode {
|
||||
switch distro.Get() {
|
||||
case distro.Synology:
|
||||
authorized, err := authorizeSynology(r)
|
||||
|
@ -42,15 +42,17 @@
|
||||
webf.StringVar(&webArgs.listen, "listen", "localhost:8088", "listen address; use port 0 for automatic")
|
||||
webf.BoolVar(&webArgs.cgi, "cgi", false, "run as CGI script")
|
||||
webf.StringVar(&webArgs.prefix, "prefix", "", "URL prefix added to requests (for cgi or reverse proxies)")
|
||||
webf.BoolVar(&webArgs.readonly, "readonly", false, "run web UI in read-only mode")
|
||||
return webf
|
||||
})(),
|
||||
Exec: runWeb,
|
||||
}
|
||||
|
||||
var webArgs struct {
|
||||
listen string
|
||||
cgi bool
|
||||
prefix string
|
||||
listen string
|
||||
cgi bool
|
||||
prefix string
|
||||
readonly bool
|
||||
}
|
||||
|
||||
func tlsConfigFromEnvironment() *tls.Config {
|
||||
@ -94,20 +96,26 @@ func runWeb(ctx context.Context, args []string) error {
|
||||
if prefs, err := localClient.GetPrefs(ctx); err == nil {
|
||||
existingWebClient = prefs.RunWebClient
|
||||
}
|
||||
if !existingWebClient {
|
||||
var startedManagementClient bool // we started the management client
|
||||
if !existingWebClient && !webArgs.readonly {
|
||||
// Also start full client in tailscaled.
|
||||
log.Printf("starting tailscaled web client at %s:%d\n", selfIP.String(), web.ListenPort)
|
||||
if err := setRunWebClient(ctx, true); err != nil {
|
||||
return fmt.Errorf("starting web client in tailscaled: %w", err)
|
||||
}
|
||||
startedManagementClient = true
|
||||
}
|
||||
|
||||
webServer, err := web.NewServer(web.ServerOpts{
|
||||
opts := web.ServerOpts{
|
||||
Mode: web.LoginServerMode,
|
||||
CGIMode: webArgs.cgi,
|
||||
PathPrefix: webArgs.prefix,
|
||||
LocalClient: &localClient,
|
||||
})
|
||||
}
|
||||
if webArgs.readonly {
|
||||
opts.Mode = web.ReadOnlyServerMode
|
||||
}
|
||||
webServer, err := web.NewServer(opts)
|
||||
if err != nil {
|
||||
log.Printf("tailscale.web: %v", err)
|
||||
return err
|
||||
@ -117,10 +125,10 @@ func runWeb(ctx context.Context, args []string) error {
|
||||
case <-ctx.Done():
|
||||
// Shutdown the server.
|
||||
webServer.Shutdown()
|
||||
if !webArgs.cgi && !existingWebClient {
|
||||
if !webArgs.cgi && startedManagementClient {
|
||||
log.Println("stopping tailscaled web client")
|
||||
// When not in cgi mode, shut down the tailscaled
|
||||
// web client on cli termination.
|
||||
// web client on cli termination if we started it.
|
||||
if err := setRunWebClient(context.Background(), false); err != nil {
|
||||
log.Printf("stopping tailscaled web client: %v", err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user