summaryrefslogtreecommitdiff
path: root/vendor/github.com/authzed/zed/internal/storage/config.go
blob: 1fca6792e0bb4d4ccf99559f9dc031e78894913b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
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)
}