|
|
@@ -6,8 +6,8 @@ package kubestore
|
|
|
|
|
|
import (
|
|
|
"context"
|
|
|
+ "encoding/json"
|
|
|
"fmt"
|
|
|
- "log"
|
|
|
"net"
|
|
|
"net/http"
|
|
|
"os"
|
|
|
@@ -57,6 +57,8 @@ type Store struct {
|
|
|
certShareMode string // 'ro', 'rw', or empty
|
|
|
podName string
|
|
|
|
|
|
+ logf logger.Logf
|
|
|
+
|
|
|
// memory holds the latest tailscale state. Writes write state to a kube
|
|
|
// Secret and memory, Reads read from memory.
|
|
|
memory mem.Store
|
|
|
@@ -96,6 +98,7 @@ func newWithClient(logf logger.Logf, c kubeclient.Client, secretName string) (*S
|
|
|
canPatch: canPatch,
|
|
|
secretName: secretName,
|
|
|
podName: os.Getenv("POD_NAME"),
|
|
|
+ logf: logf,
|
|
|
}
|
|
|
if envknob.IsCertShareReadWriteMode() {
|
|
|
s.certShareMode = "rw"
|
|
|
@@ -113,11 +116,11 @@ func newWithClient(logf logger.Logf, c kubeclient.Client, secretName string) (*S
|
|
|
if err := s.loadCerts(context.Background(), sel); err != nil {
|
|
|
// We will attempt to again retrieve the certs from Secrets when a request for an HTTPS endpoint
|
|
|
// is received.
|
|
|
- log.Printf("[unexpected] error loading TLS certs: %v", err)
|
|
|
+ s.logf("[unexpected] error loading TLS certs: %v", err)
|
|
|
}
|
|
|
}
|
|
|
if s.certShareMode == "ro" {
|
|
|
- go s.runCertReload(context.Background(), logf)
|
|
|
+ go s.runCertReload(context.Background())
|
|
|
}
|
|
|
return s, nil
|
|
|
}
|
|
|
@@ -147,7 +150,7 @@ func (s *Store) WriteState(id ipn.StateKey, bs []byte) (err error) {
|
|
|
// of a Tailscale Kubernetes node's state Secret.
|
|
|
func (s *Store) WriteTLSCertAndKey(domain string, cert, key []byte) (err error) {
|
|
|
if s.certShareMode == "ro" {
|
|
|
- log.Printf("[unexpected] TLS cert and key write in read-only mode")
|
|
|
+ s.logf("[unexpected] TLS cert and key write in read-only mode")
|
|
|
}
|
|
|
if err := dnsname.ValidHostname(domain); err != nil {
|
|
|
return fmt.Errorf("invalid domain name %q: %w", domain, err)
|
|
|
@@ -258,11 +261,11 @@ func (s *Store) updateSecret(data map[string][]byte, secretName string) (err err
|
|
|
defer func() {
|
|
|
if err != nil {
|
|
|
if err := s.client.Event(ctx, eventTypeWarning, reasonTailscaleStateUpdateFailed, err.Error()); err != nil {
|
|
|
- log.Printf("kubestore: error creating tailscaled state update Event: %v", err)
|
|
|
+ s.logf("kubestore: error creating tailscaled state update Event: %v", err)
|
|
|
}
|
|
|
} else {
|
|
|
if err := s.client.Event(ctx, eventTypeNormal, reasonTailscaleStateUpdated, "Successfully updated tailscaled state Secret"); err != nil {
|
|
|
- log.Printf("kubestore: error creating tailscaled state Event: %v", err)
|
|
|
+ s.logf("kubestore: error creating tailscaled state Event: %v", err)
|
|
|
}
|
|
|
}
|
|
|
cancel()
|
|
|
@@ -342,17 +345,72 @@ func (s *Store) loadState() (err error) {
|
|
|
return ipn.ErrStateNotExist
|
|
|
}
|
|
|
if err := s.client.Event(ctx, eventTypeWarning, reasonTailscaleStateLoadFailed, err.Error()); err != nil {
|
|
|
- log.Printf("kubestore: error creating Event: %v", err)
|
|
|
+ s.logf("kubestore: error creating Event: %v", err)
|
|
|
}
|
|
|
return err
|
|
|
}
|
|
|
if err := s.client.Event(ctx, eventTypeNormal, reasonTailscaleStateLoaded, "Successfully loaded tailscaled state from Secret"); err != nil {
|
|
|
- log.Printf("kubestore: error creating Event: %v", err)
|
|
|
+ s.logf("kubestore: error creating Event: %v", err)
|
|
|
+ }
|
|
|
+ data, err := s.maybeStripAttestationKeyFromProfile(secret.Data)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("error attempting to strip attestation data from state Secret: %w", err)
|
|
|
}
|
|
|
- s.memory.LoadFromMap(secret.Data)
|
|
|
+ s.memory.LoadFromMap(data)
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
+// maybeStripAttestationKeyFromProfile removes the hardware attestation key
|
|
|
+// field from serialized Tailscale profile. This is done to recover from a bug
|
|
|
+// introduced in 1.92, where node-bound hardware attestation keys were added to
|
|
|
+// Tailscale states stored in Kubernetes Secrets.
|
|
|
+// See https://github.com/tailscale/tailscale/issues/18302
|
|
|
+// TODO(irbekrm): it would be good if we could somehow determine when we no
|
|
|
+// longer need to run this check.
|
|
|
+func (s *Store) maybeStripAttestationKeyFromProfile(data map[string][]byte) (map[string][]byte, error) {
|
|
|
+ prefsKey := extractPrefsKey(data)
|
|
|
+ prefsBytes, ok := data[prefsKey]
|
|
|
+ if !ok {
|
|
|
+ return data, nil
|
|
|
+ }
|
|
|
+ var prefs map[string]any
|
|
|
+ if err := json.Unmarshal(prefsBytes, &prefs); err != nil {
|
|
|
+ s.logf("[unexpected]: kube store: failed to unmarshal prefs data")
|
|
|
+ // don't error as in most cases the state won't have the attestation key
|
|
|
+ return data, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ config, ok := prefs["Config"].(map[string]any)
|
|
|
+ if !ok {
|
|
|
+ return data, nil
|
|
|
+ }
|
|
|
+ if _, hasKey := config["AttestationKey"]; !hasKey {
|
|
|
+ return data, nil
|
|
|
+ }
|
|
|
+ s.logf("kube store: found redundant attestation key, deleting")
|
|
|
+ delete(config, "AttestationKey")
|
|
|
+ prefsBytes, err := json.Marshal(prefs)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("[unexpected] kube store: failed to marshal profile after removing attestation key: %v", err)
|
|
|
+ }
|
|
|
+ data[prefsKey] = prefsBytes
|
|
|
+ if err := s.updateSecret(map[string][]byte{prefsKey: prefsBytes}, s.secretName); err != nil {
|
|
|
+ // don't error out - this might have been a temporary kube API server
|
|
|
+ // connection issue. The key will be removed from the in-memory cache
|
|
|
+ // and we'll retry updating the Secret on the next restart.
|
|
|
+ s.logf("kube store: error updating Secret after stripping AttestationKey: %v", err)
|
|
|
+ }
|
|
|
+ return data, nil
|
|
|
+}
|
|
|
+
|
|
|
+const currentProfileKey = "_current-profile"
|
|
|
+
|
|
|
+// extractPrefs returns the key at which Tailscale prefs are stored in the
|
|
|
+// provided Secret data.
|
|
|
+func extractPrefsKey(data map[string][]byte) string {
|
|
|
+ return string(data[currentProfileKey])
|
|
|
+}
|
|
|
+
|
|
|
// runCertReload relists and reloads all TLS certs for endpoints shared by this
|
|
|
// node from Secrets other than the state Secret to ensure that renewed certs get eventually loaded.
|
|
|
// It is not critical to reload a cert immediately after
|
|
|
@@ -361,7 +419,7 @@ func (s *Store) loadState() (err error) {
|
|
|
// Note that if shared certs are not found in memory on an HTTPS request, we
|
|
|
// do a Secret lookup, so this mechanism does not need to ensure that newly
|
|
|
// added Ingresses' certs get loaded.
|
|
|
-func (s *Store) runCertReload(ctx context.Context, logf logger.Logf) {
|
|
|
+func (s *Store) runCertReload(ctx context.Context) {
|
|
|
ticker := time.NewTicker(time.Hour * 24)
|
|
|
defer ticker.Stop()
|
|
|
for {
|
|
|
@@ -371,7 +429,7 @@ func (s *Store) runCertReload(ctx context.Context, logf logger.Logf) {
|
|
|
case <-ticker.C:
|
|
|
sel := s.certSecretSelector()
|
|
|
if err := s.loadCerts(ctx, sel); err != nil {
|
|
|
- logf("[unexpected] error reloading TLS certs: %v", err)
|
|
|
+ s.logf("[unexpected] error reloading TLS certs: %v", err)
|
|
|
}
|
|
|
}
|
|
|
}
|