3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-11-26 13:59:44 +01:00

Merge pull request #993 from slingamn/issue952_cloak_db.1

fix #952
This commit is contained in:
Shivaram Lingamneni 2020-05-08 13:11:48 -07:00 committed by GitHub
commit f2e6142136
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 129 additions and 76 deletions

View File

@ -110,8 +110,9 @@ server:
# already up and running is problematic).
casemapping: "precis"
# whether to look up user hostnames with reverse DNS
# (to suppress this for privacy purposes, use the ip-cloaking options below)
# whether to look up user hostnames with reverse DNS.
# (disabling this will expose user IPs instead of hostnames;
# to make IP/hostname information private, see the ip-cloaking section)
lookup-hostnames: true
# whether to confirm hostname lookups using "forward-confirmed reverse DNS", i.e., for
# any hostname returned from reverse DNS, resolve it back to an IP address and reject it
@ -230,19 +231,9 @@ server:
# whether to enable IP cloaking
enabled: false
# fake TLD at the end of the hostname, e.g., pwbs2ui4377257x8.oragono
netname: "oragono"
# secret key to prevent dictionary attacks against cloaked IPs
# any high-entropy secret is valid for this purpose:
# you MUST generate a new one for your installation.
# suggestion: use the output of `oragono mksecret`
# note that rotating this key will invalidate all existing ban masks.
secret: "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4"
# name of an environment variable to pull the secret from, for use with
# k8s secret distribution:
# secret-environment-variable: "ORAGONO_CLOAKING_SECRET"
# fake TLD at the end of the hostname, e.g., pwbs2ui4377257x8.irc
# you may want to use your network name here
netname: "irc"
# the cloaked hostname is derived only from the CIDR (most significant bits
# of the IP address), up to a configurable number of bits. this is the

View File

