Skip to content

Commit 6f3ecbf

Browse files
committed
feat: add init-admin CLI command for all server roles
Add an init-admin subcommand to the manufacturing, owner, and rendezvous server commands. Each command connects to the configured database, initializes auth tables, parses the server role's OpenAPI spec to derive the full scope set, then seeds an admin user with an API key printed to stdout. Flags: --name (default "admin"), --email (default "admin@example.com"), --force (create additional admin when users already exist). Example usage: go-fdo-server manufacturing init-admin --config manufacturing.yaml The test helper resetState() is updated to reset the init-admin subcommand flags, preventing pflag panics on re-initialization. Signed-off-by: Miguel Martín <mmartinv@redhat.com>
1 parent 0e85d1d commit 6f3ecbf

4 files changed

Lines changed: 168 additions & 0 deletions

File tree

cmd/config_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,17 @@ func resetState(t *testing.T) {
3838
manufacturingCmd.ResetFlags()
3939
manufacturingCmd.ResetCommands()
4040
manufacturingCmd.SetArgs(nil)
41+
manufacturingInitAdminCmd.ResetFlags()
4142

4243
ownerCmd.ResetFlags()
4344
ownerCmd.ResetCommands()
4445
ownerCmd.SetArgs(nil)
46+
ownerInitAdminCmd.ResetFlags()
4547

4648
rendezvousCmd.ResetFlags()
4749
rendezvousCmd.ResetCommands()
4850
rendezvousCmd.SetArgs(nil)
51+
rendezvousInitAdminCmd.ResetFlags()
4952

5053
rootCmdInit()
5154
ownerCmdInit()

cmd/manufacturing.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import (
77
"fmt"
88
"log/slog"
99

10+
manufacturer "github.com/fido-device-onboard/go-fdo-server/api/v2/manufacturer"
11+
"github.com/fido-device-onboard/go-fdo-server/internal/auth"
1012
"github.com/fido-device-onboard/go-fdo-server/internal/config"
1113
"github.com/fido-device-onboard/go-fdo-server/internal/server"
14+
"github.com/fido-device-onboard/go-fdo-server/internal/state"
1215
"github.com/spf13/cobra"
1316
"github.com/spf13/viper"
1417
)
@@ -59,6 +62,52 @@ generate Ownership Vouchers.`,
5962
},
6063
}
6164

65+
var manufacturingInitAdminCmd = &cobra.Command{
66+
Use: "init-admin",
67+
Short: "Create initial admin user and API key",
68+
Long: `Creates an admin user with full permissions and generates an API key. The API key is printed to stdout.`,
69+
RunE: func(cmd *cobra.Command, args []string) error {
70+
var mfgConfig config.ManufacturingServerConfig
71+
if err := viper.Unmarshal(&mfgConfig); err != nil {
72+
return fmt.Errorf("failed to unmarshal config: %w", err)
73+
}
74+
75+
gormDB, err := mfgConfig.DB.GetDB()
76+
if err != nil {
77+
return fmt.Errorf("failed to connect to database: %w", err)
78+
}
79+
80+
ctx := cmd.Context()
81+
82+
if err := state.InitAuthDB(ctx, gormDB); err != nil {
83+
return fmt.Errorf("failed to initialize auth database: %w", err)
84+
}
85+
86+
name, _ := cmd.Flags().GetString("name")
87+
email, _ := cmd.Flags().GetString("email")
88+
force, _ := cmd.Flags().GetBool("force")
89+
90+
routeScopes, err := auth.ParseRouteScopes(manufacturer.OpenAPISpecJSON)
91+
if err != nil {
92+
return fmt.Errorf("failed to parse route scopes: %w", err)
93+
}
94+
serverScopes := auth.CollectAllScopes(routeScopes)
95+
96+
admin := &state.SeedAdminConfig{Name: name, Email: email}
97+
apiKey, err := state.SeedAuth(ctx, gormDB, admin, serverScopes, force)
98+
if err != nil {
99+
return err
100+
}
101+
102+
if apiKey == "" {
103+
return fmt.Errorf("auth database already seeded; use --force to create an additional admin user")
104+
}
105+
106+
fmt.Println(apiKey)
107+
return nil
108+
},
109+
}
110+
62111
// Set up the manufacturing command line. Used by the unit tests to reset state between tests.
63112
func manufacturingCmdInit() {
64113
rootCmd.AddCommand(manufacturingCmd)
@@ -70,6 +119,12 @@ func manufacturingCmdInit() {
70119
manufacturingCmd.Flags().String("device-ca-cert", "", "Device CA certificate path")
71120
manufacturingCmd.Flags().String("device-ca-key", "", "Device CA private key path")
72121
manufacturingCmd.Flags().BoolP("help", "h", false, "Help for Manufacturing server")
122+
123+
// Add init-admin subcommand
124+
manufacturingCmd.AddCommand(manufacturingInitAdminCmd)
125+
manufacturingInitAdminCmd.Flags().String("name", "admin", "Admin user name")
126+
manufacturingInitAdminCmd.Flags().String("email", "admin@example.com", "Admin user email")
127+
manufacturingInitAdminCmd.Flags().Bool("force", false, "Create admin even if users exist")
73128
}
74129

75130
func init() {

cmd/owner.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import (
77
"fmt"
88
"log/slog"
99

10+
owner "github.com/fido-device-onboard/go-fdo-server/api/v2/owner"
11+
"github.com/fido-device-onboard/go-fdo-server/internal/auth"
1012
"github.com/fido-device-onboard/go-fdo-server/internal/config"
1113
"github.com/fido-device-onboard/go-fdo-server/internal/server"
14+
"github.com/fido-device-onboard/go-fdo-server/internal/state"
1215
"github.com/spf13/cobra"
1316
"github.com/spf13/viper"
1417
)
@@ -58,6 +61,52 @@ TO0 protocol against the Rendezvous server, registering itself as a device owner
5861
},
5962
}
6063

64+
var ownerInitAdminCmd = &cobra.Command{
65+
Use: "init-admin",
66+
Short: "Create initial admin user and API key",
67+
Long: `Creates an admin user with full permissions and generates an API key. The API key is printed to stdout.`,
68+
RunE: func(cmd *cobra.Command, args []string) error {
69+
var ownerConfig config.OwnerServerConfig
70+
if err := viper.Unmarshal(&ownerConfig); err != nil {
71+
return fmt.Errorf("failed to unmarshal config: %w", err)
72+
}
73+
74+
gormDB, err := ownerConfig.DB.GetDB()
75+
if err != nil {
76+
return fmt.Errorf("failed to connect to database: %w", err)
77+
}
78+
79+
ctx := cmd.Context()
80+
81+
if err := state.InitAuthDB(ctx, gormDB); err != nil {
82+
return fmt.Errorf("failed to initialize auth database: %w", err)
83+
}
84+
85+
name, _ := cmd.Flags().GetString("name")
86+
email, _ := cmd.Flags().GetString("email")
87+
force, _ := cmd.Flags().GetBool("force")
88+
89+
routeScopes, err := auth.ParseRouteScopes(owner.OpenAPISpecJSON)
90+
if err != nil {
91+
return fmt.Errorf("failed to parse route scopes: %w", err)
92+
}
93+
serverScopes := auth.CollectAllScopes(routeScopes)
94+
95+
admin := &state.SeedAdminConfig{Name: name, Email: email}
96+
apiKey, err := state.SeedAuth(ctx, gormDB, admin, serverScopes, force)
97+
if err != nil {
98+
return err
99+
}
100+
101+
if apiKey == "" {
102+
return fmt.Errorf("auth database already seeded; use --force to create an additional admin user")
103+
}
104+
105+
fmt.Println(apiKey)
106+
return nil
107+
},
108+
}
109+
61110
// Set up the owner command line. Used by the unit tests to reset state between tests.
62111
func ownerCmdInit() {
63112
rootCmd.AddCommand(ownerCmd)
@@ -69,6 +118,12 @@ func ownerCmdInit() {
69118
ownerCmd.Flags().String("owner-key", "", "Owner private key path")
70119
ownerCmd.Flags().Bool("to0-insecure-tls", false, "Use insecure TLS (skip Rendezvous certificate verification) for the TO0 protocol")
71120
ownerCmd.Flags().BoolP("help", "h", false, "Help for Owner server")
121+
122+
// Add init-admin subcommand
123+
ownerCmd.AddCommand(ownerInitAdminCmd)
124+
ownerInitAdminCmd.Flags().String("name", "admin", "Admin user name")
125+
ownerInitAdminCmd.Flags().String("email", "admin@example.com", "Admin user email")
126+
ownerInitAdminCmd.Flags().Bool("force", false, "Create admin even if users exist")
72127
}
73128

74129
func init() {

cmd/rendezvous.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ import (
88
"log/slog"
99
"strings"
1010

11+
rendezvous "github.com/fido-device-onboard/go-fdo-server/api/v2/rendezvous"
12+
"github.com/fido-device-onboard/go-fdo-server/internal/auth"
1113
"github.com/fido-device-onboard/go-fdo-server/internal/config"
1214
"github.com/fido-device-onboard/go-fdo-server/internal/server"
15+
"github.com/fido-device-onboard/go-fdo-server/internal/state"
1316
"github.com/spf13/cobra"
1417
"github.com/spf13/viper"
1518
)
@@ -98,6 +101,52 @@ directs devices to their Owner server during the TO1 protocol.`,
98101
},
99102
}
100103

