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)
}
|