cmd/derper: add GCP Certificate Manager support (#18161)
Add --certmode=gcp for using Google Cloud Certificate Manager's public CA instead of Let's Encrypt. GCP requires External Account Binding (EAB) credentials for ACME registration, so this adds --acme-eab-kid and --acme-eab-key flags. The EAB key accepts both base64url and standard base64 encoding to support both ACME spec format and gcloud output. Fixes tailscale/corp#34881 Signed-off-by: Raj Singh <raj@tailscale.com> Co-authored-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
+34
-2
@@ -11,6 +11,7 @@ import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
@@ -24,6 +25,7 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"tailscale.com/tailcfg"
|
||||
)
|
||||
@@ -42,17 +44,33 @@ type certProvider interface {
|
||||
HTTPHandler(fallback http.Handler) http.Handler
|
||||
}
|
||||
|
||||
func certProviderByCertMode(mode, dir, hostname string) (certProvider, error) {
|
||||
func certProviderByCertMode(mode, dir, hostname, eabKID, eabKey string) (certProvider, error) {
|
||||
if dir == "" {
|
||||
return nil, errors.New("missing required --certdir flag")
|
||||
}
|
||||
switch mode {
|
||||
case "letsencrypt":
|
||||
case "letsencrypt", "gcp":
|
||||
certManager := &autocert.Manager{
|
||||
Prompt: autocert.AcceptTOS,
|
||||
HostPolicy: autocert.HostWhitelist(hostname),
|
||||
Cache: autocert.DirCache(dir),
|
||||
}
|
||||
if mode == "gcp" {
|
||||
if eabKID == "" || eabKey == "" {
|
||||
return nil, errors.New("--certmode=gcp requires --acme-eab-kid and --acme-eab-key flags")
|
||||
}
|
||||
keyBytes, err := decodeEABKey(eabKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
certManager.Client = &acme.Client{
|
||||
DirectoryURL: "https://dv.acme-v02.api.pki.goog/directory",
|
||||
}
|
||||
certManager.ExternalAccountBinding = &acme.ExternalAccountBinding{
|
||||
KID: eabKID,
|
||||
Key: keyBytes,
|
||||
}
|
||||
}
|
||||
if hostname == "derp.tailscale.com" {
|
||||
certManager.HostPolicy = prodAutocertHostPolicy
|
||||
certManager.Email = "security@tailscale.com"
|
||||
@@ -209,3 +227,17 @@ func createSelfSignedIPCert(crtPath, keyPath, ipStr string) (*tls.Certificate, e
|
||||
}
|
||||
return &tlsCert, nil
|
||||
}
|
||||
|
||||
// decodeEABKey decodes a base64-encoded EAB key.
|
||||
// It accepts both standard base64 (with padding) and base64url (without padding).
|
||||
func decodeEABKey(s string) ([]byte, error) {
|
||||
// Try base64url first (no padding), then standard base64 (with padding).
|
||||
// This handles both ACME spec format and gcloud output format.
|
||||
if b, err := base64.RawURLEncoding.DecodeString(s); err == nil {
|
||||
return b, nil
|
||||
}
|
||||
if b, err := base64.StdEncoding.DecodeString(s); err == nil {
|
||||
return b, nil
|
||||
}
|
||||
return nil, errors.New("invalid base64 encoding for EAB key")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user