Skip to content

Commit 6894607

Browse files
committed
overlord: implement user group system
In order to better support groups. We ditched the htpasswd format and user a sqlite database instead.
1 parent 309248b commit 6894607

File tree

18 files changed

+3428
-263
lines changed

18 files changed

+3428
-263
lines changed

README.rst

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,50 @@ Run ``make`` in the project root directory. ``make`` builds the overlord daemon
3333

3434
Basic Deployment
3535
----------------
36-
1. On a server with public IP (to be used as a proxy), run ``overlordd -port 9000`` to start the server (if ``-port`` is not specified, default port is 80 or 443 depends on whether TLS is enabled or not).
37-
2. On a client machine, run ``ghost SERVER_IP`` or ``ghost.py SERVER_IP``. ``ghost`` and ``ghost.py`` are functional equivalent except one is written in Python, and the other is written in Go.
38-
3. Browse http://SERVER_IP:9000 and you will see the overlord web dashboard. The default user/password is ``overlord/cros``. To change the password, please follow the `Overlord advanced deployment guide <https://github.com/aitjcize/Overlord/blob/master/docs/deployment.rst#changing-default-password>`_.
36+
1. On a server with public IP (to be used as a proxy), initialize the database by running ``overlordd -init -db-path overlord.db``. You'll be prompted to create an admin username and password.
37+
2. Start the server with ``overlordd -port 9000 -db-path overlord.db`` (if ``-port`` is not specified, default port is 80 or 443 depends on whether TLS is enabled or not).
38+
3. On a client machine, run ``ghost SERVER_IP`` or ``ghost.py SERVER_IP``. ``ghost`` and ``ghost.py`` are functional equivalent except one is written in Python, and the other is written in Go.
39+
4. Browse http://SERVER_IP:9000 and you will see the overlord web dashboard. Log in with the admin credentials you created during initialization.
3940

40-
For testing purpose, you can run both server and client on the same machine, then browse http://localhost:9000 instead. If you want to disable authentication of the web dashboard, you could add the ``-noauth`` option when starting ``overlordd``.
41-
Overlord server supports a lot of features such as TLS encryption, client auto upgrade. For setting up these, please refer to the `Overlord advanced deployment guide <https://github.com/aitjcize/Overlord/blob/master/docs/deployment.rst>`_.
41+
For testing purpose, you can run both server and client on the same machine, then browse http://localhost:9000 instead. Overlord server supports a lot of features such as TLS encryption, client auto upgrade. For setting up these, please refer to the `Overlord advanced deployment guide <https://github.com/aitjcize/Overlord/blob/master/docs/deployment.rst>`_.
42+
43+
User and Group Management
44+
------------------------
45+
Overlord uses a SQLite database for user and group management. The database is stored in the file specified by the ``-db-path`` parameter (default: ``overlord.db``).
46+
47+
Before starting the server for the first time, you must initialize the database with the ``-init`` flag:
48+
49+
.. code-block:: bash
50+
51+
overlordd -init -db-path overlord.db
52+
53+
This command will:
54+
1. Create the database schema
55+
2. Generate a secure JWT secret for authentication
56+
3. Prompt you to create an admin user with a custom username and password
57+
4. Create the admin group
58+
59+
You can manage users and groups through the web interface by navigating to:
60+
- Users: ``/users``
61+
- Groups: ``/groups``
62+
63+
Only administrators can create, modify, and delete users and groups.
64+
65+
Allowlist Format
66+
---------------
67+
Overlord supports access control through allowlists that specify which users can access which clients. The allowlist format has been enhanced to support both users and groups with the following prefixes:
68+
69+
- ``u/username`` - Grants access to a specific user
70+
- ``g/groupname`` - Grants access to all members of a specific group
71+
- ``anyone`` - Grants access to all authenticated users
72+
73+
When running a Ghost client, you can specify the allowlist using the ``--allowlist`` parameter:
74+
75+
.. code-block:: bash
76+
77+
ghost --allowlist u/user1,u/user2,g/group1 SERVER_IP
78+
79+
If no prefix is provided for an entity, it's assumed to be a username and will be automatically prefixed with ``u/``.
4280

