mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-01 14:02:51 +00:00

So Linux/etc CLI users get helpful advice to run tailscale with --operator=$USER when they try to 'tailscale file {cp,get}' but are mysteriously forbidden. Signed-off-by: David Eger <eger@google.com> Signed-off-by: David Eger <david.eger@gmail.com>
153 lines
4.3 KiB
Go
153 lines
4.3 KiB
Go
// 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 cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/tls"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/peterbourgon/ff/v3/ffcli"
|
|
"tailscale.com/atomicfile"
|
|
"tailscale.com/client/tailscale"
|
|
"tailscale.com/ipn"
|
|
"tailscale.com/version"
|
|
)
|
|
|
|
var certCmd = &ffcli.Command{
|
|
Name: "cert",
|
|
Exec: runCert,
|
|
ShortHelp: "get TLS certs",
|
|
ShortUsage: "cert [flags] <domain>",
|
|
FlagSet: (func() *flag.FlagSet {
|
|
fs := newFlagSet("cert")
|
|
fs.StringVar(&certArgs.certFile, "cert-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.crt if --cert-file and --key-file are both unset")
|
|
fs.StringVar(&certArgs.keyFile, "key-file", "", "output cert file or \"-\" for stdout; defaults to DOMAIN.key if --cert-file and --key-file are both unset")
|
|
fs.BoolVar(&certArgs.serve, "serve-demo", false, "if true, serve on port :443 using the cert as a demo, instead of writing out the files to disk")
|
|
return fs
|
|
})(),
|
|
}
|
|
|
|
var certArgs struct {
|
|
certFile string
|
|
keyFile string
|
|
serve bool
|
|
}
|
|
|
|
func runCert(ctx context.Context, args []string) error {
|
|
if certArgs.serve {
|
|
s := &http.Server{
|
|
TLSConfig: &tls.Config{
|
|
GetCertificate: tailscale.GetCertificate,
|
|
},
|
|
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.TLS != nil && !strings.Contains(r.Host, ".") && r.Method == "GET" {
|
|
if v, ok := tailscale.ExpandSNIName(r.Context(), r.Host); ok {
|
|
http.Redirect(w, r, "https://"+v+r.URL.Path, http.StatusTemporaryRedirect)
|
|
return
|
|
}
|
|
}
|
|
fmt.Fprintf(w, "<h1>Hello from Tailscale</h1>It works.")
|
|
}),
|
|
}
|
|
log.Printf("running TLS server on :443 ...")
|
|
return s.ListenAndServeTLS("", "")
|
|
}
|
|
|
|
if len(args) != 1 {
|
|
var hint bytes.Buffer
|
|
if st, err := tailscale.Status(ctx); err == nil {
|
|
if st.BackendState != ipn.Running.String() {
|
|
fmt.Fprintf(&hint, "\nTailscale is not running.\n")
|
|
} else if len(st.CertDomains) == 0 {
|
|
fmt.Fprintf(&hint, "\nHTTPS cert support is not enabled/configured for your tailnet.\n")
|
|
} else if len(st.CertDomains) == 1 {
|
|
fmt.Fprintf(&hint, "\nFor domain, use %q.\n", st.CertDomains[0])
|
|
} else {
|
|
fmt.Fprintf(&hint, "\nValid domain options: %q.\n", st.CertDomains)
|
|
}
|
|
}
|
|
return fmt.Errorf("Usage: tailscale cert [flags] <domain>%s", hint.Bytes())
|
|
}
|
|
domain := args[0]
|
|
|
|
printf := func(format string, a ...interface{}) {
|
|
printf(format, a...)
|
|
}
|
|
if certArgs.certFile == "-" || certArgs.keyFile == "-" {
|
|
printf = log.Printf
|
|
log.SetFlags(0)
|
|
}
|
|
if certArgs.certFile == "" && certArgs.keyFile == "" {
|
|
certArgs.certFile = domain + ".crt"
|
|
certArgs.keyFile = domain + ".key"
|
|
}
|
|
certPEM, keyPEM, err := tailscale.CertPair(ctx, domain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
needMacWarning := version.IsSandboxedMacOS()
|
|
macWarn := func() {
|
|
if !needMacWarning {
|
|
return
|
|
}
|
|
needMacWarning = false
|
|
dir := "io.tailscale.ipn.macos"
|
|
if version.IsMacSysExt() {
|
|
dir = "io.tailscale.ipn.macsys"
|
|
}
|
|
printf("Warning: the macOS CLI runs in a sandbox; this binary's filesystem writes go to $HOME/Library/Containers/%s/Data\n", dir)
|
|
}
|
|
if certArgs.certFile != "" {
|
|
certChanged, err := writeIfChanged(certArgs.certFile, certPEM, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if certArgs.certFile != "-" {
|
|
macWarn()
|
|
if certChanged {
|
|
printf("Wrote public cert to %v\n", certArgs.certFile)
|
|
} else {
|
|
printf("Public cert unchanged at %v\n", certArgs.certFile)
|
|
}
|
|
}
|
|
}
|
|
if certArgs.keyFile != "" {
|
|
keyChanged, err := writeIfChanged(certArgs.keyFile, keyPEM, 0600)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if certArgs.keyFile != "-" {
|
|
macWarn()
|
|
if keyChanged {
|
|
printf("Wrote private key to %v\n", certArgs.keyFile)
|
|
} else {
|
|
printf("Private key unchanged at %v\n", certArgs.keyFile)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeIfChanged(filename string, contents []byte, mode os.FileMode) (changed bool, err error) {
|
|
if filename == "-" {
|
|
Stdout.Write(contents)
|
|
return false, nil
|
|
}
|
|
if old, err := os.ReadFile(filename); err == nil && bytes.Equal(contents, old) {
|
|
return false, nil
|
|
}
|
|
if err := atomicfile.WriteFile(filename, contents, mode); err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|