mirror of
https://github.com/juanfont/headscale.git
synced 2025-08-14 13:51:01 +02:00
Merge 16a76c9b96
into b2a18830ed
This commit is contained in:
commit
67c5ce6c9e
@ -274,6 +274,12 @@ dns:
|
||||
# The FQDN of the hosts will be
|
||||
# `hostname.base_domain` (e.g., _myhost.example.com_).
|
||||
base_domain: example.com
|
||||
tls_cert:
|
||||
type: hetzner
|
||||
hetzner:
|
||||
api_token: ""
|
||||
zone_id: ""
|
||||
ttl: 0
|
||||
|
||||
# Whether to use the local DNS settings of a node (default) or override the
|
||||
# local DNS settings and force the use of Headscale's DNS configuration.
|
||||
|
2
go.mod
2
go.mod
@ -24,6 +24,8 @@ require (
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.0
|
||||
github.com/jagottsicher/termcolor v1.0.2
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/libdns/hetzner v1.0.0
|
||||
github.com/libdns/libdns v1.1.0
|
||||
github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
|
||||
github.com/ory/dockertest/v3 v3.12.0
|
||||
github.com/philip-bui/grpc-zerolog v1.0.1
|
||||
|
4
go.sum
4
go.sum
@ -318,6 +318,10 @@ github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1
|
||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/libdns/hetzner v1.0.0 h1:dFcgqTIfdiKQTqoqBBtgU9CewD8JSnB7p6BKxQ5kheM=
|
||||
github.com/libdns/hetzner v1.0.0/go.mod h1:OmuTyXMHTfy2nCqbt9KYkf0KwQSvo0ZeFGxEQSl3r2w=
|
||||
github.com/libdns/libdns v1.1.0 h1:9ze/tWvt7Df6sbhOJRB8jT33GHEHpEQXdtkE3hPthbU=
|
||||
github.com/libdns/libdns v1.1.0/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ=
|
||||
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
|
@ -81,10 +81,11 @@ type Headscale struct {
|
||||
DERPServer *derpServer.DERPServer
|
||||
|
||||
// Things that generate changes
|
||||
extraRecordMan *dns.ExtraRecordsMan
|
||||
mapper *mapper.Mapper
|
||||
nodeNotifier *notifier.Notifier
|
||||
authProvider AuthProvider
|
||||
extraRecordMan *dns.ExtraRecordsMan
|
||||
mapper *mapper.Mapper
|
||||
nodeNotifier *notifier.Notifier
|
||||
authProvider AuthProvider
|
||||
tlsCertProvider TlsCertProvider
|
||||
|
||||
pollNetMapStreamWG sync.WaitGroup
|
||||
}
|
||||
@ -194,6 +195,10 @@ func NewHeadscale(cfg *types.Config) (*Headscale, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if app.cfg.DNSConfig.TlsCert.Type == types.TlsCertHetzner {
|
||||
app.tlsCertProvider = NewHetznerTlsCertProvider(app.cfg.DNSConfig.TlsCert.Hetzner)
|
||||
}
|
||||
|
||||
if cfg.DERP.ServerEnabled {
|
||||
derpServerKey, err := readOrCreatePrivateKey(cfg.DERP.ServerPrivateKeyPath)
|
||||
if err != nil {
|
||||
|
@ -126,10 +126,21 @@ func generateDNSConfig(
|
||||
dnsConfig := cfg.TailcfgDNSConfig.Clone()
|
||||
|
||||
addNextDNSMetadata(dnsConfig.Resolvers, node)
|
||||
addHostNameToCertDomains(dnsConfig, node)
|
||||
|
||||
return dnsConfig
|
||||
}
|
||||
|
||||
func addHostNameToCertDomains(dnsConfig *tailcfg.DNSConfig, node types.NodeView) {
|
||||
hostName := strings.ToLower(node.Hostname())
|
||||
for i, certDomain := range dnsConfig.CertDomains {
|
||||
prependedDomain := fmt.Sprintf("%s.%s", hostName, certDomain)
|
||||
if prependedDomain != certDomain {
|
||||
dnsConfig.CertDomains[i] = prependedDomain
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If any nextdns DoH resolvers are present in the list of resolvers it will
|
||||
// take metadata from the node metadata and instruct tailscale to add it
|
||||
// to the requests. This makes it possible to identify from which device the
|
||||
|
@ -101,6 +101,8 @@ func (h *Headscale) NoiseUpgradeHandler(
|
||||
router.HandleFunc("/machine/register", noiseServer.NoiseRegistrationHandler).
|
||||
Methods(http.MethodPost)
|
||||
|
||||
router.HandleFunc("/machine/set-dns", noiseServer.NoiseSetDnsHandler).
|
||||
Methods(http.MethodPost)
|
||||
// Endpoints outside of the register endpoint must use getAndValidateNode to
|
||||
// get the node to ensure that the MachineKey matches the Node setting up the
|
||||
// connection.
|
||||
@ -234,6 +236,55 @@ func regErr(err error) *tailcfg.RegisterResponse {
|
||||
return &tailcfg.RegisterResponse{Error: err.Error()}
|
||||
}
|
||||
|
||||
func (ns *noiseServer) NoiseSetDnsHandler(
|
||||
writer http.ResponseWriter,
|
||||
req *http.Request,
|
||||
) {
|
||||
if req.Method != http.MethodPost {
|
||||
httpError(writer, errMethodNotAllowed)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
setDnsRequest, setDnsResponse := func() (*tailcfg.SetDNSRequest, *tailcfg.SetDNSResponse) {
|
||||
var resp *tailcfg.SetDNSResponse
|
||||
|
||||
body, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return &tailcfg.SetDNSRequest{}, &tailcfg.SetDNSResponse{}
|
||||
}
|
||||
|
||||
var setDnsReq tailcfg.SetDNSRequest
|
||||
if err := json.Unmarshal(body, &setDnsReq); err != nil {
|
||||
return &setDnsReq, &tailcfg.SetDNSResponse{}
|
||||
}
|
||||
|
||||
ns.nodeKey = setDnsReq.NodeKey
|
||||
|
||||
resp, err = ns.headscale.handleSetDns(req.Context(), setDnsReq)
|
||||
if err != nil {
|
||||
return &setDnsReq, &tailcfg.SetDNSResponse{}
|
||||
}
|
||||
|
||||
return &setDnsReq, resp
|
||||
}()
|
||||
|
||||
// Reject unsupported versions
|
||||
if rejectUnsupported(writer, setDnsRequest.Version, ns.machineKey, setDnsRequest.NodeKey) {
|
||||
return
|
||||
}
|
||||
|
||||
respBody, err := json.Marshal(setDnsResponse)
|
||||
if err != nil {
|
||||
httpError(writer, err)
|
||||
return
|
||||
}
|
||||
|
||||
writer.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
writer.WriteHeader(http.StatusOK)
|
||||
writer.Write(respBody)
|
||||
}
|
||||
|
||||
// NoiseRegistrationHandler handles the actual registration process of a node.
|
||||
func (ns *noiseServer) NoiseRegistrationHandler(
|
||||
writer http.ResponseWriter,
|
||||
|
66
hscontrol/tlscert.go
Normal file
66
hscontrol/tlscert.go
Normal file
@ -0,0 +1,66 @@
|
||||
package hscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/juanfont/headscale/hscontrol/types"
|
||||
"github.com/libdns/hetzner"
|
||||
"github.com/libdns/libdns"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
type TlsCertProvider interface {
|
||||
GetRecordSetter() libdns.RecordSetter
|
||||
GetZoneName() string
|
||||
CreateRecord(request tailcfg.SetDNSRequest) libdns.Record
|
||||
}
|
||||
|
||||
func NewHetznerTlsCertProvider(config types.HetznerTlsCertConfig) *TlsCertProviderHetzner {
|
||||
return &TlsCertProviderHetzner{
|
||||
Provider: hetzner.New(config.ApiToken),
|
||||
ZoneId: config.ZoneId,
|
||||
ZoneName: config.ZoneName,
|
||||
Ttl: config.Ttl,
|
||||
}
|
||||
}
|
||||
|
||||
type TlsCertProviderHetzner struct {
|
||||
Provider libdns.RecordSetter
|
||||
ZoneId string
|
||||
ZoneName string
|
||||
Ttl int
|
||||
}
|
||||
|
||||
func (p *TlsCertProviderHetzner) GetRecordSetter() libdns.RecordSetter {
|
||||
return p.Provider
|
||||
}
|
||||
|
||||
func (p *TlsCertProviderHetzner) GetZoneName() string {
|
||||
return p.ZoneName
|
||||
}
|
||||
|
||||
func (p *TlsCertProviderHetzner) CreateRecord(request tailcfg.SetDNSRequest) libdns.Record {
|
||||
return &hetzner.Record{
|
||||
ZoneID: p.ZoneId,
|
||||
Type: request.Type,
|
||||
Name: request.Name,
|
||||
Value: request.Value,
|
||||
TTL: p.Ttl,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Headscale) handleSetDns(
|
||||
ctx context.Context,
|
||||
setDnsReq tailcfg.SetDNSRequest,
|
||||
) (*tailcfg.SetDNSResponse, error) {
|
||||
zoneName := h.tlsCertProvider.GetZoneName()
|
||||
recordSetter := h.tlsCertProvider.GetRecordSetter()
|
||||
libDnsRecord := h.tlsCertProvider.CreateRecord(setDnsReq)
|
||||
|
||||
_, err := recordSetter.SetRecords(ctx, zoneName, []libdns.Record{libDnsRecord})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &tailcfg.SetDNSResponse{}, nil
|
||||
}
|
@ -17,6 +17,7 @@ const (
|
||||
SelfUpdateIdentifier = "self-update"
|
||||
DatabasePostgres = "postgres"
|
||||
DatabaseSqlite = "sqlite3"
|
||||
TlsCertHetzner = "hetzner"
|
||||
)
|
||||
|
||||
var ErrCannotParsePrefix = errors.New("cannot parse prefix")
|
||||
|
@ -101,15 +101,29 @@ type Config struct {
|
||||
}
|
||||
|
||||
type DNSConfig struct {
|
||||
MagicDNS bool `mapstructure:"magic_dns"`
|
||||
BaseDomain string `mapstructure:"base_domain"`
|
||||
OverrideLocalDNS bool `mapstructure:"override_local_dns"`
|
||||
MagicDNS bool `mapstructure:"magic_dns"`
|
||||
BaseDomain string `mapstructure:"base_domain"`
|
||||
TlsCert TlsCertConfig `mapstructure:"tls_cert"`
|
||||
OverrideLocalDNS bool `mapstructure:"override_local_dns"`
|
||||
Nameservers Nameservers
|
||||
SearchDomains []string `mapstructure:"search_domains"`
|
||||
ExtraRecords []tailcfg.DNSRecord `mapstructure:"extra_records"`
|
||||
ExtraRecordsPath string `mapstructure:"extra_records_path"`
|
||||
}
|
||||
|
||||
type TlsCertConfig struct {
|
||||
// Type sets the tls cert type, one of "hetzner", ...
|
||||
Type string `mapstructure:"type"`
|
||||
Hetzner HetznerTlsCertConfig `mapstructure:"hetzner"`
|
||||
}
|
||||
|
||||
type HetznerTlsCertConfig struct {
|
||||
ApiToken string `mapstructure:"api_token"`
|
||||
ZoneId string `mapstructure:"zone_id"`
|
||||
ZoneName string `mapstructure:"zone_name"`
|
||||
Ttl int `mapstructure:"ttl"`
|
||||
}
|
||||
|
||||
type Nameservers struct {
|
||||
Global []string
|
||||
Split map[string][]string
|
||||
@ -639,6 +653,26 @@ func dns() (DNSConfig, error) {
|
||||
dns.SearchDomains = viper.GetStringSlice("dns.search_domains")
|
||||
dns.ExtraRecordsPath = viper.GetString("dns.extra_records_path")
|
||||
|
||||
if viper.IsSet("dns.tls_cert") {
|
||||
certType := viper.GetString("dns.tls_cert.type")
|
||||
if certType == TlsCertHetzner {
|
||||
zoneName := dns.BaseDomain
|
||||
if viper.IsSet("dns.tls_cert.hetzner.zone_name") {
|
||||
zoneName = viper.GetString("dns.tls_cert.hetzner.zone_name")
|
||||
}
|
||||
|
||||
dns.TlsCert = TlsCertConfig{
|
||||
Type: certType,
|
||||
Hetzner: HetznerTlsCertConfig{
|
||||
ApiToken: viper.GetString("dns.tls_cert.hetzner.api_token"),
|
||||
ZoneId: viper.GetString("dns.tls_cert.hetzner.zone_id"),
|
||||
ZoneName: zoneName,
|
||||
Ttl: viper.GetInt("dns.tls_cert.hetzner.ttl"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if viper.IsSet("dns.extra_records") {
|
||||
var extraRecords []tailcfg.DNSRecord
|
||||
|
||||
@ -749,6 +783,7 @@ func dnsToTailcfgDNS(dns DNSConfig) *tailcfg.DNSConfig {
|
||||
cfg.Routes = routes
|
||||
if dns.BaseDomain != "" {
|
||||
cfg.Domains = []string{dns.BaseDomain}
|
||||
cfg.CertDomains = cfg.Domains
|
||||
}
|
||||
cfg.Domains = append(cfg.Domains, dns.SearchDomains...)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user