4381
Usage
4482
-----
@@ -114,7 +152,52 @@ Besides from the web interface, a command line interface is also provided. The
114152
115153
2016-03-08 17:57:00 (37.5 MB/s) - ‘index.html’ saved [419/419]
116154
155+
7. User and group management with admin subcommand:
156+
157+
.. code-block:: bash
117158
159+
# List all users
160+
% ovl admin list-users
161+
USERNAME ADMIN GROUPS
162+
--------------------------------------------------
163+
admin Yes admin
164+
user1 No testers
165+
166+
# Add a new user
167+
% ovl admin add-user username password
168+
User 'username' created successfully
169+
170+
# Add user to admin group
171+
% ovl admin add-user-to-group username admin
172+
User 'username' added to group 'admin' successfully
173+
174+
# List groups
175+
% ovl admin list-groups
176+
GROUP NAME USER COUNT
177+
------------------------------
178+
admin 2
179+
testers 1
180+
181+
# View users in a group
182+
% ovl admin list-group-users admin
183+
Users in group 'admin':
184+
- admin
185+
- username
186+
187+
REST API
188+
--------
189+
Overlord provides a REST API for managing users and groups:
190+
191+
- GET /api/users - List all users
192+
- POST /api/users - Create a new user
193+
- DELETE /api/users/{username} - Delete a user
194+
- PUT /api/users/{username}/password - Update a user's password
195+
- GET /api/groups - List all groups
196+
- POST /api/groups - Create a new group
197+
- DELETE /api/groups/{groupname} - Delete a group
198+
- POST /api/groups/{groupname}/users - Add a user to a group
199+
- DELETE /api/groups/{groupname}/users/{username} - Remove a user from a group
200+
- GET /api/groups/{groupname}/users - List all users in a group
118201
119202
Disclaimer
120203
----------

bin/jwt-secret

Lines changed: 0 additions & 1 deletion
This file was deleted.

bin/overlord.htpasswd

Lines changed: 0 additions & 1 deletion
This file was deleted.

cmd/overlordd/main.go

Lines changed: 122 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,49 +5,147 @@
55
package main
66

77
import (
8+
"bufio"
89
"flag"
910
"fmt"
1011
"os"
12+
"path/filepath"
13+
"strings"
14+
"syscall"
1115

1216
"github.com/aitjcize/Overlord/overlord"
17+
"golang.org/x/term"
1318
)
1419

15-
var bindAddr = flag.String("bind", "0.0.0.0", "specify alternate bind address")
16-
var port = flag.Int("port", 0,
17-
"alternate port listen instead of standard ports (http:80, https:443)")
18-
var lanDiscInterface = flag.String("lan-disc-iface", "",
19-
"the network interface used for broadcasting LAN discovery packets")
20-
var noLanDisc = flag.Bool("no-lan-disc", false,
21-
"disable LAN discovery broadcasting")
22-
var tlsCerts = flag.String("tls", "",
23-
"TLS certificates in the form of 'cert.pem,key.pem'. Empty to disable.")
24-
var noLinkTLS = flag.Bool("no-link-tls", false,
25-
"disable TLS between ghost and overlord. Only valid when TLS is enabled.")
26-
var htpasswdPath = flag.String("htpasswd-path", "overlord.htpasswd",
27-
"the path to the .htpasswd file. Required for authentication.")
28-
var jwtSecretPath = flag.String("jwt-secret-path", "jwt-secret",
29-
"Path to the file containing the JWT secret. Required for authentication.")
20+
var (
21+
bindAddr = flag.String("bind",
22+
"0.0.0.0", "specify alternate bind address")
23+
port = flag.Int("port",
24+
0, "alternate port listen instead of standard ports (http:80, https:443)")
25+
lanDiscInterface = flag.String("lan-disc-iface",
26+
"", "the network interface used for broadcasting LAN discovery packets")
27+
noLanDisc = flag.Bool("no-lan-disc",
28+
false, "disable LAN discovery broadcasting")
29+
tlsCerts = flag.String("tls",
30+
"", "TLS certificates in the form of 'cert.pem,key.pem'. Empty to disable.")
31+
noLinkTLS = flag.Bool("no-link-tls",
32+
false, "disable TLS between ghost and overlord. Only valid when TLS is enabled.")
33+
dbPath = flag.String("db-path",
34+
"overlord.db", "the path to the SQLite database file for user, group, and authentication data")
35+
initializeDB = flag.Bool("init",
36+
false, "Initialize the database with a custom admin user and password and generate a JWT secret")
37+
adminUser = flag.String("admin-user",
38+
"", "Admin username for database initialization (only used with -init)")
39+
adminPass = flag.String("admin-pass",
40+
"", "Admin password for database initialization (only used with -init)")
41+
)
3042

