Просмотр исходного кода

Docs and minor improvements to "ntfy access"

Philipp Heckel 4 лет назад
Родитель
Сommit
5cf92c55c6
7 измененных файлов с 118 добавлено и 32 удалено
  1. 2 0
      .goreleaser.yml
  2. 1 0
      auth/auth.go
  3. 49 12
      auth/auth_sqlite.go
  4. 41 9
      cmd/access.go
  5. 3 3
      cmd/serve.go
  6. 2 2
      scripts/postinst.sh
  7. 20 6
      server/server.yml

+ 2 - 0
.goreleaser.yml

@@ -61,6 +61,8 @@ nfpms:
         type: dir
       - dst: /var/cache/ntfy/attachments
         type: dir
+      - dst: /var/lib/ntfy
+        type: dir
       - dst: /usr/share/ntfy/logo.png
         src: server/static/img/ntfy.png
     scripts:

+ 1 - 0
auth/auth.go

@@ -1,3 +1,4 @@
+// Package auth deals with authentication and authorization against topics
 package auth
 
 import (

+ 49 - 12
auth/auth_sqlite.go

@@ -2,13 +2,16 @@ package auth
 
 import (
 	"database/sql"
+	"errors"
+	"fmt"
 	_ "github.com/mattn/go-sqlite3" // SQLite driver
 	"golang.org/x/crypto/bcrypt"
 	"strings"
 )
 
 const (
-	bcryptCost = 11
+	bcryptCost              = 11
+	intentionalSlowDownHash = "$2a$11$eX15DeF27FwAgXt9wqJF0uAUMz74XywJcGBH3kP93pzKYv6ATk2ka" // Cost should match bcryptCost
 )
 
 // Auther-related queries
@@ -27,7 +30,7 @@ const (
 			write INT NOT NULL,
 			PRIMARY KEY (topic, user)
 		);
-		CREATE TABLE IF NOT EXISTS schema_version (
+		CREATE TABLE IF NOT EXISTS schemaVersion (
 			id INT PRIMARY KEY,
 			version INT NOT NULL
 		);
@@ -61,6 +64,13 @@ const (
 	deleteTopicAccessQuery = `DELETE FROM access WHERE user = ? AND topic = ?`
 )
 
+// Schema management queries
+const (
+	currentSchemaVersion     = 1
+	insertSchemaVersion      = `INSERT INTO schemaVersion VALUES (1, ?)`
+	selectSchemaVersionQuery = `SELECT version FROM schemaVersion WHERE id = 1`
+)
+
 // SQLiteAuth is an implementation of Auther and Manager. It stores users and access control list
 // in a SQLite database.
 type SQLiteAuth struct {
@@ -78,7 +88,7 @@ func NewSQLiteAuth(filename string, defaultRead, defaultWrite bool) (*SQLiteAuth
 	if err != nil {
 		return nil, err
 	}
-	if err := setupNewAuthDB(db); err != nil {
+	if err := setupAuthDB(db); err != nil {
 		return nil, err
 	}
 	return &SQLiteAuth{
@@ -88,14 +98,6 @@ func NewSQLiteAuth(filename string, defaultRead, defaultWrite bool) (*SQLiteAuth
 	}, nil
 }
 
-func setupNewAuthDB(db *sql.DB) error {
-	if _, err := db.Exec(createAuthTablesQueries); err != nil {
-		return err
-	}
-	// FIXME schema version
-	return nil
-}
-
 // Authenticate checks username and password and returns a user if correct. The method
 // returns in constant-ish time, regardless of whether the user exists or the password is
 // correct or incorrect.
@@ -105,7 +107,7 @@ func (a *SQLiteAuth) Authenticate(username, password string) (*User, error) {
 	}
 	user, err := a.User(username)
 	if err != nil {
-		bcrypt.CompareHashAndPassword([]byte("$2a$11$eX15DeF27FwAgXt9wqJF0uAUMz74XywJcGBH3kP93pzKYv6ATk2ka"),
+		bcrypt.CompareHashAndPassword([]byte(intentionalSlowDownHash),
 			[]byte("intentional slow-down to avoid timing attacks"))
 		return nil, ErrUnauthenticated
 	}
@@ -360,3 +362,38 @@ func toSQLWildcard(s string) string {
 func fromSQLWildcard(s string) string {
 	return strings.ReplaceAll(s, "%", "*")
 }
+
+func setupAuthDB(db *sql.DB) error {
+	// If 'schemaVersion' table does not exist, this must be a new database
+	rowsSV, err := db.Query(selectSchemaVersionQuery)
+	if err != nil {
+		return setupNewAuthDB(db)
+	}
+	defer rowsSV.Close()
+
+	// If 'schemaVersion' table exists, read version and potentially upgrade
+	schemaVersion := 0
+	if !rowsSV.Next() {
+		return errors.New("cannot determine schema version: database file may be corrupt")
+	}
+	if err := rowsSV.Scan(&schemaVersion); err != nil {
+		return err
+	}
+	rowsSV.Close()
+
+	// Do migrations
+	if schemaVersion == currentSchemaVersion {
+		return nil
+	}
+	return fmt.Errorf("unexpected schema version found: %d", schemaVersion)
+}
+
+func setupNewAuthDB(db *sql.DB) error {
+	if _, err := db.Exec(createAuthTablesQueries); err != nil {
+		return err
+	}
+	if _, err := db.Exec(insertSchemaVersion, currentSchemaVersion); err != nil {
+		return err
+	}
+	return nil
+}

+ 41 - 9
cmd/access.go

@@ -10,16 +10,9 @@ import (
 
 /*
 
-ntfy access                        # Shows access control list
-ntfy access phil                   # Shows access for user phil
-ntfy access phil mytopic           # Shows access for user phil and topic mytopic
-ntfy access phil mytopic rw        # Allow read-write access to mytopic for user phil
-ntfy access everyone mytopic rw    # Allow anonymous read-write access to mytopic
-ntfy access --reset                # Reset entire access control list
-ntfy access --reset phil           # Reset all access for user phil
-ntfy access --reset phil mytopic   # Reset access for user phil and topic mytopic
 
-*/
+
+ */
 
 const (
 	userEveryone = "everyone"
@@ -38,9 +31,45 @@ var cmdAccess = &cli.Command{
 	Before:    initConfigFileInputSource("config", flagsAccess),
 	Action:    execUserAccess,
 	Category:  categoryServer,
+	Description: `Manage the access control list for the ntfy server.
+
+This is a server-only command. It directly manages the user.db as defined in the server config
+file server.yml. The command only works if 'auth-file' is properly defined. Please also refer
+to the related command 'ntfy user'.
+
+The command allows you to show the access control list, as well as change it, depending on how
+it is called.
+
+Usage:
+  ntfy access                            # Shows the entire access control list
+  ntfy access USERNAME                   # Shows access control entries for USERNAME
+  ntfy access USERNAME TOPIC PERMISSION  # Allow/deny access for USERNAME to TOPIC
+
+Arguments:
+  USERNAME     an existing user, as created with 'ntfy user add'
+  TOPIC        name of a topic with optional wildcards, e.g. "mytopic*"
+  PERMISSION   one of the following:
+               - read-write (alias: rw) 
+               - read-only (aliases: read, ro)
+               - write-only (aliases: write, wo)
+               - deny (alias: none)
+
+Examples:
+  ntfy access                        
+  ntfy access phil                   # Shows access for user phil
+  ntfy access phil mytopic rw        # Allow read-write access to mytopic for user phil
+  ntfy access everyone mytopic rw    # Allow anonymous read-write access to mytopic
+  ntfy access everyone "up*" write   # Allow anonymous write-only access to topics "up..." 
+  ntfy access --reset                # Reset entire access control list
+  ntfy access --reset phil           # Reset all access for user phil
+  ntfy access --reset phil mytopic   # Reset access for user phil and topic mytopic
+`,
 }
 
 func execUserAccess(c *cli.Context) error {
+	if c.NArg() > 3 {
+		return errors.New("too many arguments, please check 'ntfy access --help' for usage details")
+	}
 	manager, err := createAuthManager(c)
 	if err != nil {
 		return err
@@ -53,6 +82,9 @@ func execUserAccess(c *cli.Context) error {
 	perms := c.Args().Get(2)
 	reset := c.Bool("reset")
 	if reset {
+		if perms != "" {
+			return errors.New("too many arguments, please check 'ntfy access --help' for usage details")
+		}
 		return resetAccess(c, manager, username, topic)
 	} else if perms == "" {
 		return showAccess(c, manager, username)

+ 3 - 3
cmd/serve.go

@@ -131,13 +131,13 @@ func execServe(c *cli.Context) error {
 		return errors.New("if attachment-cache-dir is set, base-url must also be set")
 	} else if baseURL != "" && !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
 		return errors.New("if set, base-url must start with http:// or https://")
-	} else if !util.InStringList([]string{"read-write", "read-only", "deny-all"}, authDefaultAccess) {
-		return errors.New("if set, auth-default-access must start set to 'read-write', 'read-only' or 'deny-all'")
+	} else if !util.InStringList([]string{"read-write", "read-only", "write-only", "deny-all"}, authDefaultAccess) {
+		return errors.New("if set, auth-default-access must start set to 'read-write', 'read-only', 'write-only' or 'deny-all'")
 	}
 
 	// Default auth permissions
 	authDefaultRead := authDefaultAccess == "read-write" || authDefaultAccess == "read-only"
-	authDefaultWrite := authDefaultAccess == "read-write"
+	authDefaultWrite := authDefaultAccess == "read-write" || authDefaultAccess == "write-only"
 
 	// Special case: Unset default
 	if listenHTTP == "-" {

+ 2 - 2
scripts/postinst.sh

@@ -8,8 +8,8 @@ if [ "$1" = "configure" ] || [ "$1" -ge 1 ]; then
   if [ -d /run/systemd/system ]; then
     # Create ntfy user/group
     id ntfy >/dev/null 2>&1 || useradd --system --no-create-home ntfy
-    chown ntfy.ntfy /var/cache/ntfy /var/cache/ntfy/attachments
-    chmod 700 /var/cache/ntfy /var/cache/ntfy/attachments
+    chown ntfy.ntfy /var/cache/ntfy /var/cache/ntfy/attachments /var/lib/ntfy
+    chmod 700 /var/cache/ntfy /var/cache/ntfy/attachments /var/lib/ntfy
 
     # Hack to change permissions on cache file
     configfile="/etc/ntfy/server.yml"

+ 20 - 6
server/server.yml

@@ -21,8 +21,8 @@
 
 # Path to the private key & cert file for the HTTPS web server. Not used if "listen-https" is not set.
 #
-# key-file:
-# cert-file:
+# key-file: <filename>
+# cert-file: <filename>
 
 # If set, also publish messages to a Firebase Cloud Messaging (FCM) topic for your app.
 # This is optional and only required to save battery when using the Android app.
@@ -32,6 +32,8 @@
 # If set, messages are cached in a local SQLite database instead of only in-memory. This
 # allows for service restarts without losing messages in support of the since= parameter.
 #
+# The "cache-duration" parameter defines the duration for which messages will be buffered
+# before they are deleted. This is required to support the "since=..." and "poll=1" parameter.
 # To disable the cache entirely (on-disk/in-memory), set "cache-duration" to 0.
 # The cache file is created automatically, provided that the correct permissions are set.
 #
@@ -44,13 +46,25 @@
 #   ntfy user and group by running: chown ntfy.ntfy <filename>.
 #
 # cache-file: <filename>
+# cache-duration: "12h"
 
-# Duration for which messages will be buffered before they are deleted.
-# This is required to support the "since=..." and "poll=1" parameter.
+# If set, access to the ntfy server and API can be controlled on a granular level using
+# the 'ntfy user' and 'ntfy access' commands. See the --help pages for details, or check the docs.
 #
-# You can disable the cache entirely by setting this to 0.
+# - auth-file is the SQLite user/access database; it is created automatically if it doesn't already exist
+# - auth-default-access defines the default/fallback access if no access control entry is found; it can be
+#   set to "read-write" (default), "read-only", "write-only" or "deny-all".
 #
-# cache-duration: "12h"
+# Debian/RPM package users:
+#   Use /var/lib/ntfy/user.db as user database to avoid permission issues. The package
+#   creates this folder for you.
+#
+# Check your permissions:
+#   If you are running ntfy with systemd, make sure this user database file is owned by the
+#   ntfy user and group by running: chown ntfy.ntfy <filename>.
+#
+# auth-file: <filename>
+# auth-default-access: "read-write"
 
 # If set, the X-Forwarded-For header is used to determine the visitor IP address
 # instead of the remote address of the connection.