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
|
# The FQDN of the hosts will be
|
||||||
# `hostname.base_domain` (e.g., _myhost.example.com_).
|
# `hostname.base_domain` (e.g., _myhost.example.com_).
|
||||||
base_domain: 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
|
# 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.
|
# 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/grpc-ecosystem/grpc-gateway/v2 v2.27.0
|
||||||
github.com/jagottsicher/termcolor v1.0.2
|
github.com/jagottsicher/termcolor v1.0.2
|
||||||
github.com/klauspost/compress v1.18.0
|
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/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25
|
||||||
github.com/ory/dockertest/v3 v3.12.0
|
github.com/ory/dockertest/v3 v3.12.0
|
||||||
github.com/philip-bui/grpc-zerolog v1.0.1
|
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.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
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 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
|
||||||
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
|
||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||||
|
@ -81,10 +81,11 @@ type Headscale struct {
|
|||||||
DERPServer *derpServer.DERPServer
|
DERPServer *derpServer.DERPServer
|
||||||
|
|
||||||
// Things that generate changes
|
// Things that generate changes
|
||||||
extraRecordMan *dns.ExtraRecordsMan
|
extraRecordMan *dns.ExtraRecordsMan
|
||||||
mapper *mapper.Mapper
|
mapper *mapper.Mapper
|
||||||
nodeNotifier *notifier.Notifier
|
nodeNotifier *notifier.Notifier
|
||||||
authProvider AuthProvider
|
authProvider AuthProvider
|
||||||
|
tlsCertProvider TlsCertProvider
|
||||||
|
|
||||||
pollNetMapStreamWG sync.WaitGroup
|
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 {
|
if cfg.DERP.ServerEnabled {
|
||||||
derpServerKey, err := readOrCreatePrivateKey(cfg.DERP.ServerPrivateKeyPath)
|
derpServerKey, err := readOrCreatePrivateKey(cfg.DERP.ServerPrivateKeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -126,10 +126,21 @@ func generateDNSConfig(
|
|||||||
dnsConfig := cfg.TailcfgDNSConfig.Clone()
|
dnsConfig := cfg.TailcfgDNSConfig.Clone()
|
||||||
|
|
||||||
addNextDNSMetadata(dnsConfig.Resolvers, node)
|
addNextDNSMetadata(dnsConfig.Resolvers, node)
|
||||||
|
addHostNameToCertDomains(dnsConfig, node)
|
||||||
|
|
||||||
return dnsConfig
|
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
|
// 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
|
// 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
|
// 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).
|
router.HandleFunc("/machine/register", noiseServer.NoiseRegistrationHandler).
|
||||||
Methods(http.MethodPost)
|
Methods(http.MethodPost)
|
||||||
|
|
||||||
|
router.HandleFunc("/machine/set-dns", noiseServer.NoiseSetDnsHandler).
|
||||||
|
Methods(http.MethodPost)
|
||||||
// Endpoints outside of the register endpoint must use getAndValidateNode to
|
// Endpoints outside of the register endpoint must use getAndValidateNode to
|
||||||
// get the node to ensure that the MachineKey matches the Node setting up the
|
// get the node to ensure that the MachineKey matches the Node setting up the
|
||||||
// connection.
|
// connection.
|
||||||
@ -234,6 +236,55 @@ func regErr(err error) *tailcfg.RegisterResponse {
|
|||||||
return &tailcfg.RegisterResponse{Error: err.Error()}
|
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.
|
// NoiseRegistrationHandler handles the actual registration process of a node.
|
||||||
func (ns *noiseServer) NoiseRegistrationHandler(
|
func (ns *noiseServer) NoiseRegistrationHandler(
|
||||||
writer http.ResponseWriter,
|
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"
|
SelfUpdateIdentifier = "self-update"
|
||||||
DatabasePostgres = "postgres"
|
DatabasePostgres = "postgres"
|
||||||
DatabaseSqlite = "sqlite3"
|
DatabaseSqlite = "sqlite3"
|
||||||
|
TlsCertHetzner = "hetzner"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrCannotParsePrefix = errors.New("cannot parse prefix")
|
var ErrCannotParsePrefix = errors.New("cannot parse prefix")
|
||||||
|
@ -101,15 +101,29 @@ type Config struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type DNSConfig struct {
|
type DNSConfig struct {
|
||||||
MagicDNS bool `mapstructure:"magic_dns"`
|
MagicDNS bool `mapstructure:"magic_dns"`
|
||||||
BaseDomain string `mapstructure:"base_domain"`
|
BaseDomain string `mapstructure:"base_domain"`
|
||||||
OverrideLocalDNS bool `mapstructure:"override_local_dns"`
|
TlsCert TlsCertConfig `mapstructure:"tls_cert"`
|
||||||
|
OverrideLocalDNS bool `mapstructure:"override_local_dns"`
|
||||||
Nameservers Nameservers
|
Nameservers Nameservers
|
||||||
SearchDomains []string `mapstructure:"search_domains"`
|
SearchDomains []string `mapstructure:"search_domains"`
|
||||||
ExtraRecords []tailcfg.DNSRecord `mapstructure:"extra_records"`
|
ExtraRecords []tailcfg.DNSRecord `mapstructure:"extra_records"`
|
||||||
ExtraRecordsPath string `mapstructure:"extra_records_path"`
|
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 {
|
type Nameservers struct {
|
||||||
Global []string
|
Global []string
|
||||||
Split map[string][]string
|
Split map[string][]string
|
||||||
@ -639,6 +653,26 @@ func dns() (DNSConfig, error) {
|
|||||||
dns.SearchDomains = viper.GetStringSlice("dns.search_domains")
|
dns.SearchDomains = viper.GetStringSlice("dns.search_domains")
|
||||||
dns.ExtraRecordsPath = viper.GetString("dns.extra_records_path")
|
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") {
|
if viper.IsSet("dns.extra_records") {
|
||||||
var extraRecords []tailcfg.DNSRecord
|
var extraRecords []tailcfg.DNSRecord
|
||||||
|
|
||||||
@ -749,6 +783,7 @@ func dnsToTailcfgDNS(dns DNSConfig) *tailcfg.DNSConfig {
|
|||||||
cfg.Routes = routes
|
cfg.Routes = routes
|
||||||
if dns.BaseDomain != "" {
|
if dns.BaseDomain != "" {
|
||||||
cfg.Domains = []string{dns.BaseDomain}
|
cfg.Domains = []string{dns.BaseDomain}
|
||||||
|
cfg.CertDomains = cfg.Domains
|
||||||
}
|
}
|
||||||
cfg.Domains = append(cfg.Domains, dns.SearchDomains...)
|
cfg.Domains = append(cfg.Domains, dns.SearchDomains...)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user