Cuenta de externas, arreglos de seguridad, títulos arriba y despliegue
- Rol «externas» para la asamblea de externas: pide sin cupo, siempre con el colectivo de fuera y su contacto, y sus solicitudes salen en naranja hasta que la asamblea de la Enre las confirma. - /pedir/ explica en una línea que los colectivos externos piden fecha en la asamblea de externas, los lunes de 19:00 a 20:00. - Seguridad: argon2 limitado a dos cálculos a la vez y el intento se cuenta antes de calcular (60 accesos simultáneos llevaban el proceso a 3,8 GB); bloqueo por cuenta a 20 fallos para que no se pueda dejar fuera a la asamblea; avisos por sesión en vez de por URL; sig sin «..»; eventos de 31 días como mucho; parámetros de argon2 acotados. - Títulos pegados arriba en todas las páginas; la franja de la enredadera recupera ese alto para seguir llenando la pantalla. - despliegue/: instalar.sh, cuenta.sh, servicio de systemd con el proceso encerrado, fragmento de nginx que además deja de servir .git y las notas internas, y configuración de ejemplo. Ninguna contraseña en el repositorio.
This commit is contained in:
parent
ce3127baff
commit
7d5dbeec60
24 changed files with 606 additions and 96 deletions
|
|
@ -26,6 +26,19 @@ const (
|
|||
argonSal = 16
|
||||
)
|
||||
|
||||
// argonHuecos limita cuántos cálculos de argon2 corren a la vez. Cada uno
|
||||
// reserva argonMemoria (64 MiB): sin este tope, sesenta intentos de acceso
|
||||
// simultáneos llevaban el proceso a casi 4 GB y podían tumbar la máquina, que
|
||||
// comparte con gitea y el resto de servicios de la Enre. Los que llegan de más
|
||||
// esperan su turno en vez de reservar memoria.
|
||||
var argonHuecos = make(chan struct{}, 2)
|
||||
|
||||
func argonID(clave, sal []byte, t, m uint32, p uint8, largo uint32) []byte {
|
||||
argonHuecos <- struct{}{}
|
||||
defer func() { <-argonHuecos }()
|
||||
return argon2.IDKey(clave, sal, t, m, p, largo)
|
||||
}
|
||||
|
||||
func aleatorio(n int) []byte {
|
||||
b := make([]byte, n)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
|
|
@ -40,7 +53,7 @@ func nuevoID() string { return hex.EncodeToString(aleatorio(16)) }
|
|||
// lleva dentro los parametros con los que se genero.
|
||||
func HashClave(clave string) string {
|
||||
sal := aleatorio(argonSal)
|
||||
h := argon2.IDKey([]byte(clave), sal, argonIteraciones, argonMemoria, argonHilos, argonLargo)
|
||||
h := argonID([]byte(clave), sal, argonIteraciones, argonMemoria, argonHilos, argonLargo)
|
||||
return fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
|
||||
argon2.Version, argonMemoria, argonIteraciones, argonHilos,
|
||||
base64.RawStdEncoding.EncodeToString(sal),
|
||||
|
|
@ -72,7 +85,13 @@ func VerificarClave(codificado, clave string) bool {
|
|||
if err != nil {
|
||||
return false
|
||||
}
|
||||
calculado := argon2.IDKey([]byte(clave), sal, uint32(iteraciones), uint32(memoria),
|
||||
// Los parámetros salen del propio hash, que escribimos nosotros; aun así se
|
||||
// acotan, para que un registro manipulado no pida gigas de memoria.
|
||||
if memoria <= 0 || memoria > argonMemoria || iteraciones <= 0 || iteraciones > 10 ||
|
||||
hilos <= 0 || hilos > 16 || len(esperado) == 0 || len(esperado) > 64 {
|
||||
return false
|
||||
}
|
||||
calculado := argonID([]byte(clave), sal, uint32(iteraciones), uint32(memoria),
|
||||
uint8(hilos), uint32(len(esperado)))
|
||||
return subtle.ConstantTimeCompare(calculado, esperado) == 1
|
||||
}
|
||||
|
|
@ -187,6 +206,27 @@ func (s *Sesiones) TomarClave(id string) (string, string) {
|
|||
return usuario, clave
|
||||
}
|
||||
|
||||
func (s *Sesiones) PonerAviso(id, aviso, fallo string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if ses, ok := s.m[id]; ok {
|
||||
ses.Aviso, ses.Error = aviso, fallo
|
||||
}
|
||||
}
|
||||
|
||||
// TomarAviso devuelve el aviso pendiente y lo borra: se enseña una vez.
|
||||
func (s *Sesiones) TomarAviso(id string) (string, string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
ses, ok := s.m[id]
|
||||
if !ok {
|
||||
return "", ""
|
||||
}
|
||||
aviso, fallo := ses.Aviso, ses.Error
|
||||
ses.Aviso, ses.Error = "", ""
|
||||
return aviso, fallo
|
||||
}
|
||||
|
||||
func (s *Sesiones) Borrar(id string) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
|
@ -234,29 +274,22 @@ func NuevoCerrojo(maxIntentos int, ventana, espera time.Duration) *Cerrojo {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Cerrojo) Bloqueado(claves ...string) (bool, time.Duration) {
|
||||
// Intentar comprueba y apunta el intento en la misma operación, antes de
|
||||
// calcular la clave. Si se apuntara después, como hacía Fallo, cien intentos
|
||||
// simultáneos pasaban todos la comprobación antes de que se apuntara el
|
||||
// primero.
|
||||
func (c *Cerrojo) Intentar(clave string) (bool, time.Duration) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
ahora := time.Now()
|
||||
for _, k := range claves {
|
||||
recientes := c.podar(k, ahora)
|
||||
if len(recientes) >= c.maxIntentos {
|
||||
ultimo := recientes[len(recientes)-1]
|
||||
if falta := c.espera - ahora.Sub(ultimo); falta > 0 {
|
||||
return true, falta
|
||||
}
|
||||
recientes := c.podar(clave, ahora)
|
||||
if len(recientes) >= c.maxIntentos {
|
||||
if falta := c.espera - ahora.Sub(recientes[len(recientes)-1]); falta > 0 {
|
||||
return false, falta
|
||||
}
|
||||
}
|
||||
return false, 0
|
||||
}
|
||||
|
||||
func (c *Cerrojo) Fallo(claves ...string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
ahora := time.Now()
|
||||
for _, k := range claves {
|
||||
c.fallos[k] = append(c.podar(k, ahora), ahora)
|
||||
}
|
||||
c.fallos[clave] = append(recientes, ahora)
|
||||
return true, 0
|
||||
}
|
||||
|
||||
func (c *Cerrojo) Limpiar(claves ...string) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue