mirror of
https://github.com/juanfont/headscale.git
synced 2025-01-18 18:00:04 +09:00
feat: tailscale serve support
This commit is contained in:
parent
ede4f97a16
commit
3453990452
5 changed files with 109 additions and 0 deletions
|
@ -242,6 +242,15 @@ policy:
|
|||
# HuJSON file containing ACL policies.
|
||||
path: ""
|
||||
|
||||
|
||||
certificates:
|
||||
enabled: false
|
||||
# Path to an executable that will be called when dns01 challenge is raised by tailscale client.
|
||||
# Command will be called with 3 arguments: <domain>,<type>,<value>
|
||||
# Eg: /path/to/set-dns-command "_acme-challenge.node1.example.com" "TXT" "jYhsfThsdf_Lo3shgdBRY7hNxe"
|
||||
set_dns_command: ""
|
||||
|
||||
|
||||
## DNS
|
||||
#
|
||||
# headscale supports Tailscale's DNS configuration and MagicDNS.
|
||||
|
|
|
@ -124,6 +124,13 @@ func generateDNSConfig(
|
|||
|
||||
addNextDNSMetadata(dnsConfig.Resolvers, node)
|
||||
|
||||
hostname, err := node.GetFQDN(cfg.BaseDomain)
|
||||
if err != nil {
|
||||
log.Warn().Msgf("failed to get FQDN of node %s for certDomains: %s", node.ID, err)
|
||||
} else {
|
||||
dnsConfig.CertDomains = append(dnsConfig.CertDomains, hostname)
|
||||
}
|
||||
|
||||
return dnsConfig
|
||||
}
|
||||
|
||||
|
|
|
@ -120,6 +120,10 @@ func tailNode(
|
|||
tailcfg.CapabilitySSH: []tailcfg.RawMessage{},
|
||||
}
|
||||
|
||||
if cfg.CertificatesFeatureConfig.Enabled {
|
||||
tNode.CapMap[tailcfg.CapabilityHTTPS] = []tailcfg.RawMessage{}
|
||||
}
|
||||
|
||||
if cfg.RandomizeClientPort {
|
||||
tNode.CapMap[tailcfg.NodeAttrRandomizeClientPort] = []tailcfg.RawMessage{}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
|
@ -99,6 +101,7 @@ func (h *Headscale) NoiseUpgradeHandler(
|
|||
router.HandleFunc("/machine/register", noiseServer.NoiseRegistrationHandler).
|
||||
Methods(http.MethodPost)
|
||||
router.HandleFunc("/machine/map", noiseServer.NoisePollNetMapHandler)
|
||||
router.HandleFunc("/machine/set-dns", noiseServer.SetDNSHandler).Methods(http.MethodPost)
|
||||
|
||||
noiseServer.httpBaseConfig = &http.Server{
|
||||
Handler: router,
|
||||
|
@ -232,3 +235,70 @@ func (ns *noiseServer) NoisePollNetMapHandler(
|
|||
sess.serveLongPoll()
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *noiseServer) SetDNSHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
body, _ := io.ReadAll(req.Body)
|
||||
|
||||
setDnsRequest := tailcfg.SetDNSRequest{}
|
||||
if err := json.Unmarshal(body, &setDnsRequest); err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Cannot parse MapRequest")
|
||||
http.Error(writer, "Internal error", http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
log.Info().
|
||||
Caller().
|
||||
Str("handler", "NoisePollNetMap").
|
||||
Any("headers", req.Header).
|
||||
Str("NodeKey", setDnsRequest.NodeKey.ShortString()).
|
||||
Str("Name", setDnsRequest.Name).
|
||||
Str("Type", setDnsRequest.Type).
|
||||
Str("Value", setDnsRequest.Value).
|
||||
Msg("SetDNSHandler called")
|
||||
|
||||
if !ns.headscale.cfg.CertificatesFeatureConfig.Enabled {
|
||||
http.Error(writer, "certificates feature is not enabled in headscale", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(ns.headscale.cfg.CertificatesFeatureConfig.SetDNSCommand, setDnsRequest.Name, setDnsRequest.Type, setDnsRequest.Value)
|
||||
cmd.Stdout = os.Stderr
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
|
||||
if err != nil {
|
||||
log.Error().AnErr("error", err).
|
||||
Strs("args", cmd.Args).
|
||||
Str("NodeKey", setDnsRequest.NodeKey.ShortString()).
|
||||
Str("DnsName", setDnsRequest.Name).
|
||||
Msg("Error running set_dns_command")
|
||||
http.Error(writer, "Failed to execute SetDNSCommand", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
resp := tailcfg.SetDNSResponse{}
|
||||
respBody, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Msg("Cannot encode message")
|
||||
http.Error(writer, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
_, err = writer.Write(respBody)
|
||||
if err != nil {
|
||||
log.Error().
|
||||
Caller().
|
||||
Err(err).
|
||||
Msg("Failed to write response")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -84,6 +84,8 @@ type Config struct {
|
|||
// it can be used directly when sending Netmaps to clients.
|
||||
TailcfgDNSConfig *tailcfg.DNSConfig
|
||||
|
||||
CertificatesFeatureConfig CertificatesFeatureConfig
|
||||
|
||||
UnixSocket string
|
||||
UnixSocketPermission fs.FileMode
|
||||
|
||||
|
@ -165,6 +167,11 @@ type LetsEncryptConfig struct {
|
|||
ChallengeType string
|
||||
}
|
||||
|
||||
type CertificatesFeatureConfig struct {
|
||||
Enabled bool
|
||||
SetDNSCommand string
|
||||
}
|
||||
|
||||
type PKCEConfig struct {
|
||||
Enabled bool
|
||||
Method string
|
||||
|
@ -273,6 +280,9 @@ func LoadConfig(path string, isFile bool) error {
|
|||
viper.SetDefault("tls_letsencrypt_cache_dir", "/var/www/.cache")
|
||||
viper.SetDefault("tls_letsencrypt_challenge_type", HTTP01ChallengeType)
|
||||
|
||||
viper.SetDefault("certificates.enabled", false)
|
||||
viper.SetDefault("certificates.set_dns_command", "")
|
||||
|
||||
viper.SetDefault("log.level", "info")
|
||||
viper.SetDefault("log.format", TextLogFormat)
|
||||
|
||||
|
@ -407,6 +417,10 @@ func validateServerConfig() error {
|
|||
errorText += "Fatal config error: the only supported values for tls_letsencrypt_challenge_type are HTTP-01 and TLS-ALPN-01\n"
|
||||
}
|
||||
|
||||
if (viper.GetBool("certificates.enabled") == true) && viper.GetString("certificates.set_dns_command") == "" {
|
||||
errorText += "Fatal config error: certificates.enabled is set to true, but certificates.set_dns_command is not set\n"
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(viper.GetString("server_url"), "http://") &&
|
||||
!strings.HasPrefix(viper.GetString("server_url"), "https://") {
|
||||
errorText += "Fatal config error: server_url must start with https:// or http://\n"
|
||||
|
@ -917,6 +931,11 @@ func LoadServerConfig() (*Config, error) {
|
|||
ACMEEmail: viper.GetString("acme_email"),
|
||||
ACMEURL: viper.GetString("acme_url"),
|
||||
|
||||
CertificatesFeatureConfig: CertificatesFeatureConfig{
|
||||
Enabled: viper.GetBool("certificates.enabled"),
|
||||
SetDNSCommand: os.ExpandEnv(viper.GetString("certificates.set_dns_command")),
|
||||
},
|
||||
|
||||
UnixSocket: viper.GetString("unix_socket"),
|
||||
UnixSocketPermission: util.GetFileMode("unix_socket_permission"),
|
||||
|
||||
|
|
Loading…
Reference in a new issue