@ -407,7 +407,7 @@ func csUnregisterHandler(server *Server, client *Client, command string, params
expectedCode := utils.ConfirmationCode(info.Name, info.RegisteredAt)
if expectedCode != verificationCode {
csNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this channel will remove all stored channel attributes.$b")))
csNotice(rb, fmt.Sprintf(client.t("To confirm channel unregistration, type: /CS UNREGISTER %[1]s %[2]s"), channelKey, expectedCode))
csNotice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/CS UNREGISTER %s %s", channelKey, expectedCode)))
return
}

View File

@ -27,7 +27,7 @@ func cloakConfForTesting() CloakConfig {
config := CloakConfig{
Enabled: true,
Netname: "oragono",
Secret: "_BdVPWB5sray7McbFmeuJL996yaLgG4l9tEyficGXKg",
secret: "_BdVPWB5sray7McbFmeuJL996yaLgG4l9tEyficGXKg",
CidrLenIPv4: 32,
CidrLenIPv6: 64,
NumBits: 80,
@ -55,7 +55,7 @@ func TestCloakDeterminism(t *testing.T) {
assertEqual(config.ComputeCloak(v6ipdifferentcidr), "ccmptyrjwsxv4f4d.oragono", t)
// cloak values must be sensitive to changes in the secret key
config.Secret = "HJcXK4lLawxBE4-9SIdPji_21YiL3N5r5f5-SPNrGVY"
config.SetSecret("HJcXK4lLawxBE4-9SIdPji_21YiL3N5r5f5-SPNrGVY")
assertEqual(config.ComputeCloak(v4ip), "4khy3usk8mfu42pe.oragono", t)
assertEqual(config.ComputeCloak(v6mappedIP), "4khy3usk8mfu42pe.oragono", t)
assertEqual(config.ComputeCloak(v6ip), "mxpk3c83vdxkek9j.oragono", t)
@ -66,7 +66,7 @@ func TestCloakShortv4Cidr(t *testing.T) {
config := CloakConfig{
Enabled: true,
Netname: "oragono",
Secret: "_BdVPWB5sray7McbFmeuJL996yaLgG4l9tEyficGXKg",
secret: "_BdVPWB5sray7McbFmeuJL996yaLgG4l9tEyficGXKg",
CidrLenIPv4: 24,
CidrLenIPv6: 64,
NumBits: 60,

View File

@ -5,7 +5,6 @@ package cloaks
import (
"fmt"
"net"
"os"
"golang.org/x/crypto/sha3"
@ -15,23 +14,20 @@ import (
type CloakConfig struct {
Enabled bool
Netname string
Secret string
SecretEnvVar string `yaml:"secret-environment-variable"`
CidrLenIPv4 int `yaml:"cidr-len-ipv4"`
CidrLenIPv6 int `yaml:"cidr-len-ipv6"`
NumBits int `yaml:"num-bits"`
LegacySecretValue string `yaml:"secret"`
secret string
numBytes int
ipv4Mask net.IPMask
ipv6Mask net.IPMask
}
func (cloakConfig *CloakConfig) Initialize() {
if cloakConfig.SecretEnvVar != "" {
envSecret := os.Getenv(cloakConfig.SecretEnvVar)
if envSecret != "" {
cloakConfig.Secret = envSecret
}
if !cloakConfig.Enabled {
return
}
// sanity checks:
@ -52,15 +48,20 @@ func (cloakConfig *CloakConfig) Initialize() {
cloakConfig.ipv6Mask = net.CIDRMask(cloakConfig.CidrLenIPv6, 128)
}
func (cloakConfig *CloakConfig) SetSecret(secret string) {
cloakConfig.secret = secret
}
// simple cloaking algorithm: normalize the IP to its CIDR,
// then hash the resulting bytes with a secret key,
// then truncate to the desired length, b32encode, and append the fake TLD.
func (config *CloakConfig) ComputeCloak(ip net.IP) string {
if !config.Enabled {
return ""
} else if config.NumBits == 0 {
} else if config.NumBits == 0 || config.secret == "" {
return config.Netname
}
var masked net.IP
v4ip := ip.To4()
if v4ip != nil {
@ -70,9 +71,9 @@ func (config *CloakConfig) ComputeCloak(ip net.IP) string {
}
// SHA3(K || M):
// https://crypto.stackexchange.com/questions/17735/is-hmac-needed-for-a-sha-3-based-mac
input := make([]byte, len(config.Secret)+len(masked))
copy(input, config.Secret[:])
copy(input[len(config.Secret):], masked)
input := make([]byte, len(config.secret)+len(masked))
copy(input, config.secret[:])
copy(input[len(config.secret):], masked)
digest := sha3.Sum512(input)
b32digest := utils.B32Encoder.EncodeToString(digest[:config.numBytes])
return fmt.Sprintf("%s.%s", b32digest, config.Netname)

View File

@ -1114,9 +1114,6 @@ func LoadConfig(filename string) (config *Config, err error) {
config.Server.Cloaks.Initialize()
if config.Server.Cloaks.Enabled {
if config.Server.Cloaks.Secret == "" || config.Server.Cloaks.Secret == "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" {
return nil, fmt.Errorf("You must generate a new value of server.ip-cloaking.secret to enable cloaking")
}
if !utils.IsHostname(config.Server.Cloaks.Netname) {
return nil, fmt.Errorf("Invalid netname for cloaked hostnames: %s", config.Server.Cloaks.Netname)
}

View File

@ -23,7 +23,9 @@ const (
// 'version' of the database schema
keySchemaVersion = "db.version"
// latest schema of the db
latestDbSchema = "10"
latestDbSchema = "11"
keyCloakSecret = "crypto.cloak_secret"
)
type SchemaChanger func(*Config, *buntdb.Tx) error
@ -63,6 +65,7 @@ func initializeDB(path string) error {
err = store.Update(func(tx *buntdb.Tx) error {
// set schema version
tx.Set(keySchemaVersion, latestDbSchema, nil)
tx.Set(keyCloakSecret, utils.GenerateSecretKey(), nil)
return nil
})
@ -186,6 +189,21 @@ func UpgradeDB(config *Config) (err error) {
return err
}
func LoadCloakSecret(db *buntdb.DB) (result string) {
db.View(func(tx *buntdb.Tx) error {
result, _ = tx.Get(keyCloakSecret)
return nil
})
return
}
func StoreCloakSecret(db *buntdb.DB, secret string) {
db.Update(func(tx *buntdb.Tx) error {
tx.Set(keyCloakSecret, secret, nil)
return nil
})
}
func schemaChangeV1toV2(config *Config, tx *buntdb.Tx) error {
// == version 1 -> 2 ==
// account key changes and account.verified key bugfix.
@ -621,6 +639,17 @@ func schemaChangeV9ToV10(config *Config, tx *buntdb.Tx) error {
return nil
}
// #952: move the cloak secret into the database,
// generate a new one if necessary
func schemaChangeV10ToV11(config *Config, tx *buntdb.Tx) error {
cloakSecret := config.Server.Cloaks.LegacySecretValue
if cloakSecret == "" || cloakSecret == "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4" {
cloakSecret = utils.GenerateSecretKey()
}
_, _, err := tx.Set(keyCloakSecret, cloakSecret, nil)
return err
}
func init() {
allChanges := []SchemaChange{
{
@ -668,6 +697,11 @@ func init() {
TargetVersion: "10",
Changer: schemaChangeV9ToV10,
},
{
InitialVersion: "10",
TargetVersion: "11",
Changer: schemaChangeV10ToV11,
},
}
// build the index

View File

@ -713,7 +713,7 @@ func debugHandler(server *Server, client *Client, msg ircmsg.IrcMessage, rb *Res
}
code := utils.ConfirmationCode(server.name, server.ctime)
if len(msg.Params) == 1 || msg.Params[1] != code {
rb.Notice(fmt.Sprintf(client.t("To crash the server, issue the following command: /DEBUG CRASHSERVER %s"), code))
rb.Notice(fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/DEBUG CRASHSERVER %s", code)))
return false
}
server.logger.Error("server", fmt.Sprintf("DEBUG CRASHSERVER executed by operator %s", client.Oper().Name))

View File

@ -9,7 +9,10 @@ import (
"regexp"
"time"
"github.com/goshuirc/irc-go/ircfmt"
"github.com/oragono/oragono/irc/sno"
"github.com/oragono/oragono/irc/utils"
)
const (
@ -171,6 +174,19 @@ the offered vhosts, use /HOSTSERV OFFERLIST.`,
minParams: 1,
maxParams: 1,
},
"setcloaksecret": {
handler: hsSetCloakSecretHandler,
help: `Syntax: $bSETCLOAKSECRET$b <secret> [code]
SETCLOAKSECRET can be used to set or rotate the cloak secret. You should use
a cryptographically strong secret. To prevent accidental modification, a
verification code is required; invoking the command without a code will
display the necessary code.`,
helpShort: `$bSETCLOAKSECRET$b modifies the IP cloaking secret.`,
capabs: []string{"vhosts", "rehash"},
minParams: 1,
maxParams: 2,
},
}
)
@ -429,3 +445,15 @@ func hsTakeHandler(server *Server, client *Client, command string, params []stri
server.snomasks.Send(sno.LocalVhosts, fmt.Sprintf("Client %s (account %s) took vhost %s", client.Nick(), account, vhost))
}
}
func hsSetCloakSecretHandler(server *Server, client *Client, command string, params []string, rb *ResponseBuffer) {
secret := params[0]
expectedCode := utils.ConfirmationCode(secret, server.ctime)
if len(params) == 1 || params[1] != expectedCode {
hsNotice(rb, ircfmt.Unescape(client.t("$bWarning: changing the cloak secret will invalidate stored ban/invite/exception lists.$b")))
hsNotice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/HS SETCLOAKSECRET %s %s", secret, expectedCode)))
return
}
StoreCloakSecret(server.store, secret)
hsNotice(rb, client.t("Rotated the cloak secret; you must rehash or restart the server for it to take effect"))
}

View File

@ -905,11 +905,10 @@ func nsUnregisterHandler(server *Server, client *Client, command string, params
if expectedCode != verificationCode {
if erase {
nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: erasing this account will allow it to be re-registered; consider UNREGISTER instead.$b")))
nsNotice(rb, fmt.Sprintf(client.t("To confirm account erase, type: /NS ERASE %[1]s %[2]s"), accountName, expectedCode))
} else {
nsNotice(rb, ircfmt.Unescape(client.t("$bWarning: unregistering this account will remove its stored privileges.$b")))
nsNotice(rb, fmt.Sprintf(client.t("To confirm account unregistration, type: /NS UNREGISTER %[1]s %[2]s"), accountName, expectedCode))
}
nsNotice(rb, fmt.Sprintf(client.t("To confirm, run this command: %s"), fmt.Sprintf("/NS UNREGISTER %s %s", accountName, expectedCode)))
return
}

View File

@ -550,9 +550,34 @@ func (server *Server) applyConfig(config *Config) (err error) {
}
}
server.logger.Info("server", "Using datastore", config.Datastore.Path)
if initial {
if err := server.loadDatastore(config); err != nil {
return err
}
} else {
if config.Datastore.MySQL.Enabled && config.Datastore.MySQL != oldConfig.Datastore.MySQL {
server.historyDB.SetConfig(config.Datastore.MySQL)
}
}
// now that the datastore is initialized, we can load the cloak secret from it
// XXX this modifies config after the initial load, which is naughty,
// but there's no data race because we haven't done SetConfig yet
if config.Server.Cloaks.Enabled {
config.Server.Cloaks.SetSecret(LoadCloakSecret(server.store))
}
// activate the new config
server.SetConfig(config)
// load [dk]-lines, registered users and channels, etc.
if initial {
if err := server.loadFromDatastore(config); err != nil {
return err
}
}
// burst new and removed caps
addedCaps, removedCaps := config.Diff(oldConfig)
var capBurstSessions []*Session
@ -582,17 +607,6 @@ func (server *Server) applyConfig(config *Config) (err error) {
}
}
server.logger.Info("server", "Using datastore", config.Datastore.Path)
if initial {
if err := server.loadDatastore(config); err != nil {
return err
}
} else {
if config.Datastore.MySQL.Enabled && config.Datastore.MySQL != oldConfig.Datastore.MySQL {
server.historyDB.SetConfig(config.Datastore.MySQL)
}
}
server.setupPprofListener(config)
// set RPL_ISUPPORT
@ -702,10 +716,13 @@ func (server *Server) loadDatastore(config *Config) error {
db, err := OpenDatabase(config)
if err == nil {
server.store = db
return nil
} else {
return fmt.Errorf("Failed to open datastore: %s", err.Error())
}
}
func (server *Server) loadFromDatastore(config *Config) (err error) {
// load *lines (from the datastores)
server.logger.Debug("server", "Loading D/Klines")
server.loadDLines()

View File

@ -17,7 +17,6 @@ import (
"github.com/oragono/oragono/irc"
"github.com/oragono/oragono/irc/logger"
"github.com/oragono/oragono/irc/mkcerts"
"github.com/oragono/oragono/irc/utils"
"golang.org/x/crypto/bcrypt"
"golang.org/x/crypto/ssh/terminal"
)
@ -97,7 +96,6 @@ Usage:
oragono upgradedb [--conf <filename>] [--quiet]
oragono genpasswd [--conf <filename>] [--quiet]
oragono mkcerts [--conf <filename>] [--quiet]
oragono mksecret [--conf <filename>] [--quiet]
oragono run [--conf <filename>] [--quiet] [--smoke]
oragono -h | --help
oragono --version
@ -109,7 +107,7 @@ Options:
arguments, _ := docopt.ParseArgs(usage, nil, version)
// don't require a config file for genpasswd or mksecret
// don't require a config file for genpasswd
if arguments["genpasswd"].(bool) {
var password string
fd := int(os.Stdin.Fd())
@ -135,9 +133,6 @@ Options:
fmt.Println()
}
return
} else if arguments["mksecret"].(bool) {
fmt.Println(utils.GenerateSecretKey())
return
} else if arguments["mkcerts"].(bool) {
doMkcerts(arguments["--conf"].(string), arguments["--quiet"].(bool))
return

View File

@ -131,8 +131,9 @@ server:
# already up and running is problematic).
casemapping: "precis"
# whether to look up user hostnames with reverse DNS
# (to suppress this for privacy purposes, use the ip-cloaking options below)
# whether to look up user hostnames with reverse DNS.
# (disabling this will expose user IPs instead of hostnames;
# to make IP/hostname information private, see the ip-cloaking section)
lookup-hostnames: true
# whether to confirm hostname lookups using "forward-confirmed reverse DNS", i.e., for
# any hostname returned from reverse DNS, resolve it back to an IP address and reject it
@ -249,21 +250,11 @@ server:
# IP is not already known, it is infeasible to recover it from the cloaked name.
ip-cloaking:
# whether to enable IP cloaking
enabled: false
enabled: true
# fake TLD at the end of the hostname, e.g., pwbs2ui4377257x8.oragono
netname: "oragono"
# secret key to prevent dictionary attacks against cloaked IPs
# any high-entropy secret is valid for this purpose:
# you MUST generate a new one for your installation.
# suggestion: use the output of `oragono mksecret`
# note that rotating this key will invalidate all existing ban masks.
secret: "siaELnk6Kaeo65K3RCrwJjlWaZ-Bt3WuZ2L8MXLbNb4"
# name of an environment variable to pull the secret from, for use with
# k8s secret distribution:
# secret-environment-variable: "ORAGONO_CLOAKING_SECRET"
# fake TLD at the end of the hostname, e.g., pwbs2ui4377257x8.irc
# you may want to use your network name here
netname: "irc"
# the cloaked hostname is derived only from the CIDR (most significant bits
# of the IP address), up to a configurable number of bits. this is the