Add read only user (#7862)

* add readonly user

* add args

* address comments

* avoid same user name

* Prevents timing attacks

* doc

---------

Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
Deyu Han
2025-12-25 13:18:16 -08:00
committed by GitHub
parent e8a41ec053
commit 225e3d0302
7 changed files with 171 additions and 50 deletions

View File

@@ -33,13 +33,15 @@ var (
)
type AdminOptions struct {
port *int
grpcPort *int
master *string
masters *string // deprecated, for backward compatibility
adminUser *string
adminPassword *string
dataDir *string
port *int
grpcPort *int
master *string
masters *string // deprecated, for backward compatibility
adminUser *string
adminPassword *string
readOnlyUser *string
readOnlyPassword *string
dataDir *string
}
func init() {
@@ -52,6 +54,8 @@ func init() {
a.adminUser = cmdAdmin.Flag.String("adminUser", "admin", "admin interface username")
a.adminPassword = cmdAdmin.Flag.String("adminPassword", "", "admin interface password (if empty, auth is disabled)")
a.readOnlyUser = cmdAdmin.Flag.String("readOnlyUser", "", "read-only user username (optional, for view-only access)")
a.readOnlyPassword = cmdAdmin.Flag.String("readOnlyPassword", "", "read-only user password (optional, for view-only access; requires adminPassword to be set)")
}
var cmdAdmin = &Command{
@@ -84,7 +88,11 @@ var cmdAdmin = &Command{
Authentication:
- If adminPassword is not set, the admin interface runs without authentication
- If adminPassword is set, users must login with adminUser/adminPassword
- If adminPassword is set, users must login with adminUser/adminPassword (full access)
- Optional read-only access: set readOnlyUser and readOnlyPassword for view-only access
- Read-only users can view cluster status and configurations but cannot make changes
- IMPORTANT: When read-only credentials are configured, adminPassword MUST also be set
- This ensures an admin account exists to manage and authorize read-only access
- Sessions are secured with auto-generated session keys
Security Configuration:
@@ -139,6 +147,26 @@ func runAdmin(cmd *Command, args []string) bool {
return false
}
// Security validation: prevent empty username when password is set
if *a.adminPassword != "" && *a.adminUser == "" {
fmt.Println("Error: -adminUser cannot be empty when -adminPassword is set")
return false
}
if *a.readOnlyPassword != "" && *a.readOnlyUser == "" {
fmt.Println("Error: -readOnlyUser is required when -readOnlyPassword is set")
return false
}
// Security validation: prevent username conflicts between admin and read-only users
if *a.adminUser != "" && *a.readOnlyUser != "" && *a.adminUser == *a.readOnlyUser {
fmt.Println("Error: -adminUser and -readOnlyUser must be different when both are configured")
return false
}
// Security validation: admin password is required for read-only user
if *a.readOnlyPassword != "" && *a.adminPassword == "" {
fmt.Println("Error: -adminPassword must be set when -readOnlyPassword is configured")
return false
}
// Set default gRPC port if not specified
if *a.grpcPort == 0 {
*a.grpcPort = *a.port + 10000
@@ -160,7 +188,10 @@ func runAdmin(cmd *Command, args []string) bool {
fmt.Printf("Data Directory: Not specified (configuration will be in-memory only)\n")
}
if *a.adminPassword != "" {
fmt.Printf("Authentication: Enabled (user: %s)\n", *a.adminUser)
fmt.Printf("Authentication: Enabled (admin user: %s)\n", *a.adminUser)
if *a.readOnlyPassword != "" {
fmt.Printf("Read-only access: Enabled (read-only user: %s)\n", *a.readOnlyUser)
}
} else {
fmt.Printf("Authentication: Disabled\n")
}
@@ -274,8 +305,9 @@ func startAdminServer(ctx context.Context, options AdminOptions) error {
}()
// Create handlers and setup routes
authRequired := *options.adminPassword != ""
adminHandlers := handlers.NewAdminHandlers(adminServer)
adminHandlers.SetupRoutes(r, *options.adminPassword != "", *options.adminUser, *options.adminPassword)
adminHandlers.SetupRoutes(r, authRequired, *options.adminUser, *options.adminPassword, *options.readOnlyUser, *options.readOnlyPassword)
// Server configuration
addr := fmt.Sprintf(":%d", *options.port)