mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-07-28 03:22:38 +03:00
This is needed in order to avoid issues similar to https://github.com/VictoriaMetrics/VictoriaLogs/issues/1419 ,
when the returned error is unwrapped in order to decide how to handle it.
See the fix for this issue at c38ca01c25
129 lines
3.8 KiB
Go
129 lines
3.8 KiB
Go
package flagutil
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"strings"
|
|
"sync/atomic"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fasttime"
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/fs/fscore"
|
|
)
|
|
|
|
// NewPassword returns new `password` flag with the given name and description.
|
|
//
|
|
// The password value is hidden when calling Password.String() for security reasons,
|
|
// since the returned value can be put in logs.
|
|
// Call Password.Get() for obtaining the real password value.
|
|
func NewPassword(name, description string) *Password {
|
|
description += fmt.Sprintf("\nFlag value can be read from the given file when using -%s=file:///abs/path/to/file or -%s=file://./relative/path/to/file."+
|
|
"\nFlag value can be read from the given http/https url when using -%s=http://host/path or -%s=https://host/path", name, name, name, name)
|
|
p := &Password{
|
|
flagname: name,
|
|
}
|
|
s := ""
|
|
p.value.Store(&s)
|
|
flag.Var(p, name, description)
|
|
return p
|
|
}
|
|
|
|
// Password is a flag holding a password.
|
|
//
|
|
// If the flag value is file:///path/to/file or http://host/path ,
|
|
// then its contents is automatically re-read from the given file or url
|
|
type Password struct {
|
|
nextRefreshTimestamp atomic.Uint64
|
|
|
|
value atomic.Pointer[string]
|
|
|
|
// flagname is the name of the flag
|
|
flagname string
|
|
|
|
// sourcePath contains either url or path to file with the password
|
|
sourcePath string
|
|
}
|
|
|
|
// Name returns the name of p flag.
|
|
func (p *Password) Name() string {
|
|
return p.flagname
|
|
}
|
|
|
|
// Get returns the current p value.
|
|
//
|
|
// It re-reads p value from the file:///path/to/file or http://host/path
|
|
// if they were passed to Password.Set.
|
|
func (p *Password) Get() string {
|
|
p.maybeRereadPassword()
|
|
sPtr := p.value.Load()
|
|
return *sPtr
|
|
}
|
|
|
|
func (p *Password) maybeRereadPassword() {
|
|
if p.sourcePath == "" {
|
|
// Fast path - nothing to re-read
|
|
return
|
|
}
|
|
tsCurr := fasttime.UnixTimestamp()
|
|
tsNext := p.nextRefreshTimestamp.Load()
|
|
if tsCurr < tsNext {
|
|
// Fast path - nothing to re-read
|
|
return
|
|
}
|
|
|
|
// Re-read password from p.sourcePath
|
|
p.nextRefreshTimestamp.Store(tsCurr + 2)
|
|
s, err := fscore.ReadPasswordFromFileOrHTTP(p.sourcePath)
|
|
if err != nil {
|
|
// cannot use lib/logger, since it can be uninitialized yet
|
|
log.Printf("flagutil: fall back to the previous password for -%s, since failed to re-read it from %q: %s\n", p.flagname, p.sourcePath, err)
|
|
} else {
|
|
p.value.Store(&s)
|
|
}
|
|
}
|
|
|
|
// String implements flag.Value interface.
|
|
func (p *Password) String() string {
|
|
return "secret"
|
|
}
|
|
|
|
// Set implements flag.Value interface.
|
|
func (p *Password) Set(value string) error {
|
|
p.nextRefreshTimestamp.Store(0)
|
|
switch {
|
|
case strings.HasPrefix(value, "file://"):
|
|
p.sourcePath = strings.TrimPrefix(value, "file://")
|
|
// Do not attempt to read the password from sourcePath now, since the file may not exist yet.
|
|
// The password will be read on the first access via Password.Get.
|
|
// Generate a random password for now in order to prevent from unauthorized access to protected resources
|
|
// while the sourcePath file doesn't exist.
|
|
p.initRandomValue()
|
|
return nil
|
|
case strings.HasPrefix(value, "http://"), strings.HasPrefix(value, "https://"):
|
|
p.sourcePath = value
|
|
// Do not attempt to read the password from sourcePath now, since the url may now exist yet.
|
|
// The password will be read on the first access via Password.Get.
|
|
// Generate a random password for now in order to prevent from unauthorized access to protected resources
|
|
// while the sourcePath file doesn't exist.
|
|
p.initRandomValue()
|
|
return nil
|
|
default:
|
|
p.sourcePath = ""
|
|
p.value.Store(&value)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (p *Password) initRandomValue() {
|
|
var buf [64]byte
|
|
_, err := io.ReadFull(rand.Reader, buf[:])
|
|
if err != nil {
|
|
// cannot use lib/logger here, since it can be uninitialized yet
|
|
panic(fmt.Errorf("FATAL: cannot read random data: %w", err))
|
|
}
|
|
s := string(buf[:])
|
|
p.value.Store(&s)
|
|
}
|