@node Passwords
@unnumbered Password authentication
-Password authentication is required for packages uploading.
-You have to store your authentication data in @option{-passwd} file in
-following format:
+Password authentication is required for packages uploading. Passwords
+are dynamically changed through the FIFO file. You have to create it and
+use in @option{-passwd} option:
@example
-username:hashed-password
+$ mkfifo passwd
+$ gocheese -passwd passwd ...
@end example
-Empty lines and having @verb{|#|} at the beginning are skipped.
+Then you must feed it newline-separated records in following format:
+
+@example
+username:hashed-password
+@end example
-Supported hashing algorithms are:
+Where @code{hashed-password} is in one of following algorithms:
@table @asis
@item @url{https://www.argon2i.com/, Argon2i} (recommended one!)
To get Argon2i hashed-password you can use any of following tools:
+
@itemize
- @item go get @url{https://github.com/balakhonova/argon2i,
- github.com/balakhonova/argon2i} (Go)
+ @item @code{go get @url{https://github.com/balakhonova/argon2i,
+ github.com/balakhonova/argon2i}} (Go)
@item @url{https://github.com/p-h-c/phc-winner-argon2} (C)
@end itemize
+
Example user @code{foo} with password @code{bar} can have the
following password file entry:
You can use your operating system tools:
@example
-# BSD-based systems:
-$ echo -n "password" | sha256
-
-# GNU/Linux-based systems
-$ echo -n "password" | sha256sum
+$ echo -n "password" | `command -v sha256 || echo sha256sum`
@end example
Example user @code{foo} with password @code{bar} will have the
@end table
-You can refresh passwords by sending @code{SIGHUP} signal to the working daemon:
+To add or update password entry:
@example
-$ pkill -HUP gocheese
-$ kill -HUP `pidof gocheese`
-$ svc -h /var/service/gocheese
+$ echo foo:$sha256$... > passwd
+$ cat passwords.txt > passwd
@end example
-Before refreshing it's recommended to check @option{-passwd} file with
-@option{-passwd-check} option to prevent daemon failure.
+To delete login entry use empty password:
+
+@example
+$ echo foo: > passwd
+@end example
+
+You can also check you passwords file with:
+
+@example
+$ gocheese -passwd-check < passwords.txt
+$ echo $?
+@end example
)
const (
- Version = "2.6.0"
+ Version = "3.0.0"
HTMLBegin = `<!DOCTYPE html>
<html>
<head>
gpgUpdateURLPath = flag.String("gpgupdate", "/gpgupdate/", "GPG forceful refreshing URL path")
pypiURL = flag.String("pypi", "https://pypi.org/simple/", "Upstream (PyPI) URL")
pypiCertHash = flag.String("pypi-cert-hash", "", "Authenticate upstream by its X.509 certificate's SPKI SHA256 hash")
- passwdPath = flag.String("passwd", "passwd", "Path to file with authenticators")
logTimestamped = flag.Bool("log-timestamped", false, "Prepend timestmap to log messages")
- passwdCheck = flag.Bool("passwd-check", false, "Test the -passwd file for syntax errors and exit")
+ passwdPath = flag.String("passwd", "", "Path to FIFO for upload authentication")
+ passwdCheck = flag.Bool("passwd-check", false, "Run password checker")
fsck = flag.Bool("fsck", false, "Check integrity of all packages (errors are in stderr)")
maxClients = flag.Int("maxclients", 128, "Maximal amount of simultaneous clients")
version = flag.Bool("version", false, "Print version information")
}
if *passwdCheck {
- refreshPasswd()
- return
+ if passwdReader(os.Stdin) {
+ os.Exit(0)
+ } else {
+ os.Exit(1)
+ }
+ }
+
+ if *passwdPath != "" {
+ go func() {
+ for {
+ fd, err := os.OpenFile(*passwdPath, os.O_RDONLY, os.FileMode(0666))
+ if err != nil {
+ log.Fatalln(err)
+ }
+ passwdReader(fd)
+ fd.Close()
+ }
+ }()
}
if (*tlsCert != "" && *tlsKey == "") || (*tlsCert == "" && *tlsKey != "") {
if err != nil {
log.Fatalln(err)
}
- refreshPasswd()
tlsConfig := tls.Config{
ClientSessionCache: tls.NewLRUClientSessionCache(16),
NextProtos: []string{"h2", "http/1.1"},
http.HandleFunc(*gpgUpdateURLPath, handler)
}
- needsRefreshPasswd := make(chan os.Signal, 0)
needsShutdown := make(chan os.Signal, 0)
exitErr := make(chan error, 0)
- signal.Notify(needsRefreshPasswd, syscall.SIGHUP)
signal.Notify(needsShutdown, syscall.SIGTERM, syscall.SIGINT)
- go func() {
- for range needsRefreshPasswd {
- log.Println("refreshing passwords")
- refreshPasswd()
- }
- }()
go func(s *http.Server) {
<-needsShutdown
killed = true
package main
import (
+ "bufio"
"errors"
- "io/ioutil"
"log"
"os"
"strings"
+ "sync"
)
-var passwords map[string]Auther = make(map[string]Auther)
+var (
+ Passwords map[string]Auther = make(map[string]Auther)
+ PasswordsM sync.RWMutex
+)
type Auther interface {
Auth(password string) bool
return algorithm, auther, err
}
-func refreshPasswd() {
- passwd, err := ioutil.ReadFile(*passwdPath)
- if os.IsNotExist(err) {
- return
- }
- if err != nil {
- log.Fatal(err)
- }
- passwordsNew := make(map[string]Auther)
- for i, credentials := range strings.Split(strings.TrimRight(string(passwd), "\n"), "\n") {
- if len(credentials) == 0 || strings.HasPrefix(credentials, "#") {
+func passwdReader(fd *os.File) bool {
+ isGood := true
+ scanner := bufio.NewScanner(fd)
+ for scanner.Scan() {
+ t := scanner.Text()
+ if len(t) == 0 {
continue
}
- splitted := strings.Split(credentials, ":")
+ splitted := strings.Split(t, ":")
if len(splitted) != 2 {
- log.Fatalf("%s:%d: Wrong login:password format", *passwdPath, i)
+ log.Println("wrong login:password format:", t)
+ isGood = false
+ continue
}
login := splitted[0]
- if _, exists := passwordsNew[login]; exists {
- log.Fatalf("%s:%d: %s: already exists", *passwdPath, i, login)
+ passwd := splitted[1]
+ if passwd == "" {
+ log.Println("deleting login:", login)
+ PasswordsM.Lock()
+ delete(Passwords, login)
+ PasswordsM.Unlock()
+ continue
}
- _, auther, err := strToAuther(splitted[1])
+ _, auther, err := strToAuther(passwd)
if err != nil {
- log.Fatalf("%s:%d: %s: %s", *passwdPath, i, login, err)
+ log.Println("login:", login, "invalid password:", err)
+ isGood = false
+ continue
}
- passwordsNew[login] = auther
- log.Println("added password for:", login)
+ log.Println("adding password for:", login)
+ PasswordsM.Lock()
+ Passwords[login] = auther
+ PasswordsM.Unlock()
}
- passwords = passwordsNew
+ return isGood
}
http.Error(w, "unauthenticated", http.StatusUnauthorized)
return
}
- auther, ok := passwords[username]
+ PasswordsM.RLock()
+ auther, ok := Passwords[username]
+ PasswordsM.RUnlock()
if !ok || !auther.Auth(password) {
log.Println(r.RemoteAddr, "unauthenticated", username)
http.Error(w, "unauthenticated", http.StatusUnauthorized)