diff --git a/packages/cmd/kmip.go b/packages/cmd/kmip.go index ad8c5d55..cc1851f7 100644 --- a/packages/cmd/kmip.go +++ b/packages/cmd/kmip.go @@ -5,15 +5,22 @@ package cmd import ( "fmt" + "os" + "os/exec" + "runtime" "github.com/Infisical/infisical-merge/packages/config" + localkmip "github.com/Infisical/infisical-merge/packages/kmip" "github.com/Infisical/infisical-merge/packages/util" kmip "github.com/infisical/infisical-kmip" + "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) var kmipCmd = &cobra.Command{ - Example: `infisical kmip`, + Example: ` infisical kmip + infisical kmip start --identity-client-id= --identity-client-secret= --hostnames-or-ips= + sudo infisical kmip systemd install --identity-client-id= --identity-client-secret= --hostnames-or-ips=`, Short: "Used to manage KMIP servers", Use: "kmip", DisableFlagsInUseLine: true, @@ -29,10 +36,17 @@ var kmipStartCmd = &cobra.Command{ Run: startKmipServer, } +const ( + INFISICAL_KMIP_LISTEN_ADDRESS_ENV_NAME = "INFISICAL_KMIP_LISTEN_ADDRESS" + INFISICAL_KMIP_SERVER_NAME_ENV_NAME = "INFISICAL_KMIP_SERVER_NAME" + INFISICAL_KMIP_CERTIFICATE_TTL_ENV_NAME = "INFISICAL_KMIP_CERTIFICATE_TTL" + INFISICAL_KMIP_HOSTNAMES_OR_IPS_ENV_NAME = "INFISICAL_KMIP_HOSTNAMES_OR_IPS" +) + func startKmipServer(cmd *cobra.Command, args []string) { - listenAddr, err := cmd.Flags().GetString("listen-address") + listenAddr, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "listen-address", []string{INFISICAL_KMIP_LISTEN_ADDRESS_ENV_NAME}, "localhost:5696") if err != nil { - util.HandleError(err, "Unable to parse flag") + util.HandleError(err, "Unable to parse listen address") } identityAuthMethod, err := cmd.Flags().GetString("identity-auth-method") @@ -50,7 +64,6 @@ func startKmipServer(cmd *cobra.Command, args []string) { if strategy == util.AuthStrategy.UNIVERSAL_AUTH { identityClientId, err = util.GetCmdFlagOrEnv(cmd, "identity-client-id", []string{util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME}) - if err != nil { util.HandleError(err, "Unable to parse identity client ID") } @@ -63,19 +76,19 @@ func startKmipServer(cmd *cobra.Command, args []string) { util.PrintErrorMessageAndExit(fmt.Sprintf("Unsupported login method: %s", identityAuthMethod)) } - serverName, err := cmd.Flags().GetString("server-name") + serverName, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "server-name", []string{INFISICAL_KMIP_SERVER_NAME_ENV_NAME}, "kmip-server") if err != nil { - util.HandleError(err, "Unable to parse flag") + util.HandleError(err, "Unable to parse server name") } - certificateTTL, err := cmd.Flags().GetString("certificate-ttl") + certificateTTL, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "certificate-ttl", []string{INFISICAL_KMIP_CERTIFICATE_TTL_ENV_NAME}, "1y") if err != nil { - util.HandleError(err, "Unable to parse flag") + util.HandleError(err, "Unable to parse certificate TTL") } - hostnamesOrIps, err := cmd.Flags().GetString("hostnames-or-ips") + hostnamesOrIps, err := util.GetCmdFlagOrEnv(cmd, "hostnames-or-ips", []string{INFISICAL_KMIP_HOSTNAMES_OR_IPS_ENV_NAME}) if err != nil { - util.HandleError(err, "Unable to parse flag") + util.HandleError(err, "Unable to parse hostnames or IPs") } kmip.StartServer(kmip.ServerConfig{ @@ -89,15 +102,128 @@ func startKmipServer(cmd *cobra.Command, args []string) { }) } +var kmipSystemdCmd = &cobra.Command{ + Use: "systemd", + Short: "Manage systemd service for Infisical KMIP server", + Long: "Manage systemd service for Infisical KMIP server. Use 'systemd install' to install and enable the service.", + Example: ` sudo infisical kmip systemd install --identity-client-id= --identity-client-secret= --hostnames-or-ips= + sudo infisical kmip systemd uninstall`, + DisableFlagsInUseLine: true, + Args: cobra.NoArgs, +} + +var kmipSystemdInstallCmd = &cobra.Command{ + Use: "install", + Short: "Install and enable systemd service for the KMIP server (requires sudo)", + Long: "Install and enable systemd service for the KMIP server. Must be run with sudo on Linux.", + Example: "sudo infisical kmip systemd install --identity-client-id= --identity-client-secret= --hostnames-or-ips=", + DisableFlagsInUseLine: true, + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + if runtime.GOOS != "linux" { + util.HandleError(fmt.Errorf("systemd service installation is only supported on Linux")) + } + + if os.Geteuid() != 0 { + util.HandleError(fmt.Errorf("systemd service installation requires root/sudo privileges")) + } + + identityClientId, err := util.GetCmdFlagOrEnv(cmd, "identity-client-id", []string{util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME}) + if err != nil { + util.HandleError(err, "Unable to parse identity client ID") + } + + identityClientSecret, err := util.GetCmdFlagOrEnv(cmd, "identity-client-secret", []string{util.INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_NAME}) + if err != nil { + util.HandleError(err, "Unable to parse identity client secret") + } + + domain, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "domain", []string{util.INFISICAL_API_URL_ENV_NAME}, "") + if err != nil { + util.HandleError(err, "Unable to parse domain") + } + + listenAddress, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "listen-address", []string{INFISICAL_KMIP_LISTEN_ADDRESS_ENV_NAME}, "localhost:5696") + if err != nil { + util.HandleError(err, "Unable to parse listen address") + } + + serverName, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "server-name", []string{INFISICAL_KMIP_SERVER_NAME_ENV_NAME}, "kmip-server") + if err != nil { + util.HandleError(err, "Unable to parse server name") + } + + certificateTTL, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "certificate-ttl", []string{INFISICAL_KMIP_CERTIFICATE_TTL_ENV_NAME}, "1y") + if err != nil { + util.HandleError(err, "Unable to parse certificate TTL") + } + + hostnamesOrIps, err := util.GetCmdFlagOrEnv(cmd, "hostnames-or-ips", []string{INFISICAL_KMIP_HOSTNAMES_OR_IPS_ENV_NAME}) + if err != nil { + util.HandleError(err, "Unable to parse hostnames or IPs") + } + + err = localkmip.InstallKmipSystemdService(identityClientId, identityClientSecret, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps) + if err != nil { + util.HandleError(err, "Failed to install systemd service") + } + + enableCmd := exec.Command("systemctl", "enable", "infisical-kmip") + if err := enableCmd.Run(); err != nil { + util.HandleError(err, "Failed to enable systemd service") + } + + log.Info().Msg("Successfully installed and enabled infisical-kmip service") + log.Info().Msg("To start the service, run: sudo systemctl start infisical-kmip") + }, +} + +var kmipSystemdUninstallCmd = &cobra.Command{ + Use: "uninstall", + Short: "Uninstall and remove systemd service for the KMIP server (requires sudo)", + Long: "Uninstall and remove systemd service for the KMIP server. Must be run with sudo on Linux.", + Example: "sudo infisical kmip systemd uninstall", + DisableFlagsInUseLine: true, + Args: cobra.NoArgs, + Run: func(cmd *cobra.Command, args []string) { + if runtime.GOOS != "linux" { + util.HandleError(fmt.Errorf("systemd service installation is only supported on Linux")) + } + + if os.Geteuid() != 0 { + util.HandleError(fmt.Errorf("systemd service uninstallation requires root/sudo privileges")) + } + + if err := localkmip.UninstallKmipSystemdService(); err != nil { + util.HandleError(err, "Failed to uninstall systemd service") + } + }, +} + func init() { + // KMIP start command flags kmipStartCmd.Flags().String("listen-address", "localhost:5696", "The address for the KMIP server to listen on. Defaults to localhost:5696") kmipStartCmd.Flags().String("identity-auth-method", string(util.AuthStrategy.UNIVERSAL_AUTH), "The auth method to use for authenticating the machine identity. Defaults to universal-auth.") kmipStartCmd.Flags().String("identity-client-id", "", "Universal auth client ID of machine identity") kmipStartCmd.Flags().String("identity-client-secret", "", "Universal auth client secret of machine identity") - kmipStartCmd.Flags().String("server-name", "kmip-server", "The name of the KMIP server") - kmipStartCmd.Flags().String("certificate-ttl", "1y", "The TTL duration for the server certificate") + kmipStartCmd.Flags().String("server-name", "kmip-server", "The name of the KMIP server. Defaults to kmip-server") + kmipStartCmd.Flags().String("certificate-ttl", "1y", "The TTL duration for the server certificate. Defaults to 1y") kmipStartCmd.Flags().String("hostnames-or-ips", "", "Comma-separated list of hostnames or IPs") + // KMIP systemd install command flags + kmipSystemdInstallCmd.Flags().String("identity-client-id", "", "Universal auth client ID of machine identity") + kmipSystemdInstallCmd.Flags().String("identity-client-secret", "", "Universal auth client secret of machine identity") + kmipSystemdInstallCmd.Flags().String("domain", "", "Domain of your self-hosted Infisical instance") + kmipSystemdInstallCmd.Flags().String("listen-address", "", "The address for the KMIP server to listen on") + kmipSystemdInstallCmd.Flags().String("server-name", "", "The name of the KMIP server") + kmipSystemdInstallCmd.Flags().String("certificate-ttl", "", "The TTL duration for the server certificate") + kmipSystemdInstallCmd.Flags().String("hostnames-or-ips", "", "Comma-separated list of hostnames or IPs") + + // Wire up command hierarchy + kmipSystemdCmd.AddCommand(kmipSystemdInstallCmd) + kmipSystemdCmd.AddCommand(kmipSystemdUninstallCmd) + kmipCmd.AddCommand(kmipStartCmd) + kmipCmd.AddCommand(kmipSystemdCmd) RootCmd.AddCommand(kmipCmd) } diff --git a/packages/kmip/systemd.go b/packages/kmip/systemd.go new file mode 100644 index 00000000..0367bcb4 --- /dev/null +++ b/packages/kmip/systemd.go @@ -0,0 +1,130 @@ +package kmip + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + + "github.com/rs/zerolog/log" +) + +const systemdServiceTemplate = `[Unit] +Description=Infisical KMIP Server Service +After=network.target + +[Service] +Type=simple +EnvironmentFile=/etc/infisical/kmip.conf +ExecStart=infisical kmip start +Restart=on-failure +InaccessibleDirectories=/home +PrivateTmp=yes +LimitCORE=infinity +LimitNOFILE=1000000 +LimitNPROC=60000 +LimitRTPRIO=infinity +LimitRTTIME=7000000 + +[Install] +WantedBy=multi-user.target +` + +func InstallKmipSystemdService(clientId, clientSecret, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps string) error { + if runtime.GOOS != "linux" { + log.Info().Msg("Skipping systemd service installation - not on Linux") + return nil + } + + if os.Geteuid() != 0 { + log.Info().Msg("Skipping systemd service installation - not running as root/sudo") + return nil + } + + configDir := "/etc/infisical" + if err := os.MkdirAll(configDir, 0755); err != nil { + return fmt.Errorf("failed to create config directory: %v", err) + } + + configContent := fmt.Sprintf("INFISICAL_UNIVERSAL_AUTH_CLIENT_ID=%s\n", clientId) + configContent += fmt.Sprintf("INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET=%s\n", clientSecret) + + if domain != "" { + configContent += fmt.Sprintf("INFISICAL_API_URL=%s\n", domain) + } + if listenAddress != "" { + configContent += fmt.Sprintf("INFISICAL_KMIP_LISTEN_ADDRESS=%s\n", listenAddress) + } + if serverName != "" { + configContent += fmt.Sprintf("INFISICAL_KMIP_SERVER_NAME=%s\n", serverName) + } + if certificateTTL != "" { + configContent += fmt.Sprintf("INFISICAL_KMIP_CERTIFICATE_TTL=%s\n", certificateTTL) + } + if hostnamesOrIps != "" { + configContent += fmt.Sprintf("INFISICAL_KMIP_HOSTNAMES_OR_IPS=%s\n", hostnamesOrIps) + } + + configPath := filepath.Join(configDir, "kmip.conf") + if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil { + return fmt.Errorf("failed to write config file: %v", err) + } + + servicePath := "/etc/systemd/system/infisical-kmip.service" + if err := os.WriteFile(servicePath, []byte(systemdServiceTemplate), 0644); err != nil { + return fmt.Errorf("failed to write systemd service file: %v", err) + } + + reloadCmd := exec.Command("systemctl", "daemon-reload") + if err := reloadCmd.Run(); err != nil { + return fmt.Errorf("failed to reload systemd: %v", err) + } + + return nil +} + +func UninstallKmipSystemdService() error { + if runtime.GOOS != "linux" { + log.Info().Msg("Skipping systemd service uninstallation - not on Linux") + return nil + } + + if os.Geteuid() != 0 { + log.Info().Msg("Skipping systemd service uninstallation - not running as root/sudo") + return nil + } + + // Stop the service if it's running + stopCmd := exec.Command("systemctl", "stop", "infisical-kmip") + if err := stopCmd.Run(); err != nil { + log.Warn().Msgf("Failed to stop service: %v", err) + } + + // Disable the service + disableCmd := exec.Command("systemctl", "disable", "infisical-kmip") + if err := disableCmd.Run(); err != nil { + log.Warn().Msgf("Failed to disable service: %v", err) + } + + // Remove the service file + servicePath := "/etc/systemd/system/infisical-kmip.service" + if err := os.Remove(servicePath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove systemd service file: %v", err) + } + + // Remove the configuration file + configPath := "/etc/infisical/kmip.conf" + if err := os.Remove(configPath); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to remove config file: %v", err) + } + + // Reload systemd to apply changes + reloadCmd := exec.Command("systemctl", "daemon-reload") + if err := reloadCmd.Run(); err != nil { + return fmt.Errorf("failed to reload systemd: %v", err) + } + + log.Info().Msg("Successfully uninstalled Infisical KMIP systemd service") + return nil +} diff --git a/packages/util/constants.go b/packages/util/constants.go index 57a1cddb..77d907c4 100644 --- a/packages/util/constants.go +++ b/packages/util/constants.go @@ -48,6 +48,7 @@ const ( // Generic env variable used for auth methods that require a machine identity ID INFISICAL_MACHINE_IDENTITY_ID_NAME = "INFISICAL_MACHINE_IDENTITY_ID" + INFISICAL_API_URL_ENV_NAME = "INFISICAL_API_URL" SECRET_TYPE_PERSONAL = "personal" SECRET_TYPE_SHARED = "shared"