3143
func usage() {
32-
fmt.Fprintf(os.Stderr, "Usage: overlordd [OPTIONS]\n")
44+
prog := filepath.Base(os.Args[0])
45+
fmt.Fprintf(os.Stderr, "Usage: %s [options]\n", prog)
46+
fmt.Fprintln(os.Stderr, "Options:")
3347
flag.PrintDefaults()
34-
os.Exit(2)
48+
os.Exit(1)
49+
}
50+
51+
func promptForInput(prompt string) string {
52+
reader := bufio.NewReader(os.Stdin)
53+
fmt.Print(prompt)
54+
input, _ := reader.ReadString('\n')
55+
return strings.TrimSpace(input)
56+
}
57+
58+
func promptForPassword(prompt string) string {
59+
fmt.Print(prompt)
60+
password, err := term.ReadPassword(int(syscall.Stdin))
61+
fmt.Println() // Add a newline after the password input
62+
63+
if err != nil {
64+
panic(err)
65+
}
66+
return string(password)
67+
}
68+
69+
func initializeDatabase(dbPath string) error {
70+
adminUsername := *adminUser
71+
adminPassword := *adminPass
72+
73+
// If admin username is not provided via command line, prompt for it
74+
if adminUsername == "" {
75+
adminUsername = promptForInput("Enter admin username [admin]: ")
76+
if adminUsername == "" {
77+
adminUsername = "admin"
78+
}
79+
}
80+
81+
// If admin password is not provided via command line, prompt for it
82+
if adminPassword == "" {
83+
for {
84+
adminPassword = promptForPassword("Enter admin password: ")
85+
if adminPassword == "" {
86+
fmt.Println("Password cannot be empty, please try again.")
87+
continue
88+
}
89+
break
90+
}
91+
} else if adminPassword == "" {
92+
return fmt.Errorf("password cannot be empty")
93+
}
94+
95+
dbManager := overlord.NewDatabaseManager(dbPath)
96+
97+
err := dbManager.Initialize(adminUsername, adminPassword)
98+
if err != nil {
99+
return fmt.Errorf("failed to initialize database: %v", err)
100+
}
101+
102+
fmt.Println("Database initialization complete.")
103+
fmt.Println("You can now start the server without the -init flag.")
104+
return nil
105+
}
106+
107+
func checkDatabaseInitialized(dbPath string) bool {
108+
// Simple check - if the file exists and has data, consider it initialized
109+
info, err := os.Stat(dbPath)
110+
if err != nil || info.Size() == 0 {
111+
return false
112+
}
113+
return true
35114
}
36115

37116
func main() {
38-
flag.Usage = usage
39117
flag.Parse()
40118

41-
// Validate required flags
42-
if *htpasswdPath == "" {
43-
fmt.Fprintf(os.Stderr, "Error: -htpasswd-path is required\n")
119+
if len(flag.Args()) > 0 {
120+
fmt.Fprintf(os.Stderr, "Error: unknown argument: %s\n", flag.Args()[0])
44121
usage()
45122
}
46-
if *jwtSecretPath == "" {
47-
fmt.Fprintf(os.Stderr, "Error: -jwt-secret-path is required\n")
123+
124+
// Validate required flags
125+
if *dbPath == "" {
126+
fmt.Fprintf(os.Stderr, "Error: -db-path is required\n")
48127
usage()
49128
}
50129

130+
// Initialize the database if requested
131+
if *initializeDB {
132+
if checkDatabaseInitialized(*dbPath) {
133+
fmt.Fprintf(os.Stderr, "Error: Database already initialized\n")
134+
os.Exit(1)
135+
}
136+
if err := initializeDatabase(*dbPath); err != nil {
137+
fmt.Fprintf(os.Stderr, "Error initializing database: %v\n", err)
138+
os.Exit(1)
139+
}
140+
os.Exit(0)
141+
}
142+
143+
// Check if the database is initialized
144+
if !checkDatabaseInitialized(*dbPath) {
145+
fmt.Fprintf(os.Stderr, "Error: Database not initialized. Run with -init to initialize\n")
146+
os.Exit(1)
147+
}
148+
51149
overlord.StartOverlord(*bindAddr, *port, *lanDiscInterface, !*noLanDisc,
52-
*tlsCerts, !*noLinkTLS, *htpasswdPath, *jwtSecretPath)
150+
*tlsCerts, !*noLinkTLS, *dbPath)
53151
}

0 commit comments

Comments
 (0)