mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-13 11:44:36 +00:00
d8c5d00ecb
Add a mode control for derp server, and add a "manual" mode to get derp server certificate. Under manual mode, certificate is searched in the directory given by "--cert-dir". Certificate should in PEM format, and use "hostname.{key,crt}" as filename. If no hostname is used, search by the hostname given for listen. Fixes #2794 Signed-off-by: SilverBut <SilverBut@users.noreply.github.com>
96 lines
2.7 KiB
Go
96 lines
2.7 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 main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"path/filepath"
|
|
"regexp"
|
|
|
|
"golang.org/x/crypto/acme/autocert"
|
|
)
|
|
|
|
var unsafeHostnameCharacters = regexp.MustCompile(`[^a-zA-Z0-9-\.]`)
|
|
|
|
type certProvider interface {
|
|
// TLSConfig creates a new TLS config suitable for net/http.Server servers.
|
|
TLSConfig() *tls.Config
|
|
// HTTPHandler handle ACME related request, if any.
|
|
HTTPHandler(fallback http.Handler) http.Handler
|
|
}
|
|
|
|
func certProviderByCertMode(mode, dir, hostname string) (certProvider, error) {
|
|
if dir == "" {
|
|
return nil, errors.New("missing required --certdir flag")
|
|
}
|
|
switch mode {
|
|
case "letsencrypt":
|
|
certManager := &autocert.Manager{
|
|
Prompt: autocert.AcceptTOS,
|
|
HostPolicy: autocert.HostWhitelist(hostname),
|
|
Cache: autocert.DirCache(dir),
|
|
}
|
|
if hostname == "derp.tailscale.com" {
|
|
certManager.HostPolicy = prodAutocertHostPolicy
|
|
certManager.Email = "security@tailscale.com"
|
|
}
|
|
return certManager, nil
|
|
case "manual":
|
|
return NewManualCertManager(dir, hostname)
|
|
default:
|
|
return nil, fmt.Errorf("unsupport cert mode: %q", mode)
|
|
}
|
|
}
|
|
|
|
type manualCertManager struct {
|
|
cert *tls.Certificate
|
|
hostname string
|
|
}
|
|
|
|
// NewManualCertManager returns a cert provider which read certificate by given hostname on create.
|
|
func NewManualCertManager(certdir, hostname string) (certProvider, error) {
|
|
keyname := unsafeHostnameCharacters.ReplaceAllString(hostname, "")
|
|
crtPath := filepath.Join(certdir, keyname+".crt")
|
|
keyPath := filepath.Join(certdir, keyname+".key")
|
|
cert, err := tls.LoadX509KeyPair(crtPath, keyPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can not load x509 key pair for hostname %q: %w", keyname, err)
|
|
}
|
|
// ensure hostname matches with the certificate
|
|
x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("can not load cert: %w", err)
|
|
}
|
|
if x509Cert.VerifyHostname(hostname) != nil {
|
|
return nil, errors.New("refuse to load cert: hostname mismatch with key")
|
|
}
|
|
return &manualCertManager{cert: &cert, hostname: hostname}, nil
|
|
}
|
|
|
|
func (m *manualCertManager) TLSConfig() *tls.Config {
|
|
return &tls.Config{
|
|
Certificates: nil,
|
|
NextProtos: []string{
|
|
"h2", "http/1.1", // enable HTTP/2
|
|
},
|
|
GetCertificate: m.getCertificate,
|
|
}
|
|
}
|
|
|
|
func (m *manualCertManager) getCertificate(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
|
if hi.ServerName != m.hostname {
|
|
return nil, fmt.Errorf("cert mismatch with hostname: %q", hi.ServerName)
|
|
}
|
|
return m.cert, nil
|
|
}
|
|
|
|
func (m *manualCertManager) HTTPHandler(fallback http.Handler) http.Handler {
|
|
return fallback
|
|
}
|