diff --git a/api_key.go b/api_key.go index c1bbce2d..7f47b2ad 100644 --- a/api_key.go +++ b/api_key.go @@ -46,11 +46,25 @@ func (h *Headscale) CreateAPIKey( // Key to return to user, this will only be visible _once_ keyStr := prefix + "." + toBeHashed - hash, err := bcrypt.GenerateFromPassword([]byte(toBeHashed), bcrypt.DefaultCost) + key, err := h.SaveAPIKey(prefix, toBeHashed, expiration) if err != nil { return "", nil, err } + return keyStr, key, nil +} + +// SaveAPIKey saves an ApiKey in a namespace. +func (h *Headscale) SaveAPIKey( + prefix string, + toBeHashed string, + expiration *time.Time, +) (*APIKey, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(toBeHashed), bcrypt.DefaultCost) + if err != nil { + return nil, err + } + key := APIKey{ Prefix: prefix, Hash: hash, @@ -58,10 +72,10 @@ func (h *Headscale) CreateAPIKey( } if err := h.db.Save(&key).Error; err != nil { - return "", nil, fmt.Errorf("failed to save API key to database: %w", err) + return nil, fmt.Errorf("failed to save API key to database: %w", err) } - return keyStr, &key, nil + return &key, nil } // ListAPIKeys returns the list of ApiKeys for a namespace. diff --git a/cmd/headscale/cli/server.go b/cmd/headscale/cli/server.go index a1d19600..28c8d23b 100644 --- a/cmd/headscale/cli/server.go +++ b/cmd/headscale/cli/server.go @@ -1,12 +1,21 @@ package cli import ( + "github.com/prometheus/common/model" "github.com/rs/zerolog/log" "github.com/spf13/cobra" + "time" ) func init() { rootCmd.AddCommand(serveCmd) + + serveCmd.Flags(). + String("api-key-prefix", "", "Initial API Key prefix") + serveCmd.Flags(). + String("api-key-pass", "", "Initial API Key password") + serveCmd.Flags(). + String("api-key-expiration", DefaultAPIKeyExpiry, "Human-readable expiration for initial API key (e.g. 30m, 24h)") } var serveCmd = &cobra.Command{ @@ -21,6 +30,26 @@ var serveCmd = &cobra.Command{ log.Fatal().Caller().Err(err).Msg("Error initializing") } + // Save API key if provided + prefix, _ := cmd.Flags().GetString("api-key-prefix") + password, _ := cmd.Flags().GetString("api-key-pass") + if prefix != "" || password != "" { + if !(prefix != "" && password != "") { + log.Fatal().Caller().Msg("For initial API key both prefix and password should be provided") + } + + durationStr, _ := cmd.Flags().GetString("api-key-expiration") + duration, err := model.ParseDuration(durationStr) + if err != nil { + log.Fatal().Caller().Err(err).Msg("Could not parse duration") + } + expiration := time.Now().UTC().Add(time.Duration(duration)) + + if _, err := app.SaveAPIKey(prefix, password, &expiration); err != nil { + log.Fatal().Caller().Err(err).Msg("Error while saving initial API key") + } + } + err = app.Serve() if err != nil { log.Fatal().Caller().Err(err).Msg("Error starting server") diff --git a/cmd/headscale/cli/utils.go b/cmd/headscale/cli/utils.go index c07e3a25..5aefe569 100644 --- a/cmd/headscale/cli/utils.go +++ b/cmd/headscale/cli/utils.go @@ -97,7 +97,7 @@ func getHeadscaleCLIClient() (context.Context, v1.HeadscaleServiceClient, *grpc. if cfg.CLI.Insecure { tlsConfig := &tls.Config{ - // turn of gosec as we are intentionally setting + // turn off gosec as we are intentionally setting // insecure. //nolint:gosec InsecureSkipVerify: true,