Skip to main content
Glama

Keeper Secrets Manager - MCP

init.go9.1 kB
package commands import ( "encoding/base64" "fmt" "os" "path/filepath" "strings" "github.com/keeper-security/ksm-mcp/internal/config" "github.com/keeper-security/ksm-mcp/internal/ksm" "github.com/keeper-security/ksm-mcp/internal/storage" "github.com/keeper-security/ksm-mcp/internal/validation" "github.com/keeper-security/ksm-mcp/pkg/types" "github.com/spf13/cobra" ) var ( initProfile string initToken string initConfig string initNoProtectionPassword bool ) // initCmd represents the init command var initCmd = &cobra.Command{ Use: "init", Short: "Initialize a new KSM profile", Long: `Initialize a new Keeper Secrets Manager profile with either a one-time token or existing config file. Examples: # Initialize with one-time token ksm-mcp init --profile myprofile --token US:TOKEN_HERE # Initialize from existing KSM config file ksm-mcp init --profile myprofile --config ~/path/to/config.json # Initialize with base64-encoded config ksm-mcp init --profile myprofile --config "BASE64_ENCODED_CONFIG" # Initialize from environment variable export KSM_CONFIG="BASE64_ENCODED_CONFIG" ksm-mcp init --profile myprofile`, RunE: runInit, } func init() { rootCmd.AddCommand(initCmd) // Ensure init command outputs to stderr (important for Docker entrypoint) initCmd.SetOut(os.Stderr) initCmd.SetErr(os.Stderr) initCmd.Flags().StringVar(&initProfile, "profile", "", "profile name (required)") initCmd.Flags().StringVar(&initToken, "token", "", "one-time token (US:TOKEN_HERE)") initCmd.Flags().StringVar(&initConfig, "config", "", "path to KSM config file or base64-encoded config") initCmd.Flags().BoolVar(&initNoProtectionPassword, "no-protection-password", false, "disable protection password for local profile encryption (NOT RECOMMENDED)") _ = initCmd.MarkFlagRequired("profile") } func runInit(cmd *cobra.Command, args []string) error { // Check for KSM_CONFIG_BASE64 or KSM_CONFIG environment variable if no flags provided if initToken == "" && initConfig == "" { if envConfig := os.Getenv("KSM_CONFIG_BASE64"); envConfig != "" { initConfig = envConfig } else if envConfig := os.Getenv("KSM_CONFIG"); envConfig != "" { initConfig = envConfig } } if initToken == "" && initConfig == "" { return fmt.Errorf("either --token, --config, KSM_CONFIG_BASE64 or KSM_CONFIG environment variable must be provided") } // Validate only one config source is provided if initToken != "" && initConfig != "" { return fmt.Errorf("cannot specify both --token and --config") } // Validate profile name validator := validation.NewValidator() if err := validator.ValidateProfileName(initProfile); err != nil { return fmt.Errorf("invalid profile name: %w", err) } // Get or create config directory configDir := os.Getenv("KSM_MCP_CONFIG_DIR") if configDir == "" { home, err := os.UserHomeDir() if err != nil { return fmt.Errorf("failed to get home directory: %w", err) } configDir = filepath.Join(home, ".keeper", "ksm-mcp") } // Create config directory if it doesn't exist if err := os.MkdirAll(configDir, 0700); err != nil { return fmt.Errorf("failed to create config directory: %w", err) } // Load or create config cfg, err := config.LoadOrCreate(filepath.Join(configDir, "config.yaml")) if err != nil { return fmt.Errorf("failed to load config: %w", err) } // Create storage with password if config has one var store *storage.ProfileStore if cfg.Security.ProtectionPasswordHash != "" { fmt.Fprint(os.Stderr, "Enter protection password: ") password, err := readPassword() if err != nil { return fmt.Errorf("failed to read password: %w", err) } store, err = storage.NewProfileStoreWithPassword(configDir, password) if err != nil { return fmt.Errorf("failed to create profile store: %w", err) } } else { // First time setup if initNoProtectionPassword { // Create store without password fmt.Fprintln(os.Stderr, "⚠️ WARNING: Creating profile WITHOUT protection password for local profile encryption.") fmt.Fprintln(os.Stderr, "Your KSM credentials will be stored in plain text.") fmt.Fprintln(os.Stderr, "This is NOT RECOMMENDED for production use.") store = storage.NewProfileStore(configDir) } else { // Prompt for protection password fmt.Fprintln(os.Stderr, "First time setup - please create a protection password for local profile encryption.") fmt.Fprintln(os.Stderr, "This password will be used to encrypt all stored profiles.") fmt.Fprint(os.Stderr, "Enter protection password: ") password, err := readPassword() if err != nil { return fmt.Errorf("failed to read password: %w", err) } fmt.Fprint(os.Stderr, "Confirm protection password: ") confirm, err := readPassword() if err != nil { return fmt.Errorf("failed to read password: %w", err) } if password != confirm { return fmt.Errorf("passwords do not match") } store, err = storage.NewProfileStoreWithPassword(configDir, password) if err != nil { return fmt.Errorf("failed to create profile store: %w", err) } // Save protection password hash in config cfg.Security.ProtectionPasswordHash = store.GetPasswordHash() if err := cfg.SaveDefault(); err != nil { return fmt.Errorf("failed to save config: %w", err) } } } // Check if profile already exists if _, err := store.GetProfile(initProfile); err == nil { return fmt.Errorf("profile '%s' already exists", initProfile) } fmt.Fprintf(os.Stderr, "Initializing profile '%s'...\n", initProfile) var ksmConfig map[string]string if initToken != "" { // Validate token if err := validator.ValidateToken(initToken); err != nil { return fmt.Errorf("invalid token: %w", err) } // Initialize with one-time token verboseLog("Initializing KSM with token") ksmConfig, err = ksm.InitializeWithToken(initToken) if err != nil { return fmt.Errorf("failed to initialize with token: %w", err) } fmt.Fprintln(os.Stderr, "✓ Successfully initialized KSM configuration") } else { // Determine if config is a file path or base64 var configData []byte var err error // Check if it's a file path (contains / or \ or starts with ~ or .) if strings.ContainsAny(initConfig, "/\\") || strings.HasPrefix(initConfig, "~") || strings.HasPrefix(initConfig, ".") { // Load from file verboseLog("Loading KSM config from file: %s", initConfig) configPath := initConfig if strings.HasPrefix(configPath, "~") { home, err := os.UserHomeDir() if err != nil { return fmt.Errorf("failed to get home directory: %w", err) } configPath = filepath.Join(home, configPath[1:]) } // Clean and validate the path cleanPath := filepath.Clean(configPath) configData, err = os.ReadFile(cleanPath) // #nosec G304 - user-provided path for config if err != nil { return fmt.Errorf("failed to read config file: %w", err) } fmt.Fprintln(os.Stderr, "✓ Successfully loaded KSM configuration from file") } else { // Assume it's base64-encoded verboseLog("Loading base64-encoded KSM config") configData, err = base64.StdEncoding.DecodeString(initConfig) if err != nil { return fmt.Errorf("failed to decode base64 config: %w", err) } fmt.Fprintln(os.Stderr, "✓ Successfully loaded KSM configuration from base64") } ksmConfig, err = ksm.InitializeWithConfig(configData) if err != nil { return fmt.Errorf("failed to initialize with config: %w", err) } } // Test the connection fmt.Fprint(os.Stderr, "Testing connection to Keeper Secrets Manager... ") client, err := ksm.NewClient(&types.Profile{ Name: initProfile, Config: ksmConfig, }, nil) // No logger for CLI if err != nil { fmt.Fprintln(os.Stderr, "✗") return fmt.Errorf("failed to create client: %w", err) } // Test the connection by listing secrets if _, err := client.ListSecrets([]string{}); err != nil { return fmt.Errorf("failed to test KSM connection: %w", err) } // Create and save profile profile := &types.Profile{ Name: initProfile, Config: ksmConfig, } if err := store.CreateProfile(profile.Name, profile.Config); err != nil { return fmt.Errorf("failed to save profile: %w", err) } // If this is the first profile, make it default if cfg.Profiles.Default == "" { cfg.Profiles.Default = initProfile if err := cfg.SaveDefault(); err != nil { return fmt.Errorf("failed to update default profile: %w", err) } fmt.Fprintf(os.Stderr, "✓ Set '%s' as default profile\n", initProfile) } fmt.Fprintf(os.Stderr, "\nProfile '%s' initialized successfully!\n", initProfile) fmt.Fprintln(os.Stderr, "\nTo start the MCP server, run:") fmt.Fprintf(os.Stderr, " ksm-mcp serve --profile %s\n", initProfile) return nil } // readPassword reads a password from stdin without echoing func readPassword() (string, error) { // Simple implementation - in production use golang.org/x/term var password string _, err := fmt.Scanln(&password) if err != nil { return "", err } return strings.TrimSpace(password), nil }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Keeper-Security/keeper-mcp-golang-docker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server