summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/zed/internal/storage
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/authzed/zed/internal/storage')
-rw-r--r--vendor/github.com/authzed/zed/internal/storage/config.go194
-rw-r--r--vendor/github.com/authzed/zed/internal/storage/secrets.go265
2 files changed, 459 insertions, 0 deletions
diff --git a/vendor/github.com/authzed/zed/internal/storage/config.go b/vendor/github.com/authzed/zed/internal/storage/config.go
new file mode 100644
index 0000000..1fca679
--- /dev/null
+++ b/vendor/github.com/authzed/zed/internal/storage/config.go
@@ -0,0 +1,194 @@
+package storage
+
+import (
+ "encoding/json"
+ "errors"
+ "io/fs"
+ "os"
+ "path/filepath"
+ "runtime"
+
+ "github.com/jzelinskie/stringz"
+)
+
+const configFileName = "config.json"
+
+// ErrConfigNotFound is returned if there is no Config in a ConfigStore.
+var ErrConfigNotFound = errors.New("config did not exist")
+
+// ErrTokenNotFound is returned if there is no Token in a ConfigStore.
+var ErrTokenNotFound = errors.New("token does not exist")
+
+// Config represents the contents of a zed configuration file.
+type Config struct {
+ Version string
+ CurrentToken string
+}
+
+// ConfigStore is anything that can persistently store a Config.
+type ConfigStore interface {
+ Get() (Config, error)
+ Put(Config) error
+ Exists() (bool, error)
+}
+
+// TokenWithOverride returns a Token that retrieves its values from the reference Token, and has its values overridden
+// any of the non-empty/non-nil values of the overrideToken.
+func TokenWithOverride(overrideToken Token, referenceToken Token) (Token, error) {
+ insecure := referenceToken.Insecure
+ if overrideToken.Insecure != nil {
+ insecure = overrideToken.Insecure
+ }
+
+ // done so that logging messages don't show nil for the resulting context
+ if insecure == nil {
+ bFalse := false
+ insecure = &bFalse
+ }
+
+ noVerifyCA := referenceToken.NoVerifyCA
+ if overrideToken.NoVerifyCA != nil {
+ noVerifyCA = overrideToken.NoVerifyCA
+ }
+
+ // done so that logging messages don't show nil for the resulting context
+ if noVerifyCA == nil {
+ bFalse := false
+ noVerifyCA = &bFalse
+ }
+
+ caCert := referenceToken.CACert
+ if overrideToken.CACert != nil {
+ caCert = overrideToken.CACert
+ }
+
+ return Token{
+ Name: referenceToken.Name,
+ Endpoint: stringz.DefaultEmpty(overrideToken.Endpoint, referenceToken.Endpoint),
+ APIToken: stringz.DefaultEmpty(overrideToken.APIToken, referenceToken.APIToken),
+ Insecure: insecure,
+ NoVerifyCA: noVerifyCA,
+ CACert: caCert,
+ }, nil
+}
+
+// CurrentToken is a convenient way to obtain the CurrentToken field from the
+// current Config.
+func CurrentToken(cs ConfigStore, ss SecretStore) (token Token, err error) {
+ cfg, err := cs.Get()
+ if err != nil {
+ return Token{}, err
+ }
+
+ return GetTokenIfExists(cfg.CurrentToken, ss)
+}
+
+// SetCurrentToken is a convenient way to set the CurrentToken field in a
+// the current config.
+func SetCurrentToken(name string, cs ConfigStore, ss SecretStore) error {
+ // Ensure the token exists
+ exists, err := TokenExists(name, ss)
+ if err != nil {
+ return err
+ }
+
+ if !exists {
+ return ErrTokenNotFound
+ }
+
+ cfg, err := cs.Get()
+ if err != nil {
+ if errors.Is(err, ErrConfigNotFound) {
+ cfg = Config{Version: "v1"}
+ } else {
+ return err
+ }
+ }
+
+ cfg.CurrentToken = name
+ return cs.Put(cfg)
+}
+
+// JSONConfigStore implements a ConfigStore that stores its Config in a JSON file at the provided ConfigPath.
+type JSONConfigStore struct {
+ ConfigPath string
+}
+
+// Enforce that our implementation satisfies the interface.
+var _ ConfigStore = JSONConfigStore{}
+
+// Get parses a Config from the filesystem.
+func (s JSONConfigStore) Get() (Config, error) {
+ cfgBytes, err := os.ReadFile(filepath.Join(s.ConfigPath, configFileName))
+ if errors.Is(err, fs.ErrNotExist) {
+ return Config{}, ErrConfigNotFound
+ } else if err != nil {
+ return Config{}, err
+ }
+
+ var cfg Config
+ if err := json.Unmarshal(cfgBytes, &cfg); err != nil {
+ return Config{}, err
+ }
+
+ return cfg, nil
+}
+
+// Put overwrites a Config on the filesystem.
+func (s JSONConfigStore) Put(cfg Config) error {
+ if err := os.MkdirAll(s.ConfigPath, 0o774); err != nil {
+ return err
+ }
+
+ cfgBytes, err := json.Marshal(cfg)
+ if err != nil {
+ return err
+ }
+
+ return atomicWriteFile(filepath.Join(s.ConfigPath, configFileName), cfgBytes, 0o774)
+}
+
+func (s JSONConfigStore) Exists() (bool, error) {
+ if _, err := os.Stat(filepath.Join(s.ConfigPath, configFileName)); errors.Is(err, fs.ErrNotExist) {
+ return false, nil
+ } else if err != nil {
+ return false, err
+ }
+ return true, nil
+}
+
+// atomicWriteFile writes data to filename+some suffix, then renames it into
+// filename.
+//
+// Copyright (c) 2019 Tailscale Inc & AUTHORS All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be found
+// at the following URL:
+// https://github.com/tailscale/tailscale/blob/main/LICENSE
+func atomicWriteFile(filename string, data []byte, perm os.FileMode) (err error) {
+ f, err := os.CreateTemp(filepath.Dir(filename), filepath.Base(filename)+".tmp")
+ if err != nil {
+ return err
+ }
+ tmpName := f.Name()
+ defer func() {
+ if err != nil {
+ f.Close()
+ os.Remove(tmpName)
+ }
+ }()
+ if _, err := f.Write(data); err != nil {
+ return err
+ }
+ if runtime.GOOS != "windows" {
+ if err := f.Chmod(perm); err != nil {
+ return err
+ }
+ }
+ if err := f.Sync(); err != nil {
+ return err
+ }
+ if err := f.Close(); err != nil {
+ return err
+ }
+ return os.Rename(tmpName, filename)
+}
diff --git a/vendor/github.com/authzed/zed/internal/storage/secrets.go b/vendor/github.com/authzed/zed/internal/storage/secrets.go
new file mode 100644
index 0000000..3436f6b
--- /dev/null
+++ b/vendor/github.com/authzed/zed/internal/storage/secrets.go
@@ -0,0 +1,265 @@
+package storage
+
+import (
+ "encoding/json"
+ "errors"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/99designs/keyring"
+ "github.com/charmbracelet/x/term"
+ "github.com/jzelinskie/stringz"
+
+ "github.com/authzed/zed/internal/console"
+)
+
+type Token struct {
+ Name string
+ Endpoint string
+ APIToken string
+ Insecure *bool
+ NoVerifyCA *bool
+ CACert []byte
+}
+
+func (t Token) AnyValue() bool {
+ if t.Endpoint != "" || t.APIToken != "" || t.Insecure != nil || t.NoVerifyCA != nil || len(t.CACert) > 0 {
+ return true
+ }
+
+ return false
+}
+
+func (t Token) Certificate() (cert []byte, ok bool) {
+ if len(t.CACert) > 0 {
+ return t.CACert, true
+ }
+ return nil, false
+}
+
+func (t Token) IsInsecure() bool {
+ return t.Insecure != nil && *t.Insecure
+}
+
+func (t Token) HasNoVerifyCA() bool {
+ return t.NoVerifyCA != nil && *t.NoVerifyCA
+}
+
+func (t Token) Redacted() string {
+ prefix, _ := t.SplitAPIToken()
+ if prefix == "" {
+ return "<redacted>"
+ }
+
+ return stringz.Join("_", prefix, "<redacted>")
+}
+
+func (t Token) SplitAPIToken() (prefix, secret string) {
+ exploded := strings.Split(t.APIToken, "_")
+ return strings.Join(exploded[:len(exploded)-1], "_"), exploded[len(exploded)-1]
+}
+
+type Secrets struct {
+ Tokens []Token
+}
+
+type SecretStore interface {
+ Get() (Secrets, error)
+ Put(s Secrets) error
+}
+
+// GetTokenIfExists returns an empty token if no token exists.
+func GetTokenIfExists(name string, ss SecretStore) (Token, error) {
+ secrets, err := ss.Get()
+ if err != nil {
+ return Token{}, err
+ }
+
+ for _, token := range secrets.Tokens {
+ if name == token.Name {
+ return token, nil
+ }
+ }
+
+ return Token{}, nil
+}
+
+func TokenExists(name string, ss SecretStore) (bool, error) {
+ secrets, err := ss.Get()
+ if err != nil {
+ return false, err
+ }
+
+ for _, token := range secrets.Tokens {
+ if name == token.Name {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
+
+func PutToken(t Token, ss SecretStore) error {
+ secrets, err := ss.Get()
+ if err != nil {
+ return err
+ }
+
+ replaced := false
+ for i, token := range secrets.Tokens {
+ if token.Name == t.Name {
+ secrets.Tokens[i] = t
+ replaced = true
+ }
+ }
+
+ if !replaced {
+ secrets.Tokens = append(secrets.Tokens, t)
+ }
+
+ return ss.Put(secrets)
+}
+
+func RemoveToken(name string, ss SecretStore) error {
+ secrets, err := ss.Get()
+ if err != nil {
+ return err
+ }
+
+ for i, token := range secrets.Tokens {
+ if token.Name == name {
+ secrets.Tokens = append(secrets.Tokens[:i], secrets.Tokens[i+1:]...)
+ break
+ }
+ }
+
+ return ss.Put(secrets)
+}
+
+type KeychainSecretStore struct {
+ ConfigPath string
+ ring keyring.Keyring
+}
+
+var _ SecretStore = (*KeychainSecretStore)(nil)
+
+const (
+ svcName = "zed"
+ keyringEntryName = svcName + " secrets"
+ envRecommendation = "Setting the environment variable `ZED_KEYRING_PASSWORD` to your password will skip prompts.\n"
+ keyringDoesNotExistPrompt = "Keyring file does not already exist.\nEnter a new non-empty passphrase for the new keyring file: "
+ keyringPrompt = "Enter passphrase to unlock zed keyring: "
+ emptyKeyringPasswordError = "your passphrase must not be empty"
+)
+
+func fileExists(path string) (bool, error) {
+ _, err := os.Stat(path)
+ switch {
+ case err == nil:
+ return true, nil
+ case os.IsNotExist(err):
+ return false, nil
+ default:
+ return false, err
+ }
+}
+
+func promptPassword(prompt string) (string, error) {
+ console.Printf(prompt)
+ b, err := term.ReadPassword(os.Stdin.Fd())
+ if err != nil {
+ return "", err
+ }
+ console.Printf("\n") // Clear the line after a prompt
+ return string(b), err
+}
+
+func (k *KeychainSecretStore) keyring() (keyring.Keyring, error) {
+ if k.ring != nil {
+ return k.ring, nil
+ }
+
+ keyringPath := filepath.Join(k.ConfigPath, "keyring.jwt")
+
+ ring, err := keyring.Open(keyring.Config{
+ ServiceName: "zed",
+ FileDir: keyringPath,
+ FilePasswordFunc: func(_ string) (string, error) {
+ if password, ok := os.LookupEnv("ZED_KEYRING_PASSWORD"); ok {
+ return password, nil
+ }
+
+ // Check if this is the first run where the keyring is created.
+ keyringExists, err := fileExists(filepath.Join(keyringPath, keyringEntryName))
+ if err != nil {
+ return "", err
+ }
+ if !keyringExists {
+ // This is the first run and we're creating a password.
+ passwordString, err := promptPassword(envRecommendation + keyringDoesNotExistPrompt)
+ if err != nil {
+ return "", err
+ }
+
+ if len(passwordString) == 0 {
+ // NOTE: we enforce a non-empty keyring password to prevent
+ // user frustration around accidentally setting an empty
+ // passphrase and then not knowing what it might be.
+ return "", errors.New(emptyKeyringPasswordError)
+ }
+
+ return passwordString, nil
+ }
+
+ passwordString, err := promptPassword(envRecommendation + keyringPrompt)
+ if err != nil {
+ return "", err
+ }
+
+ return passwordString, nil
+ },
+ })
+ if err != nil {
+ return ring, err
+ }
+
+ k.ring = ring
+ return ring, err
+}
+
+func (k *KeychainSecretStore) Get() (Secrets, error) {
+ ring, err := k.keyring()
+ if err != nil {
+ return Secrets{}, err
+ }
+
+ entry, err := ring.Get(keyringEntryName)
+ if err != nil {
+ if errors.Is(err, keyring.ErrKeyNotFound) {
+ return Secrets{}, nil // empty is okay!
+ }
+ return Secrets{}, err
+ }
+
+ var s Secrets
+ err = json.Unmarshal(entry.Data, &s)
+ return s, err
+}
+
+func (k *KeychainSecretStore) Put(s Secrets) error {
+ ring, err := k.keyring()
+ if err != nil {
+ return err
+ }
+
+ data, err := json.Marshal(s)
+ if err != nil {
+ return err
+ }
+
+ return ring.Set(keyring.Item{
+ Key: keyringEntryName,
+ Data: data,
+ })
+}