Single Fetcher.Fetch on the Guard that holds DenyPrefixes. skipTlsVerification is ignored. No 502. keys add writes the secret only to --out. Release binary has no -test-allow-prefix.
156 lines
3.0 KiB
Go
156 lines
3.0 KiB
Go
package auth
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"crypto/subtle"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/pelletier/go-toml/v2"
|
|
)
|
|
|
|
type Caller struct {
|
|
Name string `toml:"name"`
|
|
Workspace string `toml:"workspace"`
|
|
KeySHA256 string `toml:"key_sha256"`
|
|
}
|
|
|
|
type file struct {
|
|
Caller []Caller `toml:"caller"`
|
|
}
|
|
|
|
// Store holds hashed keys. Reload on SIGHUP via Load.
|
|
type Store struct {
|
|
mu sync.RWMutex
|
|
path string
|
|
callers []Caller
|
|
}
|
|
|
|
func Load(path string) (*Store, error) {
|
|
s := &Store{path: path}
|
|
if err := s.reload(); err != nil {
|
|
return nil, err
|
|
}
|
|
return s, nil
|
|
}
|
|
|
|
func (s *Store) Reload() error { return s.reload() }
|
|
|
|
func (s *Store) reload() error {
|
|
b, err := os.ReadFile(s.path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var f file
|
|
if err := toml.Unmarshal(b, &f); err != nil {
|
|
return err
|
|
}
|
|
s.mu.Lock()
|
|
s.callers = f.Caller
|
|
s.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// LookupBearer returns the caller for a raw bearer token, or nil.
|
|
func (s *Store) LookupBearer(token string) *Caller {
|
|
if token == "" || s == nil {
|
|
return nil
|
|
}
|
|
sum := sha256.Sum256([]byte(token))
|
|
want := hex.EncodeToString(sum[:])
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
var found *Caller
|
|
for i := range s.callers {
|
|
got := strings.ToLower(strings.TrimSpace(s.callers[i].KeySHA256))
|
|
if len(got) != len(want) {
|
|
continue
|
|
}
|
|
if subtle.ConstantTimeCompare([]byte(got), []byte(want)) == 1 {
|
|
c := s.callers[i]
|
|
found = &c
|
|
}
|
|
}
|
|
return found
|
|
}
|
|
|
|
func HashKey(key string) string {
|
|
sum := sha256.Sum256([]byte(key))
|
|
return hex.EncodeToString(sum[:])
|
|
}
|
|
|
|
func GenerateKey() (string, error) {
|
|
var b [32]byte
|
|
if _, err := rand.Read(b[:]); err != nil {
|
|
return "", err
|
|
}
|
|
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
|
}
|
|
|
|
func Add(path, name, workspace, keyOut string) error {
|
|
if _, err := os.Stat(keyOut); err == nil {
|
|
return fmt.Errorf("refusing to overwrite %s", keyOut)
|
|
}
|
|
key, err := GenerateKey()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := os.WriteFile(keyOut, []byte(key+"\n"), 0o600); err != nil {
|
|
return err
|
|
}
|
|
var f file
|
|
if b, err := os.ReadFile(path); err == nil {
|
|
_ = toml.Unmarshal(b, &f)
|
|
}
|
|
f.Caller = append(f.Caller, Caller{Name: name, Workspace: workspace, KeySHA256: HashKey(key)})
|
|
out, err := toml.Marshal(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tmp := path + ".tmp"
|
|
if err := os.WriteFile(tmp, out, 0o640); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(tmp, path)
|
|
}
|
|
|
|
func List(path string) ([]Caller, error) {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var f file
|
|
if err := toml.Unmarshal(b, &f); err != nil {
|
|
return nil, err
|
|
}
|
|
return f.Caller, nil
|
|
}
|
|
|
|
func Revoke(path, name string) error {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var f file
|
|
if err := toml.Unmarshal(b, &f); err != nil {
|
|
return err
|
|
}
|
|
out := f.Caller[:0]
|
|
for _, c := range f.Caller {
|
|
if c.Name != name {
|
|
out = append(out, c)
|
|
}
|
|
}
|
|
f.Caller = out
|
|
raw, err := toml.Marshal(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return os.WriteFile(path, raw, 0o640)
|
|
}
|