@ -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. Print f( "[unexpected] error loading TLS certs: %v" , err )
s. log f( "[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. Print f( "[unexpected] TLS cert and key write in read-only mode" )
s. log f( "[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. Print f( "kubestore: error creating tailscaled state update Event: %v" , err )
s. log f( "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. Print f( "kubestore: error creating tailscaled state Event: %v" , err )
s. log f( "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. Print f( "kubestore: error creating Event: %v" , err )
s. log f( "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 ( d ata)
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 )
}
}
}