ipn/store: add ability to store data as k8s secrets.

Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
Maisem Ali
2021-09-01 08:11:43 -07:00
committed by Maisem Ali
parent f53792026e
commit 0842e2f45b
11 changed files with 493 additions and 4 deletions

View File

@@ -613,9 +613,19 @@ func Run(ctx context.Context, logf logger.Logf, logid string, getEngine func() (
var store ipn.StateStore
if opts.StatePath != "" {
store, err = ipn.NewFileStore(opts.StatePath)
if err != nil {
return fmt.Errorf("ipn.NewFileStore(%q): %v", opts.StatePath, err)
const kubePrefix = "kube:"
switch {
case strings.HasPrefix(opts.StatePath, kubePrefix):
secretName := strings.TrimPrefix(opts.StatePath, kubePrefix)
store, err = ipn.NewKubeStore(secretName)
if err != nil {
return fmt.Errorf("ipn.NewKubeStore(%q): %v", secretName, err)
}
default:
store, err = ipn.NewFileStore(opts.StatePath)
if err != nil {
return fmt.Errorf("ipn.NewFileStore(%q): %v", opts.StatePath, err)
}
}
if opts.AutostartStateKey == "" {
autoStartKey, err := store.ReadState(ipn.ServerModeStartKey)

View File

@@ -6,6 +6,7 @@ package ipn
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
@@ -14,8 +15,10 @@ import (
"os"
"path/filepath"
"sync"
"time"
"tailscale.com/atomicfile"
"tailscale.com/kube"
)
// ErrStateNotExist is returned by StateStore.ReadState when the
@@ -55,6 +58,75 @@ type StateStore interface {
WriteState(id StateKey, bs []byte) error
}
// KubeStore is a StateStore that uses a Kubernetes Secret for persistence.
type KubeStore struct {
client *kube.Client
secretName string
}
// NewKubeStore returns a new KubeStore that persists to the named secret.
func NewKubeStore(secretName string) (*KubeStore, error) {
c, err := kube.New()
if err != nil {
return nil, err
}
return &KubeStore{
client: c,
secretName: secretName,
}, nil
}
func (s *KubeStore) String() string { return "KubeStore" }
// ReadState implements the StateStore interface.
func (s *KubeStore) ReadState(id StateKey) ([]byte, error) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
secret, err := s.client.GetSecret(ctx, s.secretName)
if err != nil {
if st, ok := err.(*kube.Status); ok && st.Code == 404 {
return nil, ErrStateNotExist
}
return nil, err
}
b, ok := secret.Data[string(id)]
if !ok {
return nil, ErrStateNotExist
}
return b, nil
}
// WriteState implements the StateStore interface.
func (s *KubeStore) WriteState(id StateKey, bs []byte) error {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
secret, err := s.client.GetSecret(ctx, s.secretName)
if err != nil {
if st, ok := err.(*kube.Status); ok && st.Code == 404 {
return s.client.CreateSecret(ctx, &kube.Secret{
TypeMeta: kube.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: kube.ObjectMeta{
Name: s.secretName,
},
Data: map[string][]byte{
string(id): bs,
},
})
}
return err
}
secret.Data[string(id)] = bs
if err := s.client.UpdateSecret(ctx, secret); err != nil {
return err
}
return err
}
// MemoryStore is a store that keeps state in memory only.
type MemoryStore struct {
mu sync.Mutex