mirror of
https://github.com/Luzifer/twitch-bot.git
synced 2024-12-20 20:01:17 +00:00
[core] Add validation and reset of encrypted values
in order to detect change of encryption pass Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
7126f6d7b7
commit
3213f4ac37
4 changed files with 80 additions and 0 deletions
|
@ -209,6 +209,13 @@ func (s Service) ListPermittedChannels() ([]string, error) {
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Service) RemoveAllExtendedTwitchCredentials() error {
|
||||||
|
return errors.Wrap(
|
||||||
|
s.db.DB().Delete(&extendedPermission{}, "1 = 1").Error,
|
||||||
|
"deleting data from table",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func (s Service) RemoveExendedTwitchCredentials(channel string) error {
|
func (s Service) RemoveExendedTwitchCredentials(channel string) error {
|
||||||
return errors.Wrap(
|
return errors.Wrap(
|
||||||
s.db.DB().Delete(&extendedPermission{}, "channel = ?", strings.TrimLeft(channel, "#")).Error,
|
s.db.DB().Delete(&extendedPermission{}, "channel = ?", strings.TrimLeft(channel, "#")).Error,
|
||||||
|
|
17
main.go
17
main.go
|
@ -198,6 +198,7 @@ func handleSubCommand(args []string) {
|
||||||
fmt.Println(" actor-docs Generate markdown documentation for available actors")
|
fmt.Println(" actor-docs Generate markdown documentation for available actors")
|
||||||
fmt.Println(" api-token <name> <scope...> Generate an api-token to be entered into the config")
|
fmt.Println(" api-token <name> <scope...> Generate an api-token to be entered into the config")
|
||||||
fmt.Println(" migrate-v2 <old file> Migrate old (*.json.gz) storage file into new database")
|
fmt.Println(" migrate-v2 <old file> Migrate old (*.json.gz) storage file into new database")
|
||||||
|
fmt.Println(" reset-secrets Remove encrypted data to reset encryption passphrase")
|
||||||
fmt.Println(" validate-config Try to load configuration file and report errors if any")
|
fmt.Println(" validate-config Try to load configuration file and report errors if any")
|
||||||
fmt.Println(" help Prints this help message")
|
fmt.Println(" help Prints this help message")
|
||||||
|
|
||||||
|
@ -217,6 +218,18 @@ func handleSubCommand(args []string) {
|
||||||
|
|
||||||
log.Info("v2 storage file was migrated")
|
log.Info("v2 storage file was migrated")
|
||||||
|
|
||||||
|
case "reset-secrets":
|
||||||
|
// Nuke permission table entries
|
||||||
|
if err := accessService.RemoveAllExtendedTwitchCredentials(); err != nil {
|
||||||
|
log.WithError(err).Fatal("resetting Twitch credentials")
|
||||||
|
}
|
||||||
|
log.Info("removed stored Twitch credentials")
|
||||||
|
|
||||||
|
if err := db.ResetEncryptedCoreMeta(); err != nil {
|
||||||
|
log.WithError(err).Fatal("resetting encrypted meta entries")
|
||||||
|
}
|
||||||
|
log.Info("removed encrypted meta entries")
|
||||||
|
|
||||||
case "validate-config":
|
case "validate-config":
|
||||||
if err := loadConfig(cfg.Config); err != nil {
|
if err := loadConfig(cfg.Config); err != nil {
|
||||||
log.WithError(err).Fatal("loading config")
|
log.WithError(err).Fatal("loading config")
|
||||||
|
@ -303,6 +316,10 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = db.ValidateEncryption(); err != nil {
|
||||||
|
log.WithError(err).Fatal("validation of database encryption failed, fix encryption passphrase or use 'twitch-bot reset-secrets' to wipe encrypted data")
|
||||||
|
}
|
||||||
|
|
||||||
if err = loadConfig(cfg.Config); err != nil {
|
if err = loadConfig(cfg.Config); err != nil {
|
||||||
if os.IsNotExist(errors.Cause(err)) {
|
if os.IsNotExist(errors.Cause(err)) {
|
||||||
if err = writeDefaultConfigFile(cfg.Config); err != nil {
|
if err = writeDefaultConfigFile(cfg.Config); err != nil {
|
||||||
|
|
|
@ -2,12 +2,23 @@ package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/sha512"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/clause"
|
"gorm.io/gorm/clause"
|
||||||
|
|
||||||
|
"github.com/Luzifer/go_helpers/v2/backoff"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
encryptionValidationKey = "encryption-validation"
|
||||||
|
encryptionValidationMinBackoff = 500 * time.Millisecond
|
||||||
|
encryptionValidationTries = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -47,12 +58,55 @@ func (c connector) ReadEncryptedCoreMeta(key string, value any) error {
|
||||||
return c.readCoreMeta(key, value, c.DecryptField)
|
return c.readCoreMeta(key, value, c.DecryptField)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ResetEncryptedCoreMeta removes all CoreKV entries from the database
|
||||||
|
func (c connector) ResetEncryptedCoreMeta() error {
|
||||||
|
return errors.Wrap(
|
||||||
|
c.db.Delete(&coreKV{}, "value LIKE ?", "U2FsdGVkX1%").Error,
|
||||||
|
"removing encrypted meta entries",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// StoreEncryptedCoreMeta works like StoreCoreMeta but encrypts the
|
// StoreEncryptedCoreMeta works like StoreCoreMeta but encrypts the
|
||||||
// marshalled value before storing it
|
// marshalled value before storing it
|
||||||
func (c connector) StoreEncryptedCoreMeta(key string, value any) error {
|
func (c connector) StoreEncryptedCoreMeta(key string, value any) error {
|
||||||
return c.storeCoreMeta(key, value, c.EncryptField)
|
return c.storeCoreMeta(key, value, c.EncryptField)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c connector) ValidateEncryption() error {
|
||||||
|
validationHasher := sha512.New()
|
||||||
|
fmt.Fprint(validationHasher, c.encryptionSecret)
|
||||||
|
|
||||||
|
var (
|
||||||
|
storedHash string
|
||||||
|
validationHash = fmt.Sprintf("%x", validationHasher.Sum(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
err := backoff.NewBackoff().
|
||||||
|
WithMaxIterations(encryptionValidationTries).
|
||||||
|
WithMinIterationTime(encryptionValidationMinBackoff).
|
||||||
|
Retry(func() error {
|
||||||
|
return c.ReadEncryptedCoreMeta(encryptionValidationKey, &storedHash)
|
||||||
|
})
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case err == nil:
|
||||||
|
if storedHash != validationHash {
|
||||||
|
// Shouldn't happen: When decryption is possible it should match
|
||||||
|
return errors.New("mismatch between expected and stored hash")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case errors.Is(err, ErrCoreMetaNotFound):
|
||||||
|
return errors.Wrap(
|
||||||
|
c.StoreEncryptedCoreMeta(encryptionValidationKey, validationHash),
|
||||||
|
"initializing encryption validation",
|
||||||
|
)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return errors.Wrap(err, "reading encryption-validation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c connector) readCoreMeta(key string, value any, processor func(string) (string, error)) (err error) {
|
func (c connector) readCoreMeta(key string, value any, processor func(string) (string, error)) (err error) {
|
||||||
var data coreKV
|
var data coreKV
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,10 @@ type (
|
||||||
ReadCoreMeta(key string, value any) error
|
ReadCoreMeta(key string, value any) error
|
||||||
StoreCoreMeta(key string, value any) error
|
StoreCoreMeta(key string, value any) error
|
||||||
ReadEncryptedCoreMeta(key string, value any) error
|
ReadEncryptedCoreMeta(key string, value any) error
|
||||||
|
ResetEncryptedCoreMeta() error
|
||||||
StoreEncryptedCoreMeta(key string, value any) error
|
StoreEncryptedCoreMeta(key string, value any) error
|
||||||
DecryptField(string) (string, error)
|
DecryptField(string) (string, error)
|
||||||
EncryptField(string) (string, error)
|
EncryptField(string) (string, error)
|
||||||
|
ValidateEncryption() error
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue