install go2rtc on bob
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Credentials
|
||||
|
||||
This module allows you to get variables:
|
||||
|
||||
- from custom storage (ex. config file)
|
||||
- from [credential files](https://systemd.io/CREDENTIALS/)
|
||||
- from environment variables
|
||||
@@ -0,0 +1,79 @@
|
||||
package creds
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
SetValue(name, value string) error
|
||||
GetValue(name string) (string, bool)
|
||||
}
|
||||
|
||||
var storage Storage
|
||||
|
||||
func SetStorage(s Storage) {
|
||||
storage = s
|
||||
}
|
||||
|
||||
func SetValue(name, value string) error {
|
||||
if storage == nil {
|
||||
return errors.New("credentials: storage not initialized")
|
||||
}
|
||||
if err := storage.SetValue(name, value); err != nil {
|
||||
return err
|
||||
}
|
||||
AddSecret(value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetValue(name string) (value string, ok bool) {
|
||||
value, ok = getValue(name)
|
||||
AddSecret(value)
|
||||
return
|
||||
}
|
||||
|
||||
func getValue(name string) (string, bool) {
|
||||
if storage != nil {
|
||||
if value, ok := storage.GetValue(name); ok {
|
||||
return value, true
|
||||
}
|
||||
}
|
||||
|
||||
if dir, ok := os.LookupEnv("CREDENTIALS_DIRECTORY"); ok {
|
||||
if value, _ := os.ReadFile(filepath.Join(dir, name)); value != nil {
|
||||
return strings.TrimSpace(string(value)), true
|
||||
}
|
||||
}
|
||||
|
||||
return os.LookupEnv(name)
|
||||
}
|
||||
|
||||
// ReplaceVars - support format ${CAMERA_PASSWORD} and ${RTSP_USER:admin}
|
||||
func ReplaceVars(data []byte) []byte {
|
||||
re := regexp.MustCompile(`\${([^}{]+)}`)
|
||||
return re.ReplaceAllFunc(data, func(match []byte) []byte {
|
||||
key := string(match[2 : len(match)-1])
|
||||
|
||||
var def string
|
||||
var defok bool
|
||||
|
||||
if i := strings.IndexByte(key, ':'); i > 0 {
|
||||
key, def = key[:i], key[i+1:]
|
||||
defok = true
|
||||
}
|
||||
|
||||
if value, ok := GetValue(key); ok {
|
||||
return []byte(value)
|
||||
}
|
||||
|
||||
if defok {
|
||||
return []byte(def)
|
||||
}
|
||||
|
||||
return match
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package creds
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
func AddSecret(value string) {
|
||||
if value == "" {
|
||||
return
|
||||
}
|
||||
|
||||
secretsMu.Lock()
|
||||
defer secretsMu.Unlock()
|
||||
|
||||
if slices.Contains(secrets, value) {
|
||||
return
|
||||
}
|
||||
|
||||
secrets = append(secrets, value)
|
||||
secretsReplacer = nil
|
||||
}
|
||||
|
||||
var secrets []string
|
||||
var secretsMu sync.Mutex
|
||||
var secretsReplacer *strings.Replacer
|
||||
var userinfoRegexp *regexp.Regexp
|
||||
|
||||
func getReplacer() *strings.Replacer {
|
||||
secretsMu.Lock()
|
||||
defer secretsMu.Unlock()
|
||||
|
||||
if secretsReplacer == nil {
|
||||
oldnew := make([]string, 0, 2*len(secrets))
|
||||
for _, s := range secrets {
|
||||
oldnew = append(oldnew, s, "***")
|
||||
}
|
||||
secretsReplacer = strings.NewReplacer(oldnew...)
|
||||
}
|
||||
|
||||
if userinfoRegexp == nil {
|
||||
userinfoRegexp = regexp.MustCompile(`://[` + userinfo + `]+@`)
|
||||
}
|
||||
|
||||
return secretsReplacer
|
||||
}
|
||||
|
||||
// Uniform Resource Identifier (URI)
|
||||
// https://datatracker.ietf.org/doc/html/rfc3986
|
||||
const (
|
||||
unreserved = `A-Za-z0-9-._~`
|
||||
subdelims = `!$&'()*+,;=`
|
||||
userinfo = unreserved + subdelims + `%:`
|
||||
)
|
||||
|
||||
func SecretString(s string) string {
|
||||
re := getReplacer()
|
||||
s = userinfoRegexp.ReplaceAllString(s, `://***@`)
|
||||
return re.Replace(s)
|
||||
}
|
||||
|
||||
func SecretWrite(w io.Writer, s string) (n int, err error) {
|
||||
re := getReplacer()
|
||||
s = userinfoRegexp.ReplaceAllString(s, `://***@`)
|
||||
return re.WriteString(w, s)
|
||||
}
|
||||
|
||||
func SecretWriter(w io.Writer) io.Writer {
|
||||
return &secretWriter{w}
|
||||
}
|
||||
|
||||
type secretWriter struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
func (s *secretWriter) Write(b []byte) (int, error) {
|
||||
return SecretWrite(s.w, string(b))
|
||||
}
|
||||
|
||||
func SecretResponse(w http.ResponseWriter) http.ResponseWriter {
|
||||
return &secretResponse{w}
|
||||
}
|
||||
|
||||
type secretResponse struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (s *secretResponse) Write(b []byte) (int, error) {
|
||||
return SecretWrite(s.ResponseWriter, string(b))
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package creds
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
AddSecret("admin")
|
||||
AddSecret("pa$$word")
|
||||
|
||||
s := SecretString("rtsp://admin:pa$$word@192.168.1.123/stream1")
|
||||
require.Equal(t, "rtsp://***:***@192.168.1.123/stream1", s)
|
||||
}
|
||||
Reference in New Issue
Block a user