104+
var rendezvousInitAdminCmd = &cobra.Command{
105+
Use: "init-admin",
106+
Short: "Create initial admin user and API key",
107+
Long: `Creates an admin user with full permissions and generates an API key. The API key is printed to stdout.`,
108+
RunE: func(cmd *cobra.Command, args []string) error {
109+
var rvConfig config.RendezvousServerConfig
110+
if err := viper.Unmarshal(&rvConfig); err != nil {
111+
return fmt.Errorf("failed to unmarshal config: %w", err)
112+
}
113+
114+
gormDB, err := rvConfig.DB.GetDB()
115+
if err != nil {
116+
return fmt.Errorf("failed to connect to database: %w", err)
117+
}
118+
119+
ctx := cmd.Context()
120+
121+
if err := state.InitAuthDB(ctx, gormDB); err != nil {
122+
return fmt.Errorf("failed to initialize auth database: %w", err)
123+
}
124+
125+
name, _ := cmd.Flags().GetString("name")
126+
email, _ := cmd.Flags().GetString("email")
127+
force, _ := cmd.Flags().GetBool("force")
128+
129+
routeScopes, err := auth.ParseRouteScopes(rendezvous.OpenAPISpecJSON)
130+
if err != nil {
131+
return fmt.Errorf("failed to parse route scopes: %w", err)
132+
}
133+
serverScopes := auth.CollectAllScopes(routeScopes)
134+
135+
admin := &state.SeedAdminConfig{Name: name, Email: email}
136+
apiKey, err := state.SeedAuth(ctx, gormDB, admin, serverScopes, force)
137+
if err != nil {
138+
return err
139+
}
140+
141+
if apiKey == "" {
142+
return fmt.Errorf("auth database already seeded; use --force to create an additional admin user")
143+
}
144+
145+
fmt.Println(apiKey)
146+
return nil
147+
},
148+
}
149+
101150
// Set up the rendezvous command line. Used by the unit tests to reset state between tests.
102151
func rendezvousCmdInit() {
103152
rootCmd.AddCommand(rendezvousCmd)
@@ -110,6 +159,12 @@ func rendezvousCmdInit() {
110159
rendezvousCmd.Flags().Uint32(flag.name, flag.defaultValue, description)
111160
viper.SetDefault(flag.viperKey, flag.defaultValue)
112161
}
162+
163+
// Add init-admin subcommand
164+
rendezvousCmd.AddCommand(rendezvousInitAdminCmd)
165+
rendezvousInitAdminCmd.Flags().String("name", "admin", "Admin user name")
166+
rendezvousInitAdminCmd.Flags().String("email", "admin@example.com", "Admin user email")
167+
rendezvousInitAdminCmd.Flags().Bool("force", false, "Create admin even if users exist")
113168
}
114169

115170
func init() {

0 commit comments

Comments
 (0)