Compare commits

..

No commits in common. "master" and "v2" have entirely different histories.
master ... v2

18 changed files with 186 additions and 668 deletions

1
.gitignore vendored
View File

@ -1,3 +1,2 @@
config.json
backup
backup.json

View File

@ -84,7 +84,7 @@ func (a Addr) ValidSnapshots() ([]*ZfsSnapshot, error) {
}
}
func (a Addr) SetManaged(managed bool) error {
func (a Addr) SetManaged(val bool) error {
log.WithFields(log.Fields{"addr": a}).Debugf("starting")
defer log.WithFields(log.Fields{"addr": a}).Debugf("done")
@ -99,9 +99,9 @@ func (a Addr) SetManaged(managed bool) error {
} else {
fs.mx.Lock()
defer fs.mx.Unlock()
if fs.managed != managed {
if fs.managed != val {
var cmd string
if managed {
if val {
cmd = fmt.Sprintf("zfs set %s=+ %s", zfsManagedPropertyName, a.Path())
} else {
cmd = fmt.Sprintf("zfs set %s=- %s", zfsManagedPropertyName, a.Path())
@ -111,7 +111,7 @@ func (a Addr) SetManaged(managed bool) error {
return err
}
}
fs.managed = managed
fs.managed = val
return nil
}
}

View File

@ -3,7 +3,6 @@ package main
import (
"context"
"embed"
"fmt"
"html/template"
"io/fs"
"net/http"
@ -21,7 +20,6 @@ type AdminConfig struct {
Users []*User `json:"users"`
Secrets *SecretsConfig `json:"secrets"`
Addr string `json:"addr"`
URL string `json:"url"`
}
type SecretsConfig struct {
@ -44,7 +42,6 @@ func NewAdmin() *AdminConfig {
Addr: "0.0.0.0:8080",
Secrets: NewSecrets(),
Users: make([]*User, 0),
URL: "https://backup.example.com/",
}
return a
@ -77,6 +74,9 @@ func (a *AdminConfig) NewAdminUser() {
a.Users = append(a.Users, u)
log.WithFields(log.Fields{}).Warnf("Admin user password : %s", p)
return
}
func (a *AdminConfig) Run() {
@ -103,6 +103,12 @@ func (a *AdminConfig) Run() {
r.GET("/", HttpAnyIndex)
r.POST("/", HttpAnyIndex)
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.GET("/run", func(c *gin.Context) {
cfg.Run()
c.JSON(http.StatusOK, gin.H{
@ -110,29 +116,15 @@ func (a *AdminConfig) Run() {
})
})
r.GET("/save", func(c *gin.Context) {
if err := cfg.Save(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"message": "error",
})
} else {
c.JSON(http.StatusOK, gin.H{
"message": "done",
})
}
})
fsys, _ := fs.Sub(assets, "assets/static")
r.StaticFS("/assets", http.FS(fsys))
protected := r.Group("p", HttpAuth())
protected.GET("test", HttpAnyHome)
protected.GET("home", HttpAnyHome)
unprotected := r.Group("u", HttpNoAuth())
unprotected.GET("signin", HttpGetSignIn)
unprotected.POST("submit", HttpPostSubmit)
unprotected.GET("recover", HttpGetRecover)
unprotected.GET("signin", HttpAnySignIn)
unprotected.POST("signin", HttpAnySignIn)
srv := &http.Server{
Addr: a.Addr,
@ -168,15 +160,3 @@ func (a *AdminConfig) Run() {
}
}
func FindUserID(user string) (uint64, error) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
for _, v := range cfg.Admin.Users {
if v.Username == user {
return v.ID, nil
}
}
return 0, fmt.Errorf("no user")
}

13
app.go
View File

@ -478,7 +478,7 @@ func (a *App) Transfer() error {
defer log.WithFields(log.Fields{"app": a.name}).Debugf("done")
for _, src := range a.sources {
dests := make([]Addr, 0)
backedUp := false
for _, dest := range a.destinations {
dest2 := dest.Append("/" + src.Box() + "/" + src.Path())
if dest2.Online() {
@ -486,17 +486,18 @@ func (a *App) Transfer() error {
log.WithFields(log.Fields{"app": a.name, "call": "Mkdir", "attr": dest, "error": err}).Errorf("")
return err
}
if err := TransferZfs(src, dest2); err != nil {
log.WithFields(log.Fields{"app": a.name, "call": "TransferZfs", "src": src, "dest": dest, "error": err}).Errorf("")
return err
}
if err := dest2.SetManaged(true); err != nil {
log.WithFields(log.Fields{"app": a.name, "call": "SetManaged", "src": src, "dest": dest, "error": err}).Errorf("")
return err
}
dests = append(dests, dest2)
backedUp = true
}
}
if n, err := TransferZfs(src, dests); err != nil {
log.WithFields(log.Fields{"app": a.name, "call": "TransferZfs", "src": src, "dests": dests, "error": err}).Errorf("")
return err
} else if n > 0 {
if backedUp {
if err := src.SetBackedUp(true); err != nil {
log.WithFields(log.Fields{"app": a.name, "call": "SetBackedUp", "src": src, "error": err}).Errorf("")
return err

View File

@ -1,17 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Home :: zBackup</title>
<!-- Bootstrap core CSS -->
<link href="/assets/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="text-center">
<div class="container">
<h1 class="h3 mb-3 font-weight-normal">Logged in.</h1>
</div>
</body>
</html>

View File

@ -1,24 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<title>Recover :: zBackup</title>
<!-- Bootstrap core CSS -->
<link href="/assets/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="text-center">
<div class="container">
<form class="form-signin" method="POST" action="/u/submit">
<h1 class="h3 mb-3 font-weight-normal">Recover password</h1>
<div class="form-group">
<input type="username" class="form-control" id="username" placeholder="Username" name="username" required autofocus>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" >Recover</button>
<p class="mt-5 mb-3 text-muted">&copy; 2023-2024</p>
</form>
</div>
</body>
</html>

View File

@ -18,10 +18,10 @@
<input type="username" class="form-control" id="username" placeholder="Username" name="username" required autofocus>
</div>
<div class="form-group">
<input type="password" class="form-control" id="password" placeholder="Password" name="password" required>
<input type="password" class="form-control" id="pwd" placeholder="Password" name="password" required>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit" >Sign in</button>
<p class="mt-5 mb-3 text-muted">&copy; 2023-2024</p>
<p class="mt-5 mb-3 text-muted">&copy; 2023</p>
</form>
<a href="/u/recover">Lost password</a>
</div>

110
box.go
View File

@ -7,21 +7,31 @@ import (
"github.com/silenceper/pool"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
)
type Box struct {
name string
addr string
user string
key string
zfs *BoxZfs
sshPool pool.Pool
created bool
online bool
mx sync.Mutex
name string
addr string
user string
key string
zfs *BoxZfs
sshPool pool.Pool
created bool
online bool
allowDirectConnect bool
mx sync.Mutex
}
func (c *Config) NewBox(name, addr, user, key string) (b *Box, err error) {
type BoxSshPool struct {
signer ssh.Signer
config *ssh.ClientConfig
client *ssh.Client
logged bool
mx sync.Mutex
}
func (c *Config) NewBox(name, addr, user, key string, direct bool) (b *Box, err error) {
log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key}).Debugf("starting")
defer log.WithFields(log.Fields{"name": name, "addr": addr, "user": user, "key": key}).Debugf("done")
@ -46,9 +56,10 @@ func (c *Config) NewBox(name, addr, user, key string) (b *Box, err error) {
zfs: &BoxZfs{
online: false,
},
sshPool: p,
online: false,
created: true,
sshPool: p,
online: false,
created: true,
allowDirectConnect: true, //FIXME use direct
}
b.zfs.box = b
@ -128,34 +139,22 @@ func (b *Box) Exec(cmd string) (r string, err error) {
return s.Exec(cmd)
}
func TransferZfs(from Addr, to []Addr) (int, error) {
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("starting")
defer log.WithFields(log.Fields{"from": from, "to": to}).Debugf("done")
count := 0
for _, dest := range to {
if err := TransferDirectZfs(from, dest); err != nil {
log.WithFields(log.Fields{"from": from, "to": to, "call": "TransferDirectZfs", "attr": dest, "error": err}).Errorf("")
return count, err
} else {
count++
}
}
return count, nil
}
func TransferDirectZfs(from, to Addr) error {
func TransferZfs(from, to Addr) error {
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("starting")
defer log.WithFields(log.Fields{"from": from, "to": to}).Debugf("done")
var (
err error
fromSnapshots, toSnapshots []*ZfsSnapshot
directTransfer bool
)
if cfg.box[from.Box()].allowDirectConnect && cfg.box[to.Box()].allowDirectConnect {
directTransfer = true
} else {
directTransfer = false
}
if fromSnapshots, err = from.ValidSnapshots(); err != nil {
log.WithFields(log.Fields{"from": from, "to": to, "call": "ValidSnapshots", "attr": from, "error": err}).Errorf("")
return err
@ -172,30 +171,29 @@ func TransferDirectZfs(from, to Addr) error {
if len(toSnapshots) == 0 {
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("initiating destination")
if _, err := to.BoxExec("ssh " + from.Box() + " zfs send " + fromSnapshots[0].String() + " | zfs recv -F " + to.Path()); err != nil {
log.WithFields(log.Fields{"from": from, "to": to, "call": "BoxExec", "error": err}).Errorf("")
return err
if directTransfer {
if _, err := to.BoxExec("ssh " + from.Box() + " zfs send " + fromSnapshots[0].String() + " | zfs recv -F " + to.Path()); err != nil {
log.WithFields(log.Fields{"from": from, "to": to, "call": "BoxExec", "error": err}).Errorf("")
return err
}
newToSnapshot := &ZfsSnapshot{name: fromSnapshots[0].name, fs: cfg.box[to.Box()].zfs.filesystems[to.Path()]}
toSnapshots = append(toSnapshots, newToSnapshot)
cfg.box[to.Box()].zfs.filesystems[to.Path()].AddSnapshot(newToSnapshot)
} else {
//handle indirect transfer
}
newToSnapshot := &ZfsSnapshot{name: fromSnapshots[0].name, fs: cfg.box[to.Box()].zfs.filesystems[to.Path()]}
toSnapshots = append(toSnapshots, newToSnapshot)
cfg.box[to.Box()].zfs.filesystems[to.Path()].AddSnapshot(newToSnapshot)
}
fromFromSnapshotId := len(fromSnapshots) - 1
fromToSnapshotId := -1
for fromFromSnapshotId >= 0 {
fromToSnapshotId = len(toSnapshots) - 1
for fromToSnapshotId >= 0 {
if fromSnapshots[fromFromSnapshotId].name == toSnapshots[fromToSnapshotId].name {
break
}
fromToSnapshotId = fromToSnapshotId - 1
}
if fromToSnapshotId >= 0 {
fromFromSnapshotId := -1
lastToSnapshot := toSnapshots[len(toSnapshots)-1]
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("searching last snapshot %s", lastToSnapshot.String())
for id, v := range fromSnapshots {
if v.name == lastToSnapshot.name {
fromFromSnapshotId = id
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("found %s", v.String())
break
}
fromFromSnapshotId = fromFromSnapshotId - 1
}
if fromFromSnapshotId == -1 {
@ -206,9 +204,13 @@ func TransferDirectZfs(from, to Addr) error {
if fromFromSnapshotId < len(fromSnapshots)-1 {
log.WithFields(log.Fields{"from": from, "to": to}).Debugf("transfering from %s to %s", fromSnapshots[fromFromSnapshotId].name, fromSnapshots[len(fromSnapshots)-1].name)
if _, err := to.BoxExec("ssh " + from.Box() + " zfs send -I " + fromSnapshots[fromFromSnapshotId].String() + " " + fromSnapshots[len(fromSnapshots)-1].String() + " | zfs recv -F " + to.Path()); err != nil {
log.WithFields(log.Fields{"from": from, "to": to, "call": "BoxExec", "error": err}).Errorf("")
return err
if directTransfer {
if _, err := to.BoxExec("ssh " + from.Box() + " zfs send -I " + fromSnapshots[fromFromSnapshotId].String() + " " + fromSnapshots[len(fromSnapshots)-1].String() + " | zfs recv -F " + to.Path()); err != nil {
log.WithFields(log.Fields{"from": from, "to": to, "call": "BoxExec", "error": err}).Errorf("")
return err
}
} else {
// handle indirect transfer
}
for _, v := range fromSnapshots[fromFromSnapshotId+1:] {

View File

@ -13,19 +13,19 @@ import (
log "github.com/sirupsen/logrus"
"github.com/tailscale/hujson"
"github.com/tidwall/pretty"
)
type Config struct {
ScheduleDuration map[string]string `json:"schedule,omitempty"`
Box map[string]*BoxConfig `json:"box,omitempty"`
Email *EmailConfig `json:"email,omitempty"`
Apps []*AppConfig `json:"apps,omitempty"`
Timezone string `json:"timezone,omitempty"`
Admin *AdminConfig `json:"admin,omitempty"`
box map[string]*Box `json:"-"`
apps map[string]*App `json:"-"`
timezone *time.Location `json:"-"`
ScheduleDuration map[string]string `json:"schedule"`
Box map[string]BoxConfig `json:"box"`
Email EmailConfig `json:"email"`
Apps []AppConfig `json:"apps"`
Timezone string `json:"timezone"`
Admin *AdminConfig `json:"admin"`
Debug bool `json:"debug"`
box map[string]*Box `json:"-"`
apps map[string]*App `json:"-"`
timezone *time.Location `json:"-"`
}
var (
@ -37,9 +37,10 @@ var (
var sampleCfg []byte
type BoxConfig struct {
Addr string `json:"addr"`
User string `json:"user"`
Key string `json:"key"`
Addr string `json:"addr"`
User string `json:"user"`
Key string `json:"key"`
AllowDirectConnect bool `json:"allow_direct_connect"`
}
type AppConfig struct {
@ -47,9 +48,9 @@ type AppConfig struct {
Schedule []string `json:"schedule"`
Sources []string `json:"src"`
Destinations []string `json:"dest"`
Before map[string]string `json:"before,omitempty"`
After map[string]string `json:"after,omitempty"`
Active bool `json:"active,omitempty"`
Before map[string]string `json:"before"`
After map[string]string `json:"after"`
Active bool `json:"active"`
}
// Load config from file
@ -95,7 +96,7 @@ func LoadConfigByte(conf []byte) (*Config, error) {
return nil, err
}
if c.Email != nil {
if c.Email.Active {
if len(c.Email.SmtpHost) == 0 {
err := fmt.Errorf("no smtp")
log.WithFields(log.Fields{"error": err}).Errorf("")
@ -135,7 +136,7 @@ func LoadConfigByte(conf []byte) (*Config, error) {
c.box = make(map[string]*Box)
for k, v := range c.Box {
if b, err := c.NewBox(k, v.Addr, v.User, v.Key); err != nil {
if b, err := c.NewBox(k, v.Addr, v.User, v.Key, v.AllowDirectConnect); err != nil {
log.WithFields(log.Fields{"call": "NewBox", "attr": k, "error": err}).Errorf("")
return nil, err
} else {
@ -180,37 +181,6 @@ func LoadConfigByte(conf []byte) (*Config, error) {
return c, nil
}
// Save config
func (c *Config) Save() error {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
cfgMx.Lock()
defer cfgMx.Unlock()
b, err := json.Marshal(cfg)
if err != nil {
log.WithFields(log.Fields{"error": err, "call": "json.Marshal"}).Errorf("")
return err
}
r := pretty.PrettyOptions(b, &pretty.Options{Indent: " "})
f, err := os.Create(*cfgFile)
if err != nil {
log.WithFields(log.Fields{"error": err, "call": "os.Open"}).Errorf("")
return err
}
if _, err := f.Write(r); err != nil {
log.WithFields(log.Fields{"error": err, "call": "File.Write"}).Errorf("")
return err
}
return nil
}
// Run config
func (c *Config) Run() {
log.WithFields(log.Fields{}).Debugf("starting")
@ -312,4 +282,6 @@ func (c *Config) Run() {
log.WithFields(log.Fields{"call": "email.Send", "error": err}).Errorf("")
}
}
return
}

View File

@ -6,6 +6,7 @@ import (
"net/smtp"
"sort"
"strings"
"sync"
"time"
log "github.com/sirupsen/logrus"
@ -14,9 +15,11 @@ import (
type Email struct {
startTime time.Time
items []string
mx sync.Mutex
}
type EmailConfig struct {
Active bool `json:"active"`
SmtpHost string `json:"smtp"`
FromEmail string `json:"email_from"`
ToEmail []string `json:"email_to"`

43
go.mod
View File

@ -1,40 +1,13 @@
module git.siteop.biz/shoopea/backup
go 1.21
go 1.16
require (
github.com/gin-gonic/gin v1.9.1
github.com/robfig/cron/v3 v3.0.1
github.com/sethvargo/go-password v0.2.0
github.com/silenceper/pool v1.0.0
github.com/sirupsen/logrus v1.9.3
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a
github.com/tidwall/pretty v1.2.1
golang.org/x/crypto v0.29.0
)
require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/sethvargo/go-password v0.2.0 // indirect
github.com/silenceper/pool v1.0.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a // indirect
golang.org/x/crypto v0.9.0
)

73
go.sum
View File

@ -5,7 +5,6 @@ github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
@ -13,7 +12,6 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
@ -25,7 +23,6 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@ -33,6 +30,7 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
@ -45,7 +43,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
@ -53,6 +50,7 @@ github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
github.com/silenceper/pool v1.0.0 h1:JTCaA+U6hJAA0P8nCx+JfsRCHMwLTfatsm5QXelffmU=
github.com/silenceper/pool v1.0.0/go.mod h1:3DN13bqAbq86Lmzf6iUXWEPIWFPOSYVfaoceFvilKKI=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
@ -67,38 +65,77 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a h1:SJy1Pu0eH1C29XwJucQo73FrleVK6t4kYz4NVhp34Yw=
github.com/tailscale/hujson v0.0.0-20221223112325-20486734a56a/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM=
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

99
http.go
View File

@ -4,13 +4,9 @@ import (
"net/http"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
func HttpAuth() gin.HandlerFunc {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
return func(c *gin.Context) {
// Search signed-in userID
userID := 0
@ -18,44 +14,18 @@ func HttpAuth() gin.HandlerFunc {
// Return 404 and abort handlers chain.
c.String(http.StatusNotFound, "404 page not found")
c.AbortWithStatus(http.StatusNotFound)
return
}
}
}
func HttpNoAuth() gin.HandlerFunc {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
return func(c *gin.Context) {
return
}
}
func HttpPostSubmit(c *gin.Context) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
t, err := GetCSRFToken(c)
if err != nil {
c.String(http.StatusBadRequest, "bad token")
return
}
if !t.Valid() {
c.String(http.StatusBadRequest, "expired token")
return
}
switch t.GetPath() {
case "signin":
log.WithFields(log.Fields{"call": "Context.Request.ParseForm", "err": err}).Debugf("submit signin")
HttpSubmitSignIn(c)
HttpAnyIndex(c)
default:
log.WithFields(log.Fields{"call": "Context.Request.ParseForm", "err": err}).Debugf("submit %s", t.GetPath())
c.String(http.StatusBadRequest, "")
}
func HttpAnySignIn(c *gin.Context) {
if GetWebSessionUserID(c) > 0 {
c.Redirect(http.StatusTemporaryRedirect, "/p/home")
} else {
@ -66,83 +36,32 @@ func HttpPostSubmit(c *gin.Context) {
"Error": warning,
})
}
}
func HttpGetRecover(c *gin.Context) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
SetCSRFToken(c)
c.HTML(http.StatusOK, "page-recover.html", gin.H{})
}
func HttpGetSignIn(c *gin.Context) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
SetCSRFToken(c)
c.HTML(http.StatusOK, "page-signin.html", gin.H{})
return
}
func HttpAnyIndex(c *gin.Context) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
if GetWebSessionUserID(c) > 0 {
c.Redirect(http.StatusTemporaryRedirect, "/p/home")
} else {
c.Redirect(http.StatusTemporaryRedirect, "/u/signin")
}
return
}
func HttpAnyHome(c *gin.Context) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
if GetWebSessionUserID(c) == 0 {
c.Redirect(http.StatusTemporaryRedirect, "/u/signin")
} else {
SetCSRFToken(c)
c.HTML(http.StatusOK, "page-home.html", gin.H{})
}
return
}
func GetWebSessionUserID(c *gin.Context) uint64 {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
func GetWebSessionUserID(c *gin.Context) int64 {
return 0
}
func HttpSubmitSignIn(c *gin.Context) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
err := c.Request.ParseForm()
if err != nil {
c.SetCookie("warning", "Unable to parse form", 0, "/", cfg.Admin.URL, false, true)
log.WithFields(log.Fields{"call": "Context.Request.ParseForm", "err": err}).Debugf("")
return
}
username := c.Request.FormValue("username")
password := c.Request.FormValue("password")
userID, err := FindUserID(username)
if err != nil {
c.SetCookie("warning", "Invalid user or password", 0, "/", cfg.Admin.URL, false, true)
log.WithFields(log.Fields{"call": "FindUserID", "attr": username, "err": err}).Debugf("")
return
}
if !VerifyUserPassword(userID, password) {
c.SetCookie("warning", "Invalid user or password", 0, "/", cfg.Admin.URL, false, true)
log.WithFields(log.Fields{"call": "VerifyUserPassword", "attr": "***"}).Debugf("auth not ok")
return
}
t := NewSessionToken(userID)
c.SetCookie("session", t.Encode(), 9999999999, "/", cfg.Admin.URL, false, true)
c.SetCookie("warning", "", -1, "/", cfg.Admin.URL, false, true)
func SetCSRFToken(c *gin.Context) {
return
}

View File

@ -1,298 +0,0 @@
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"fmt"
"hash/crc32"
"time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
)
var sessionMap map[string]uint64
type SessionToken struct {
Identifier [32]byte
Verifier [32]byte
}
type CSRFToken struct {
Seed [4]byte
Path [16]byte
Time int64
Checksum uint32
}
func NewSessionToken(userID uint64) *SessionToken {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
identifier := make([]byte, 32)
_, err := rand.Read(identifier)
if err != nil {
log.WithFields(log.Fields{"call": "rand.Read", "attr": "identifier", "err": err}).Debugf("")
return &SessionToken{}
}
verifier := make([]byte, 32)
_, err = rand.Read(verifier)
if err != nil {
log.WithFields(log.Fields{"call": "rand.Read", "attr": "verifier", "err": err}).Debugf("")
return &SessionToken{}
}
hash := hmac.New(sha256.New, verifier)
hash.Write(verifier)
hashVerifier := hash.Sum(nil)
t := SessionToken{}
copy(t.Verifier[:], verifier[:32])
copy(t.Identifier[:], hashVerifier[:32])
return &t
}
func (t *SessionToken) Bytes() []byte {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
buf := new(bytes.Buffer)
_ = binary.Write(buf, binary.LittleEndian, t)
return buf.Bytes()
}
func (t *SessionToken) Load(b []byte) error {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
if len(b) != binary.Size(t) {
return fmt.Errorf("wrong size")
}
r := bytes.NewReader(b)
err := binary.Read(r, binary.LittleEndian, t)
if err != nil {
return err
}
return nil
}
func (t *SessionToken) GetSessionID() uint64 {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
if sessionID, ok := sessionMap[string(t.Identifier[:])]; !ok {
return 0
} else {
return sessionID
}
}
func GetSessionTokenParam(c *gin.Context) (*SessionToken, error) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
err := c.Request.ParseForm()
if err != nil {
log.WithFields(log.Fields{"call": "Context.Request.ParseForm", "err": err}).Errorf("")
return nil, err
}
session := c.Param("key")
return DecodeSessionToken(session)
}
func GetSessionTokenCookie(c *gin.Context) (*SessionToken, error) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
if session, err := c.Cookie("session"); err != nil {
log.WithFields(log.Fields{"call": "Context.Cookie", "attr": "session", "err": err}).Errorf("")
return nil, err
} else {
return DecodeSessionToken(session)
}
}
func (t *SessionToken) Encode() string {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
return base64.StdEncoding.EncodeToString(t.Bytes())
}
func DecodeSessionToken(s string) (*SessionToken, error) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
log.WithFields(log.Fields{"call": "base64.StdEncoding.DecodeString", "attr": "session", "err": err}).Errorf("")
return nil, err
}
t := SessionToken{}
err = t.Load(b)
if err != nil {
return nil, err
}
return &t, nil
}
func (t *CSRFToken) GetPath() string {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
buf := bytes.Buffer{}
for _, b := range t.Path {
if b > 0 {
buf.WriteByte(b)
} else {
break
}
}
return buf.String()
}
func (t *CSRFToken) Bytes() []byte {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
buf := new(bytes.Buffer)
_ = binary.Write(buf, binary.LittleEndian, t)
return buf.Bytes()
}
func (t *CSRFToken) Load(b []byte) error {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
if len(b) != binary.Size(t) {
return fmt.Errorf("wrong size")
}
r := bytes.NewReader(b)
err := binary.Read(r, binary.LittleEndian, t)
if err != nil {
log.WithFields(log.Fields{"call": "binary.Read", "attr": "b", "err": err}).Errorf("")
return err
}
chk := t.Checksum
t.Checksum = 0
if crc32.ChecksumIEEE(t.Bytes()) != chk {
return fmt.Errorf("wrong checksum")
}
t.Checksum = chk
return nil
}
func NewCSRFToken(c *gin.Context) *CSRFToken {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
t := CSRFToken{}
copy(t.Path[:], c.Request.URL.Path[0:])
t.Time = time.Now().UTC().Unix()
_, err := rand.Read(t.Seed[:])
log.WithFields(log.Fields{"call": "rand.Read", "attr": "seed", "err": err}).Errorf("")
t.Checksum = crc32.ChecksumIEEE(t.Bytes())
return &t
}
func GetCSRFToken(c *gin.Context) (*CSRFToken, error) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
if context, err := c.Cookie("context"); err != nil {
return nil, fmt.Errorf("no context param")
} else {
return DecodeCSRFToken(context)
}
}
func (t *CSRFToken) Encode() string {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
hash := sha256.New()
hash.Write([]byte(cfg.Admin.Secrets.ContextKey))
key := hash.Sum(nil)
block, _ := aes.NewCipher(key)
ciphertext := make([]byte, 32) // size of CSRFToken
if binary.Size(t) != 32 {
log.WithFields(log.Fields{"err": fmt.Errorf("size is wrong")}).Fatalf("")
}
iv := make([]byte, aes.BlockSize)
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext[:], t.Bytes())
return base64.StdEncoding.EncodeToString(ciphertext)
}
func DecodeCSRFToken(s string) (*CSRFToken, error) {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return nil, err
}
if len(b) != binary.Size(CSRFToken{}) {
return nil, fmt.Errorf("wrong size")
}
hash := sha256.New()
hash.Write([]byte(cfg.Admin.Secrets.ContextKey))
key := hash.Sum(nil)
block, err := aes.NewCipher(key)
iv := make([]byte, aes.BlockSize)
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(b, b)
t := CSRFToken{}
err = t.Load(b)
if err != nil {
return nil, err
}
return &t, nil
}
func (t *CSRFToken) Valid() bool {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
if time.Now().UTC().Sub(time.Unix(t.Time, 0)) > time.Duration(cfg.Admin.Secrets.ContextExpiration)*time.Second {
return false
}
return true
}
func SetCSRFToken(c *gin.Context) {
c.SetCookie("context", NewCSRFToken(c).Encode(), cfg.Admin.Secrets.ContextExpiration, "/", cfg.Admin.URL, false, true)
return
}

6
ssh.go
View File

@ -137,8 +137,8 @@ func (s *Ssh) ExecPipe(cmd string) error {
}
s.session = session
if err = s.session.Setenv("TZ", cfg.Timezone); err != nil {
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Setenv", "attr": cfg.Timezone, "error": err}).Errorf("")
if s.session.Setenv("TZ", cfg.Timezone); err != nil {
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Setenv", "error": err}).Errorf("")
return err
}
@ -161,7 +161,7 @@ func (s *Ssh) ExecPipe(cmd string) error {
}
if err = s.session.Start(cmd); err != nil {
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Start", "attr": cmd, "error": err}).Errorf("")
log.WithFields(log.Fields{"name": s.name, "cmd": cmd, "call": "session.Start", "error": err}).Errorf("")
s.session.Close()
return err
}

30
user.go
View File

@ -12,20 +12,16 @@ import (
)
type User struct {
ID uint64 `json:"id"`
Username string `json:"username"`
Salt string `json:"salt"`
Password string `json:"passwd"`
Email string `json:"email"`
Passwd string `json:"passwd"`
}
func NewUser(name, passwd string) (*User, error) {
log.WithFields(log.Fields{"name": name}).Debugf("starting")
defer log.WithFields(log.Fields{"name": name}).Debugf("done")
userID := uint64(1)
for _, v := range cfg.Admin.Users {
userID = max(v.ID+1, userID)
if v.Username == name {
err := errors.New("user already exists")
log.WithFields(log.Fields{"name": name, "error": err}).Errorf("")
@ -34,7 +30,6 @@ func NewUser(name, passwd string) (*User, error) {
}
u := &User{
ID: userID,
Username: name,
}
@ -49,7 +44,7 @@ func NewUser(name, passwd string) (*User, error) {
log.WithFields(log.Fields{"name": name, "call": "HashPassword", "error": err}).Errorf("")
return nil, err
} else {
u.Password = hex.EncodeToString(pass)
u.Passwd = hex.EncodeToString(pass)
}
return u, nil
@ -79,24 +74,3 @@ func (u *User) HashPassword(passwd string) ([]byte, error) {
}
}
func VerifyUserPassword(userID uint64, clearPass string) bool {
log.WithFields(log.Fields{}).Debugf("starting")
defer log.WithFields(log.Fields{}).Debugf("done")
var u *User
for _, v := range cfg.Admin.Users {
if v.ID == userID {
u = v
}
}
hashPass, err := u.HashPassword(clearPass)
if err != nil {
log.WithFields(log.Fields{"call": "user.HashPassword", "attr": "***", "error": err}).Errorf("")
return false
}
return hex.EncodeToString(hashPass) == u.Password
}

View File

@ -1,7 +1,7 @@
// Code generated by version.sh (@generated) DO NOT EDIT.
package main
var githash = "a31ff56"
var branch = "master"
var buildstamp = "2024-11-17_22:42:14"
var commits = "107"
var version = "a31ff56-b107 - 2024-11-17_22:42:14"
var githash = "1a1713e"
var branch = "v2"
var buildstamp = "2023-08-21_12:35:47"
var commits = "83"
var version = "1a1713e-b83 - 2023-08-21_12:35:47"

3
zfs.go
View File

@ -311,9 +311,6 @@ func (fs *ZfsFs) AddSnapshot(s *ZfsSnapshot) error {
}
func (fs *ZfsFs) ValidSnapshots() []*ZfsSnapshot {
log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path}).Debugf("starting")
defer log.WithFields(log.Fields{"box": fs.zfs.box.name, "fs": fs.path}).Debugf("done")
tab := make([]*ZfsSnapshot, 0)
for _, s := range fs.snapshots {