From d4e1e745e4e6b028b4c92450961891c710bed610 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Thu, 4 Jun 2026 10:01:51 -0400 Subject: [PATCH 01/12] feat(kmip): add token and AWS enrollment for KMIP server Adds --enroll-method=token|aws to 'kmip start' and 'kmip systemd install', persisting and reusing the enrollment access token, alongside the existing machine-identity flow. --- packages/api/api.go | 21 +++ packages/api/model.go | 15 +++ packages/cmd/kmip.go | 267 +++++++++++++++++++++++++++++++------- packages/kmip/aws_auth.go | 75 +++++++++++ packages/kmip/enroll.go | 143 ++++++++++++++++++++ packages/kmip/systemd.go | 83 +++++++++--- 6 files changed, 535 insertions(+), 69 deletions(-) create mode 100644 packages/kmip/aws_auth.go create mode 100644 packages/kmip/enroll.go diff --git a/packages/api/api.go b/packages/api/api.go index 30d6e0d7..a2485953 100644 --- a/packages/api/api.go +++ b/packages/api/api.go @@ -65,6 +65,7 @@ const ( operationCallOrgRelayHeartBeat = "CallOrgRelayHeartBeat" operationCallInstanceRelayHeartBeat = "CallInstanceRelayHeartBeat" operationCallRelayLogin = "CallRelayLogin" + operationCallKmipServerLogin = "CallKmipServerLogin" operationCallRelayConnect = "CallRelayConnect" operationCallRelayHeartbeatV2 = "CallRelayHeartbeatV2" operationCallIssueCertificate = "CallIssueCertificate" @@ -963,6 +964,26 @@ func CallRelayLogin(httpClient *resty.Client, request RelayLoginRequest) (RelayL return resBody, nil } +func CallKmipServerLogin(httpClient *resty.Client, request KmipServerLoginRequest) (KmipServerLoginResponse, error) { + var resBody KmipServerLoginResponse + response, err := httpClient. + R(). + SetResult(&resBody). + SetHeader("User-Agent", USER_AGENT). + SetBody(request). + Post(fmt.Sprintf("%v/v1/kmip-servers/login", config.INFISICAL_URL)) + + if err != nil { + return KmipServerLoginResponse{}, NewGenericRequestError(operationCallKmipServerLogin, err) + } + + if response.IsError() { + return KmipServerLoginResponse{}, NewAPIErrorWithResponse(operationCallKmipServerLogin, response, nil) + } + + return resBody, nil +} + func CallRelayConnect(httpClient *resty.Client) (RelayConnectResponse, error) { var resBody RelayConnectResponse response, err := httpClient. diff --git a/packages/api/model.go b/packages/api/model.go index 75d9bcf0..ce407ae5 100644 --- a/packages/api/model.go +++ b/packages/api/model.go @@ -1020,6 +1020,21 @@ type RelayLoginResponse struct { TokenType string `json:"tokenType"` } +type KmipServerLoginRequest struct { + Method string `json:"method"` + Token string `json:"token,omitempty"` + KmipServerID string `json:"kmipServerId,omitempty"` + HTTPRequestMethod string `json:"iamHttpRequestMethod,omitempty"` + IamRequestBody string `json:"iamRequestBody,omitempty"` + IamRequestHeaders string `json:"iamRequestHeaders,omitempty"` +} + +type KmipServerLoginResponse struct { + AccessToken string `json:"accessToken"` + KmipServerID string `json:"kmipServerId"` + TokenType string `json:"tokenType"` +} + type RelayConnectResponse struct { RelayID string `json:"relayId"` PKI struct { diff --git a/packages/cmd/kmip.go b/packages/cmd/kmip.go index cc1851f7..576d32a4 100644 --- a/packages/cmd/kmip.go +++ b/packages/cmd/kmip.go @@ -4,11 +4,13 @@ Copyright (c) 2023 Infisical Inc. package cmd import ( + "errors" "fmt" "os" "os/exec" "runtime" + "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/config" localkmip "github.com/Infisical/infisical-merge/packages/kmip" "github.com/Infisical/infisical-merge/packages/util" @@ -19,8 +21,8 @@ import ( var kmipCmd = &cobra.Command{ 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=`, + infisical kmip start --enroll-method=token --token= --server-name= --domain= + sudo infisical kmip systemd install --enroll-method=token --token= --server-name= --domain=`, Short: "Used to manage KMIP servers", Use: "kmip", DisableFlagsInUseLine: true, @@ -49,64 +51,169 @@ func startKmipServer(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse listen address") } - identityAuthMethod, err := cmd.Flags().GetString("identity-auth-method") + 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") } - authMethodValid, strategy := util.IsAuthMethodValid(identityAuthMethod, false) - if !authMethodValid { - util.PrintErrorMessageAndExit(fmt.Sprintf("Invalid login method: %s", identityAuthMethod)) + 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") } - var identityClientId string - var identityClientSecret string + 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") + } - 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") + enrollMethod, _ := cmd.Flags().GetString("enroll-method") + if enrollMethod == "" { + enrollMethod = os.Getenv(localkmip.INFISICAL_KMIP_ENROLL_METHOD_KEY) + } + if enrollMethod != "" && enrollMethod != localkmip.EnrollMethodToken && enrollMethod != localkmip.EnrollMethodAws { + util.HandleError(fmt.Errorf("invalid --enroll-method %q: supported values are %q and %q", + enrollMethod, localkmip.EnrollMethodToken, localkmip.EnrollMethodAws)) + } + + // Resolve the Infisical domain: explicit flag, then the value stored at enrollment, then the logged-in user's domain. + if flagDomain, _ := cmd.Flags().GetString("domain"); flagDomain != "" { + config.INFISICAL_URL = util.AppendAPIEndpoint(flagDomain) + } else if storedDomain, _ := localkmip.LoadStoredDomain(serverName); storedDomain != "" { + config.INFISICAL_URL = util.AppendAPIEndpoint(storedDomain) + } else if configFile, cfgErr := util.GetConfigFile(); cfgErr == nil && configFile.LoggedInUserDomain != "" { + config.INFISICAL_URL = util.AppendAPIEndpoint(configFile.LoggedInUserDomain) + } + + serverConfig := kmip.ServerConfig{ + Addr: listenAddr, + InfisicalBaseAPIURL: config.INFISICAL_URL, + ServerName: serverName, + CertificateTTL: certificateTTL, + HostnamesOrIps: hostnamesOrIps, + } + + if enrollMethod != "" { + serverConfig.AccessToken = enrollKmipServer(cmd, enrollMethod, serverName) + } else { + serverConfig.IdentityClientId, serverConfig.IdentityClientSecret = resolveKmipIdentityCredentials(cmd) + } + + kmip.StartServer(serverConfig) +} + +// enrollKmipServer obtains a KMIP server access token via token or AWS enrollment, +// persisting the relevant state under the KMIP server's config file. +func enrollKmipServer(cmd *cobra.Command, enrollMethod, serverName string) string { + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "unable to create HTTP client") + } + + if enrollMethod == localkmip.EnrollMethodAws { + kmipServerID, _ := cmd.Flags().GetString("kmip-server-id") + if kmipServerID == "" { + kmipServerID = os.Getenv(localkmip.INFISICAL_KMIP_SERVER_ID_KEY) + } + if kmipServerID == "" { + stored, _ := localkmip.LoadStoredServerID(serverName) + kmipServerID = stored + } + if kmipServerID == "" { + util.HandleError(errors.New("--kmip-server-id is required when --enroll-method=aws")) } - identityClientSecret, err = util.GetCmdFlagOrEnv(cmd, "identity-client-secret", []string{util.INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_NAME}) + log.Info().Msg("Authenticating KMIP server via AWS Auth (STS GetCallerIdentity)...") + accessToken, err := localkmip.LoginKmipServerWithAws(cmd.Context(), httpClient, kmipServerID) if err != nil { - util.HandleError(err, "Unable to parse identity client secret") + util.HandleError(err, "AWS Auth login failed") } - } else { - util.PrintErrorMessageAndExit(fmt.Sprintf("Unsupported login method: %s", identityAuthMethod)) + + if err := localkmip.SaveServerID(serverName, kmipServerID); err != nil { + util.HandleError(err, "failed to save KMIP server id to config") + } + if err := localkmip.SaveDomain(serverName, config.INFISICAL_URL); err != nil { + util.HandleError(err, "failed to save domain to config") + } + + log.Info().Msgf("KMIP server authenticated via AWS Auth. State saved to %s", localkmip.GetConfPathDisplay(serverName)) + return accessToken } - serverName, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "server-name", []string{INFISICAL_KMIP_SERVER_NAME_ENV_NAME}, "kmip-server") + // Enrollment token path + enrollToken, _ := cmd.Flags().GetString("token") + + // Reuse the stored access token when no new token is supplied, or when the supplied token + // matches the one we already enrolled with. Enrollment tokens are single-use and short-lived, + // so a restarting server relies on the long-lived access token persisted at enrollment. + storedEnrollToken, _ := localkmip.LoadStoredEnrollmentToken(serverName) + if enrollToken == "" || (storedEnrollToken != "" && storedEnrollToken == enrollToken) { + storedAccessToken, err := localkmip.LoadStoredAccessToken(serverName) + if err == nil && storedAccessToken != "" { + log.Info().Msg("Reusing stored KMIP server access token.") + return storedAccessToken + } + if enrollToken == "" { + util.HandleError(errors.New("--token is required when --enroll-method=token and no access token is stored")) + } + } + + log.Info().Msg("Enrolling KMIP server with enrollment token...") + enrollResp, err := api.CallKmipServerLogin(httpClient, api.KmipServerLoginRequest{ + Method: localkmip.EnrollMethodToken, + Token: enrollToken, + }) if err != nil { - util.HandleError(err, "Unable to parse server name") + util.HandleError(err, "enrollment failed") } - certificateTTL, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "certificate-ttl", []string{INFISICAL_KMIP_CERTIFICATE_TTL_ENV_NAME}, "1y") + if err := localkmip.SaveAccessToken(serverName, enrollResp.AccessToken); err != nil { + util.HandleError(err, "failed to save KMIP server access token") + } + if err := localkmip.SaveEnrollmentToken(serverName, enrollToken); err != nil { + util.HandleError(err, "failed to save enrollment token to config") + } + if err := localkmip.SaveDomain(serverName, config.INFISICAL_URL); err != nil { + util.HandleError(err, "failed to save domain to config") + } + + log.Info().Msgf("KMIP server enrolled successfully. Access token saved to %s", localkmip.GetConfPathDisplay(serverName)) + return enrollResp.AccessToken +} + +// resolveKmipIdentityCredentials parses the legacy machine-identity credentials. +func resolveKmipIdentityCredentials(cmd *cobra.Command) (string, string) { + identityAuthMethod, err := cmd.Flags().GetString("identity-auth-method") if err != nil { - util.HandleError(err, "Unable to parse certificate TTL") + util.HandleError(err, "Unable to parse flag") } - hostnamesOrIps, err := util.GetCmdFlagOrEnv(cmd, "hostnames-or-ips", []string{INFISICAL_KMIP_HOSTNAMES_OR_IPS_ENV_NAME}) + authMethodValid, strategy := util.IsAuthMethodValid(identityAuthMethod, false) + if !authMethodValid { + util.PrintErrorMessageAndExit(fmt.Sprintf("Invalid login method: %s", identityAuthMethod)) + } + + if strategy != util.AuthStrategy.UNIVERSAL_AUTH { + util.PrintErrorMessageAndExit(fmt.Sprintf("Unsupported login method: %s", identityAuthMethod)) + } + + 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 hostnames or IPs") + util.HandleError(err, "Unable to parse identity client ID") } - kmip.StartServer(kmip.ServerConfig{ - Addr: listenAddr, - InfisicalBaseAPIURL: config.INFISICAL_URL, - IdentityClientId: identityClientId, - IdentityClientSecret: identityClientSecret, - ServerName: serverName, - CertificateTTL: certificateTTL, - HostnamesOrIps: hostnamesOrIps, - }) + 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") + } + + return identityClientId, identityClientSecret } 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= + Example: ` sudo infisical kmip systemd install --enroll-method=token --token= --server-name= --domain= sudo infisical kmip systemd uninstall`, DisableFlagsInUseLine: true, Args: cobra.NoArgs, @@ -116,7 +223,7 @@ 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=", + Example: "sudo infisical kmip systemd install --enroll-method=token --token= --server-name= --domain=", DisableFlagsInUseLine: true, Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { @@ -128,21 +235,16 @@ var kmipSystemdInstallCmd = &cobra.Command{ 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") } + // Point the API client at the configured instance before any enrollment call is made. + if domain != "" { + config.INFISICAL_URL = util.AppendAPIEndpoint(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") @@ -163,9 +265,65 @@ var kmipSystemdInstallCmd = &cobra.Command{ 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") + enrollMethod, _ := cmd.Flags().GetString("enroll-method") + if enrollMethod == "" { + enrollMethod = os.Getenv(localkmip.INFISICAL_KMIP_ENROLL_METHOD_KEY) + } + if enrollMethod != "" && enrollMethod != localkmip.EnrollMethodToken && enrollMethod != localkmip.EnrollMethodAws { + util.HandleError(fmt.Errorf("invalid --enroll-method %q: supported values are %q and %q", + enrollMethod, localkmip.EnrollMethodToken, localkmip.EnrollMethodAws)) + } + + switch enrollMethod { + case localkmip.EnrollMethodToken: + enrollToken, _ := cmd.Flags().GetString("token") + if enrollToken == "" { + util.HandleError(errors.New("--token is required when --enroll-method=token")) + } + + httpClient, clientErr := util.GetRestyClientWithCustomHeaders() + if clientErr != nil { + util.HandleError(clientErr, "unable to create HTTP client") + } + + log.Info().Msg("Enrolling KMIP server with enrollment token...") + enrollResp, enrollErr := api.CallKmipServerLogin(httpClient, api.KmipServerLoginRequest{ + Method: localkmip.EnrollMethodToken, + Token: enrollToken, + }) + if enrollErr != nil { + util.HandleError(enrollErr, "enrollment failed") + } + + if err := localkmip.InstallEnrolledKmipSystemdService(enrollResp.AccessToken, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps); err != nil { + util.HandleError(err, "Failed to install systemd service") + } + case localkmip.EnrollMethodAws: + kmipServerID, _ := cmd.Flags().GetString("kmip-server-id") + if kmipServerID == "" { + kmipServerID = os.Getenv(localkmip.INFISICAL_KMIP_SERVER_ID_KEY) + } + if kmipServerID == "" { + util.HandleError(errors.New("--kmip-server-id is required when --enroll-method=aws")) + } + + if err := localkmip.InstallAwsAuthKmipSystemdService(kmipServerID, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps); err != nil { + util.HandleError(err, "Failed to install systemd service") + } + default: + identityClientId, idErr := util.GetCmdFlagOrEnv(cmd, "identity-client-id", []string{util.INFISICAL_UNIVERSAL_AUTH_CLIENT_ID_NAME}) + if idErr != nil { + util.HandleError(idErr, "Unable to parse identity client ID") + } + + identityClientSecret, secretErr := util.GetCmdFlagOrEnv(cmd, "identity-client-secret", []string{util.INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET_NAME}) + if secretErr != nil { + util.HandleError(secretErr, "Unable to parse identity client secret") + } + + if err := localkmip.InstallKmipSystemdService(identityClientId, identityClientSecret, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps); err != nil { + util.HandleError(err, "Failed to install systemd service") + } } enableCmd := exec.Command("systemctl", "enable", "infisical-kmip") @@ -202,15 +360,24 @@ var kmipSystemdUninstallCmd = &cobra.Command{ 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") + // Defaults are applied in startKmipServer so the corresponding env vars (set by the systemd + // EnvironmentFile) take precedence; a non-empty flag default here would always shadow them. + kmipStartCmd.Flags().String("listen-address", "", "The address for the KMIP server to listen on. Defaults to localhost:5696") + kmipStartCmd.Flags().String("enroll-method", "", "Enrollment method for the KMIP server: 'token' or 'aws'. When set, machine-identity flags are ignored.") + kmipStartCmd.Flags().String("token", "", "Enrollment token (when --enroll-method=token)") + kmipStartCmd.Flags().String("kmip-server-id", "", "KMIP server ID (when --enroll-method=aws)") + kmipStartCmd.Flags().String("domain", "", "Domain of your Infisical instance") 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. Defaults to kmip-server") - kmipStartCmd.Flags().String("certificate-ttl", "1y", "The TTL duration for the server certificate. Defaults to 1y") + kmipStartCmd.Flags().String("server-name", "", "The name of the KMIP server. Defaults to kmip-server") + kmipStartCmd.Flags().String("certificate-ttl", "", "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("enroll-method", "", "Enrollment method for the KMIP server: 'token' or 'aws'. When set, machine-identity flags are ignored.") + kmipSystemdInstallCmd.Flags().String("token", "", "Enrollment token (when --enroll-method=token)") + kmipSystemdInstallCmd.Flags().String("kmip-server-id", "", "KMIP server ID (when --enroll-method=aws)") 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") diff --git a/packages/kmip/aws_auth.go b/packages/kmip/aws_auth.go new file mode 100644 index 00000000..00421c06 --- /dev/null +++ b/packages/kmip/aws_auth.go @@ -0,0 +1,75 @@ +package kmip + +import ( + "context" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "github.com/Infisical/infisical-merge/packages/api" + v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4" + "github.com/go-resty/resty/v2" + infisicalSdkUtil "github.com/infisical/go-sdk/packages/util" +) + +func LoginKmipServerWithAws(ctx context.Context, httpClient *resty.Client, kmipServerID string) (string, error) { + if kmipServerID == "" { + return "", errors.New("--kmip-server-id is required when --enroll-method=aws") + } + + awsCredentials, awsRegion, err := infisicalSdkUtil.RetrieveAwsCredentials() + if err != nil { + return "", fmt.Errorf("unable to retrieve AWS credentials: %w", err) + } + + iamRequestURL := fmt.Sprintf("https://sts.%s.amazonaws.com/", awsRegion) + iamRequestBody := "Action=GetCallerIdentity&Version=2011-06-15" + + req, err := http.NewRequest(http.MethodPost, iamRequestURL, strings.NewReader(iamRequestBody)) + if err != nil { + return "", fmt.Errorf("error building STS request: %w", err) + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8") + + hash := sha256.New() + hash.Write([]byte(iamRequestBody)) + payloadHash := fmt.Sprintf("%x", hash.Sum(nil)) + + signer := v4.NewSigner() + if err := signer.SignHTTP(ctx, awsCredentials, req, payloadHash, "sts", awsRegion, time.Now()); err != nil { + return "", fmt.Errorf("error signing STS request: %w", err) + } + + headers := make(map[string]string) + for name, values := range req.Header { + if strings.ToLower(name) == "content-length" { + continue + } + headers[name] = values[0] + } + headers["Host"] = fmt.Sprintf("sts.%s.amazonaws.com", awsRegion) + + headersJSON, err := json.Marshal(headers) + if err != nil { + return "", fmt.Errorf("error marshalling headers: %w", err) + } + + resp, err := api.CallKmipServerLogin(httpClient, api.KmipServerLoginRequest{ + Method: EnrollMethodAws, + KmipServerID: kmipServerID, + HTTPRequestMethod: req.Method, + IamRequestBody: base64.StdEncoding.EncodeToString([]byte(iamRequestBody)), + IamRequestHeaders: base64.StdEncoding.EncodeToString(headersJSON), + }) + if err != nil { + return "", err + } + + return resp.AccessToken, nil +} diff --git a/packages/kmip/enroll.go b/packages/kmip/enroll.go new file mode 100644 index 00000000..f37318c3 --- /dev/null +++ b/packages/kmip/enroll.go @@ -0,0 +1,143 @@ +package kmip + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" +) + +const ( + EnrollMethodToken = "token" + EnrollMethodAws = "aws" + + INFISICAL_KMIP_ACCESS_TOKEN_KEY = "INFISICAL_KMIP_ACCESS_TOKEN" + INFISICAL_KMIP_DOMAIN_KEY = "INFISICAL_KMIP_DOMAIN" + INFISICAL_KMIP_ENROLLMENT_TOKEN_KEY = "INFISICAL_KMIP_ENROLLMENT_TOKEN" + INFISICAL_KMIP_SERVER_ID_KEY = "INFISICAL_KMIP_SERVER_ID" + INFISICAL_KMIP_ENROLL_METHOD_KEY = "INFISICAL_KMIP_ENROLL_METHOD" +) + +func kmipConfPath(name string) (string, error) { + if os.Geteuid() == 0 { + return filepath.Join("/etc/infisical/kmip", name+".conf"), nil + } + + homeDir, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("unable to determine home directory: %w", err) + } + + return filepath.Join(homeDir, ".infisical", "kmip", name+".conf"), nil +} + +func loadConfKey(name, key string) (string, error) { + confPath, err := kmipConfPath(name) + if err != nil { + return "", err + } + + data, err := os.ReadFile(confPath) + if os.IsNotExist(err) { + return "", nil + } + if err != nil { + return "", fmt.Errorf("failed to read kmip config: %w", err) + } + + prefix := key + "=" + for _, line := range strings.Split(string(data), "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, prefix) { + return strings.TrimPrefix(line, prefix), nil + } + } + + return "", nil +} + +func saveConfKey(name, key, value string) error { + confPath, err := kmipConfPath(name) + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(confPath), 0700); err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + var existingLines []string + data, err := os.ReadFile(confPath) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to read existing config: %w", err) + } + if err == nil { + prefix := key + "=" + for _, line := range strings.Split(string(data), "\n") { + trimmed := strings.TrimSpace(line) + if trimmed == "" || strings.HasPrefix(trimmed, prefix) { + continue + } + existingLines = append(existingLines, line) + } + } + + existingLines = append(existingLines, fmt.Sprintf("%s=%s", key, value)) + content := strings.Join(existingLines, "\n") + "\n" + + if err := os.WriteFile(confPath, []byte(content), 0600); err != nil { + return fmt.Errorf("failed to write kmip config: %w", err) + } + + return nil +} + +func LoadStoredAccessToken(name string) (string, error) { + if envToken := os.Getenv(INFISICAL_KMIP_ACCESS_TOKEN_KEY); envToken != "" { + return envToken, nil + } + return loadConfKey(name, INFISICAL_KMIP_ACCESS_TOKEN_KEY) +} + +func SaveAccessToken(name, token string) error { + return saveConfKey(name, INFISICAL_KMIP_ACCESS_TOKEN_KEY, token) +} + +func LoadStoredDomain(name string) (string, error) { + return loadConfKey(name, INFISICAL_KMIP_DOMAIN_KEY) +} + +func SaveDomain(name, domain string) error { + return saveConfKey(name, INFISICAL_KMIP_DOMAIN_KEY, domain) +} + +func LoadStoredEnrollmentToken(name string) (string, error) { + return loadConfKey(name, INFISICAL_KMIP_ENROLLMENT_TOKEN_KEY) +} + +func SaveEnrollmentToken(name, token string) error { + return saveConfKey(name, INFISICAL_KMIP_ENROLLMENT_TOKEN_KEY, token) +} + +func LoadStoredServerID(name string) (string, error) { + if envID := os.Getenv(INFISICAL_KMIP_SERVER_ID_KEY); envID != "" { + return envID, nil + } + return loadConfKey(name, INFISICAL_KMIP_SERVER_ID_KEY) +} + +func SaveServerID(name, kmipServerID string) error { + return saveConfKey(name, INFISICAL_KMIP_SERVER_ID_KEY, kmipServerID) +} + +func GetConfPathDisplay(name string) string { + path, err := kmipConfPath(name) + if err != nil { + if runtime.GOOS == "linux" { + return "/etc/infisical/kmip/" + name + ".conf" + } + return "~/.infisical/kmip/" + name + ".conf" + } + return path +} diff --git a/packages/kmip/systemd.go b/packages/kmip/systemd.go index 0367bcb4..f2488429 100644 --- a/packages/kmip/systemd.go +++ b/packages/kmip/systemd.go @@ -31,25 +31,8 @@ LimitRTTIME=7000000 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) - +// kmipCommonEnv appends the shared optional settings to a systemd environment file. +func kmipCommonEnv(configContent, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps string) string { if domain != "" { configContent += fmt.Sprintf("INFISICAL_API_URL=%s\n", domain) } @@ -65,6 +48,15 @@ func InstallKmipSystemdService(clientId, clientSecret, domain, listenAddress, se if hostnamesOrIps != "" { configContent += fmt.Sprintf("INFISICAL_KMIP_HOSTNAMES_OR_IPS=%s\n", hostnamesOrIps) } + return configContent +} + +// writeKmipSystemdService writes the environment file and service unit, then reloads systemd. +func writeKmipSystemdService(configContent string) error { + configDir := "/etc/infisical" + if err := os.MkdirAll(configDir, 0755); err != nil { + return fmt.Errorf("failed to create config directory: %v", err) + } configPath := filepath.Join(configDir, "kmip.conf") if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil { @@ -84,6 +76,59 @@ func InstallKmipSystemdService(clientId, clientSecret, domain, listenAddress, se return nil } +func systemdPreconditionsMet() bool { + if runtime.GOOS != "linux" { + log.Info().Msg("Skipping systemd service installation - not on Linux") + return false + } + if os.Geteuid() != 0 { + log.Info().Msg("Skipping systemd service installation - not running as root/sudo") + return false + } + return true +} + +// InstallEnrolledKmipSystemdService installs the service for the token enrollment method. +// The single-use enrollment token is exchanged for a long-lived access token before this call; +// the access token is persisted in the environment file and reused on every service restart. +func InstallEnrolledKmipSystemdService(accessToken, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps string) error { + if !systemdPreconditionsMet() { + return nil + } + + configContent := "INFISICAL_KMIP_ENROLL_METHOD=token\n" + configContent += fmt.Sprintf("INFISICAL_KMIP_ACCESS_TOKEN=%s\n", accessToken) + configContent = kmipCommonEnv(configContent, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps) + + return writeKmipSystemdService(configContent) +} + +// InstallAwsAuthKmipSystemdService installs the service for the AWS enrollment method. +// No token is persisted; the server re-authenticates via AWS STS on every service start. +func InstallAwsAuthKmipSystemdService(kmipServerID, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps string) error { + if !systemdPreconditionsMet() { + return nil + } + + configContent := "INFISICAL_KMIP_ENROLL_METHOD=aws\n" + configContent += fmt.Sprintf("INFISICAL_KMIP_SERVER_ID=%s\n", kmipServerID) + configContent = kmipCommonEnv(configContent, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps) + + return writeKmipSystemdService(configContent) +} + +func InstallKmipSystemdService(clientId, clientSecret, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps string) error { + if !systemdPreconditionsMet() { + return nil + } + + configContent := fmt.Sprintf("INFISICAL_UNIVERSAL_AUTH_CLIENT_ID=%s\n", clientId) + configContent += fmt.Sprintf("INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET=%s\n", clientSecret) + configContent = kmipCommonEnv(configContent, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps) + + return writeKmipSystemdService(configContent) +} + func UninstallKmipSystemdService() error { if runtime.GOOS != "linux" { log.Info().Msg("Skipping systemd service uninstallation - not on Linux") From 7b0619c449be2ef9b3772a9840ad4ce99fbb9182 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Thu, 4 Jun 2026 10:43:00 -0400 Subject: [PATCH 02/12] fix(kmip): clear error when re-enrolling with an already-used token --- packages/cmd/kmip.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/cmd/kmip.go b/packages/cmd/kmip.go index 576d32a4..219750e1 100644 --- a/packages/cmd/kmip.go +++ b/packages/cmd/kmip.go @@ -155,6 +155,10 @@ func enrollKmipServer(cmd *cobra.Command, enrollMethod, serverName string) strin if enrollToken == "" { util.HandleError(errors.New("--token is required when --enroll-method=token and no access token is stored")) } + // We're here because the supplied token matches the one already enrolled with, but no + // access token was persisted. Enrollment tokens are single-use, so re-submitting it would + // fail with an opaque error — surface a clear, actionable message instead of falling through. + util.HandleError(errors.New("this enrollment token has already been used and no access token is stored locally; generate a new enrollment token from the KMIP server's deploy command and retry")) } log.Info().Msg("Enrolling KMIP server with enrollment token...") From 26df9bf2ffb1124a1ab267583c5a863c31c13260 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Thu, 4 Jun 2026 19:36:48 -0400 Subject: [PATCH 03/12] chore(kmip): bump infisical-kmip to v0.3.18 Picks up the published release with the AccessToken field, so the CLI builds without the local replace directive. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 30bbe4a3..c9d5bb41 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/google/uuid v1.6.0 github.com/h2non/filetype v1.1.3 github.com/infisical/go-sdk v0.7.0 - github.com/infisical/infisical-kmip v0.3.17 + github.com/infisical/infisical-kmip v0.3.18 github.com/jackc/pgx/v5 v5.9.2 github.com/jcmturner/gokrb5/v8 v8.4.4 github.com/mattn/go-isatty v0.0.20 diff --git a/go.sum b/go.sum index e43fbce4..b7cefb2a 100644 --- a/go.sum +++ b/go.sum @@ -378,8 +378,8 @@ github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7P github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/infisical/go-sdk v0.7.0 h1:x9/1PczL+ioVD1jCp4LHQzPpBawatbmkjAC0S1OAtUA= github.com/infisical/go-sdk v0.7.0/go.mod h1:yEfXF+3YDDXiJ9zzJUSzW6me6XXPPEDK52fSU6JfpCA= -github.com/infisical/infisical-kmip v0.3.17 h1:5dBuyzHs+BxZD30JYBNufnoxRJNyPThL6lR4YPRWf4w= -github.com/infisical/infisical-kmip v0.3.17/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs= +github.com/infisical/infisical-kmip v0.3.18 h1:GNpbVtYI+l6Au9pgOGOs2HUVdwydPkXAl7HZXNzG4OM= +github.com/infisical/infisical-kmip v0.3.18/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= From 39ad62af2f5981afad12352656e8b6586f822431 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Thu, 4 Jun 2026 19:47:50 -0400 Subject: [PATCH 04/12] chore(kmip): point server login at /v1/kmip/servers/login --- packages/api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/api.go b/packages/api/api.go index a2485953..5e9c0aa8 100644 --- a/packages/api/api.go +++ b/packages/api/api.go @@ -971,7 +971,7 @@ func CallKmipServerLogin(httpClient *resty.Client, request KmipServerLoginReques SetResult(&resBody). SetHeader("User-Agent", USER_AGENT). SetBody(request). - Post(fmt.Sprintf("%v/v1/kmip-servers/login", config.INFISICAL_URL)) + Post(fmt.Sprintf("%v/v1/kmip/servers/login", config.INFISICAL_URL)) if err != nil { return KmipServerLoginResponse{}, NewGenericRequestError(operationCallKmipServerLogin, err) From e9470321766a5172fd2abcf55851fbacfe8fdc6d Mon Sep 17 00:00:00 2001 From: bernie-g Date: Thu, 4 Jun 2026 20:41:01 -0400 Subject: [PATCH 05/12] feat(kmip): make --hostnames-or-ips optional for enrollment servers For enrollment-based KMIP servers the cert config (hostnames/IPs, TTL, common name, key algorithm) is configured in the UI and read by the daemon via /kmip/servers/connect, so --hostnames-or-ips is no longer required at launch. --- packages/cmd/kmip.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cmd/kmip.go b/packages/cmd/kmip.go index 219750e1..f6897739 100644 --- a/packages/cmd/kmip.go +++ b/packages/cmd/kmip.go @@ -61,7 +61,7 @@ func startKmipServer(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse certificate TTL") } - hostnamesOrIps, err := util.GetCmdFlagOrEnv(cmd, "hostnames-or-ips", []string{INFISICAL_KMIP_HOSTNAMES_OR_IPS_ENV_NAME}) + hostnamesOrIps, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "hostnames-or-ips", []string{INFISICAL_KMIP_HOSTNAMES_OR_IPS_ENV_NAME}, "") if err != nil { util.HandleError(err, "Unable to parse hostnames or IPs") } @@ -264,7 +264,7 @@ var kmipSystemdInstallCmd = &cobra.Command{ util.HandleError(err, "Unable to parse certificate TTL") } - hostnamesOrIps, err := util.GetCmdFlagOrEnv(cmd, "hostnames-or-ips", []string{INFISICAL_KMIP_HOSTNAMES_OR_IPS_ENV_NAME}) + hostnamesOrIps, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "hostnames-or-ips", []string{INFISICAL_KMIP_HOSTNAMES_OR_IPS_ENV_NAME}, "") if err != nil { util.HandleError(err, "Unable to parse hostnames or IPs") } From 1c81639957fcfaccef7a202e63358c755a6f24e2 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Fri, 5 Jun 2026 10:28:39 -0400 Subject: [PATCH 06/12] feat(kmip): accept server name as a required positional argument infisical kmip start/systemd install now take as a positional argument, e.g. 'infisical kmip start my-server'. The --server-name flag and INFISICAL_KMIP_SERVER_NAME env var still work as alternatives; the name is required (no longer defaults to kmip-server). --- packages/cmd/kmip.go | 51 +++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/packages/cmd/kmip.go b/packages/cmd/kmip.go index f6897739..1297d680 100644 --- a/packages/cmd/kmip.go +++ b/packages/cmd/kmip.go @@ -21,8 +21,8 @@ import ( var kmipCmd = &cobra.Command{ Example: ` infisical kmip - infisical kmip start --enroll-method=token --token= --server-name= --domain= - sudo infisical kmip systemd install --enroll-method=token --token= --server-name= --domain=`, + infisical kmip start --enroll-method=token --token= --domain= + sudo infisical kmip systemd install --enroll-method=token --token= --domain=`, Short: "Used to manage KMIP servers", Use: "kmip", DisableFlagsInUseLine: true, @@ -30,14 +30,33 @@ var kmipCmd = &cobra.Command{ } var kmipStartCmd = &cobra.Command{ - Example: `infisical kmip start`, + Example: `infisical kmip start --enroll-method=token --token= --domain=`, Short: "Used to start a KMIP server", - Use: "start", + Use: "start [server-name]", DisableFlagsInUseLine: true, - Args: cobra.NoArgs, + Args: cobra.MaximumNArgs(1), Run: startKmipServer, } +// resolveKmipServerName resolves the KMIP server name from (in order) the positional argument, +// the --server-name flag, or the INFISICAL_KMIP_SERVER_NAME env var. The name is required. +func resolveKmipServerName(cmd *cobra.Command, args []string) string { + if len(args) > 0 && args[0] != "" { + return args[0] + } + + serverName, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "server-name", []string{INFISICAL_KMIP_SERVER_NAME_ENV_NAME}, "") + if err != nil { + util.HandleError(err, "Unable to parse server name") + } + + if serverName == "" { + util.HandleError(errors.New("KMIP server name is required: pass it as an argument (e.g. 'infisical kmip start my-server') or via --server-name / INFISICAL_KMIP_SERVER_NAME")) + } + + return serverName +} + const ( INFISICAL_KMIP_LISTEN_ADDRESS_ENV_NAME = "INFISICAL_KMIP_LISTEN_ADDRESS" INFISICAL_KMIP_SERVER_NAME_ENV_NAME = "INFISICAL_KMIP_SERVER_NAME" @@ -51,10 +70,7 @@ func startKmipServer(cmd *cobra.Command, args []string) { 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") - } + serverName := resolveKmipServerName(cmd, args) certificateTTL, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "certificate-ttl", []string{INFISICAL_KMIP_CERTIFICATE_TTL_ENV_NAME}, "1y") if err != nil { @@ -217,19 +233,19 @@ 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 --enroll-method=token --token= --server-name= --domain= + Example: ` sudo infisical kmip systemd install --enroll-method=token --token= --domain= sudo infisical kmip systemd uninstall`, DisableFlagsInUseLine: true, Args: cobra.NoArgs, } var kmipSystemdInstallCmd = &cobra.Command{ - Use: "install", + Use: "install [server-name]", 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 --enroll-method=token --token= --server-name= --domain=", + Example: "sudo infisical kmip systemd install --enroll-method=token --token= --domain=", DisableFlagsInUseLine: true, - Args: cobra.NoArgs, + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { if runtime.GOOS != "linux" { util.HandleError(fmt.Errorf("systemd service installation is only supported on Linux")) @@ -254,10 +270,7 @@ var kmipSystemdInstallCmd = &cobra.Command{ 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") - } + serverName := resolveKmipServerName(cmd, args) certificateTTL, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "certificate-ttl", []string{INFISICAL_KMIP_CERTIFICATE_TTL_ENV_NAME}, "1y") if err != nil { @@ -374,7 +387,7 @@ func init() { 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", "", "The name of the KMIP server. Defaults to kmip-server") + kmipStartCmd.Flags().String("server-name", "", "The name of the KMIP server. Alternative to passing it as a positional argument; required if not passed positionally") kmipStartCmd.Flags().String("certificate-ttl", "", "The TTL duration for the server certificate. Defaults to 1y") kmipStartCmd.Flags().String("hostnames-or-ips", "", "Comma-separated list of hostnames or IPs") @@ -386,7 +399,7 @@ func init() { 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("server-name", "", "The name of the KMIP server. Alternative to passing it as a positional argument; required if not passed positionally") 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") From 416fb23e8fc1e7f11373a2e8bf2c82b1a7933488 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Fri, 5 Jun 2026 16:54:22 -0400 Subject: [PATCH 07/12] chore(kmip): bump infisical-kmip to v0.3.19 Picks up the /kmip/servers/connect daemon path so enrollment-based KMIP servers fetch their certificate from the platform instead of passing cert config at launch. --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c9d5bb41..c13ef3a4 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/google/uuid v1.6.0 github.com/h2non/filetype v1.1.3 github.com/infisical/go-sdk v0.7.0 - github.com/infisical/infisical-kmip v0.3.18 + github.com/infisical/infisical-kmip v0.3.19 github.com/jackc/pgx/v5 v5.9.2 github.com/jcmturner/gokrb5/v8 v8.4.4 github.com/mattn/go-isatty v0.0.20 diff --git a/go.sum b/go.sum index b7cefb2a..63fd6ff6 100644 --- a/go.sum +++ b/go.sum @@ -378,8 +378,8 @@ github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7P github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/infisical/go-sdk v0.7.0 h1:x9/1PczL+ioVD1jCp4LHQzPpBawatbmkjAC0S1OAtUA= github.com/infisical/go-sdk v0.7.0/go.mod h1:yEfXF+3YDDXiJ9zzJUSzW6me6XXPPEDK52fSU6JfpCA= -github.com/infisical/infisical-kmip v0.3.18 h1:GNpbVtYI+l6Au9pgOGOs2HUVdwydPkXAl7HZXNzG4OM= -github.com/infisical/infisical-kmip v0.3.18/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs= +github.com/infisical/infisical-kmip v0.3.19 h1:JDndxhM9+GoHTqSOX47H3KIxIuDmrEHsq17wvhw3RL8= +github.com/infisical/infisical-kmip v0.3.19/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= From 0f9b8835a29c6b3fe9376b428761757a485b8f27 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Fri, 5 Jun 2026 17:39:52 -0400 Subject: [PATCH 08/12] chore(kmip): tidy e2e module for infisical-kmip v0.3.19 The e2e module (replace infisical-merge => ../) still pinned infisical-kmip v0.3.17 as an indirect dep, which left it un-tidy and failed the E2E 'go test' tidy check. Re-tidied to v0.3.19 to match the root module. --- e2e/go.mod | 2 +- e2e/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/go.mod b/e2e/go.mod index 94948e4e..349cbe48 100644 --- a/e2e/go.mod +++ b/e2e/go.mod @@ -157,7 +157,7 @@ require ( github.com/in-toto/in-toto-golang v0.9.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/infisical/go-sdk v0.7.0 // indirect - github.com/infisical/infisical-kmip v0.3.17 // indirect + github.com/infisical/infisical-kmip v0.3.19 // indirect github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect diff --git a/e2e/go.sum b/e2e/go.sum index f12f8d6a..1560c629 100644 --- a/e2e/go.sum +++ b/e2e/go.sum @@ -555,8 +555,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/infisical/go-sdk v0.7.0 h1:x9/1PczL+ioVD1jCp4LHQzPpBawatbmkjAC0S1OAtUA= github.com/infisical/go-sdk v0.7.0/go.mod h1:yEfXF+3YDDXiJ9zzJUSzW6me6XXPPEDK52fSU6JfpCA= -github.com/infisical/infisical-kmip v0.3.17 h1:5dBuyzHs+BxZD30JYBNufnoxRJNyPThL6lR4YPRWf4w= -github.com/infisical/infisical-kmip v0.3.17/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs= +github.com/infisical/infisical-kmip v0.3.19 h1:JDndxhM9+GoHTqSOX47H3KIxIuDmrEHsq17wvhw3RL8= +github.com/infisical/infisical-kmip v0.3.19/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf h1:FtEj8sfIcaaBfAKrE1Cwb61YDtYq9JxChK1c7AKce7s= github.com/inhies/go-bytesize v0.0.0-20220417184213-4913239db9cf/go.mod h1:yrqSXGoD/4EKfF26AOGzscPOgTTJcyAwM2rpixWT+t4= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= From 87255b7a57d4e089dd716bdaa71972b4970cd7f4 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Mon, 8 Jun 2026 10:19:52 -0400 Subject: [PATCH 09/12] fix(kmip): reuse stored token on restart instead of falling back to legacy auth When no enroll method is given, reuse the stored enrollment access token unless explicit identity credentials were passed, rather than silently dropping to legacy machine-identity auth. Mirrors the gateway's start behavior. Token systemd installs rely on this fallback (the long-lived token is reused); the AWS path still persists the method to force STS re-auth. --- packages/cmd/kmip.go | 18 ++++++++++++++++-- packages/kmip/systemd.go | 6 ++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/cmd/kmip.go b/packages/cmd/kmip.go index 1297d680..aa35c134 100644 --- a/packages/cmd/kmip.go +++ b/packages/cmd/kmip.go @@ -83,6 +83,7 @@ func startKmipServer(cmd *cobra.Command, args []string) { } enrollMethod, _ := cmd.Flags().GetString("enroll-method") + // Fall back to env var for systemd-managed runs where flags aren't set. if enrollMethod == "" { enrollMethod = os.Getenv(localkmip.INFISICAL_KMIP_ENROLL_METHOD_KEY) } @@ -108,10 +109,23 @@ func startKmipServer(cmd *cobra.Command, args []string) { HostnamesOrIps: hostnamesOrIps, } - if enrollMethod != "" { + isResourceAuth := enrollMethod == localkmip.EnrollMethodToken || enrollMethod == localkmip.EnrollMethodAws + if isResourceAuth { serverConfig.AccessToken = enrollKmipServer(cmd, enrollMethod, serverName) } else { - serverConfig.IdentityClientId, serverConfig.IdentityClientSecret = resolveKmipIdentityCredentials(cmd) + // No enroll method given: reuse the stored enrollment access token unless explicit identity + // credentials were passed. This keeps a manual restart of an enrolled server from silently + // falling back to legacy machine-identity auth. Mirrors the gateway's start behavior. + hasExplicitCreds := cmd.Flags().Changed("identity-client-id") || cmd.Flags().Changed("identity-client-secret") + if !hasExplicitCreds { + if storedToken, _ := localkmip.LoadStoredAccessToken(serverName); storedToken != "" { + log.Info().Msg("Using stored KMIP server access token") + serverConfig.AccessToken = storedToken + } + } + if serverConfig.AccessToken == "" { + serverConfig.IdentityClientId, serverConfig.IdentityClientSecret = resolveKmipIdentityCredentials(cmd) + } } kmip.StartServer(serverConfig) diff --git a/packages/kmip/systemd.go b/packages/kmip/systemd.go index f2488429..253fdf0b 100644 --- a/packages/kmip/systemd.go +++ b/packages/kmip/systemd.go @@ -96,8 +96,10 @@ func InstallEnrolledKmipSystemdService(accessToken, domain, listenAddress, serve return nil } - configContent := "INFISICAL_KMIP_ENROLL_METHOD=token\n" - configContent += fmt.Sprintf("INFISICAL_KMIP_ACCESS_TOKEN=%s\n", accessToken) + // No enroll method is persisted: on restart the bare `kmip start` reuses this stored access + // token via the no-explicit-creds fallback (the token is long-lived). The AWS path instead + // persists the method so it re-authenticates via STS on each start. + configContent := fmt.Sprintf("INFISICAL_KMIP_ACCESS_TOKEN=%s\n", accessToken) configContent = kmipCommonEnv(configContent, domain, listenAddress, serverName, certificateTTL, hostnamesOrIps) return writeKmipSystemdService(configContent) From fad900271ecfb7d8b935b360adb17affde1a4c59 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Mon, 8 Jun 2026 10:32:22 -0400 Subject: [PATCH 10/12] refactor(kmip): extract enroll-method resolution into a helper Both 'kmip start' and 'kmip systemd install' resolved + validated --enroll-method with the same flag/env/validate block. Unify into resolveEnrollMethod (per review feedback). --- packages/cmd/kmip.go | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/cmd/kmip.go b/packages/cmd/kmip.go index aa35c134..8af4bdca 100644 --- a/packages/cmd/kmip.go +++ b/packages/cmd/kmip.go @@ -57,6 +57,20 @@ func resolveKmipServerName(cmd *cobra.Command, args []string) string { return serverName } +// resolveEnrollMethod reads the enroll method from --enroll-method, falling back to the env var for +// systemd-managed runs, and validates it. An empty result means the legacy machine-identity path. +func resolveEnrollMethod(cmd *cobra.Command) string { + enrollMethod, _ := cmd.Flags().GetString("enroll-method") + if enrollMethod == "" { + enrollMethod = os.Getenv(localkmip.INFISICAL_KMIP_ENROLL_METHOD_KEY) + } + if enrollMethod != "" && enrollMethod != localkmip.EnrollMethodToken && enrollMethod != localkmip.EnrollMethodAws { + util.HandleError(fmt.Errorf("invalid --enroll-method %q: supported values are %q and %q", + enrollMethod, localkmip.EnrollMethodToken, localkmip.EnrollMethodAws)) + } + return enrollMethod +} + const ( INFISICAL_KMIP_LISTEN_ADDRESS_ENV_NAME = "INFISICAL_KMIP_LISTEN_ADDRESS" INFISICAL_KMIP_SERVER_NAME_ENV_NAME = "INFISICAL_KMIP_SERVER_NAME" @@ -82,15 +96,7 @@ func startKmipServer(cmd *cobra.Command, args []string) { util.HandleError(err, "Unable to parse hostnames or IPs") } - enrollMethod, _ := cmd.Flags().GetString("enroll-method") - // Fall back to env var for systemd-managed runs where flags aren't set. - if enrollMethod == "" { - enrollMethod = os.Getenv(localkmip.INFISICAL_KMIP_ENROLL_METHOD_KEY) - } - if enrollMethod != "" && enrollMethod != localkmip.EnrollMethodToken && enrollMethod != localkmip.EnrollMethodAws { - util.HandleError(fmt.Errorf("invalid --enroll-method %q: supported values are %q and %q", - enrollMethod, localkmip.EnrollMethodToken, localkmip.EnrollMethodAws)) - } + enrollMethod := resolveEnrollMethod(cmd) // Resolve the Infisical domain: explicit flag, then the value stored at enrollment, then the logged-in user's domain. if flagDomain, _ := cmd.Flags().GetString("domain"); flagDomain != "" { @@ -296,14 +302,7 @@ var kmipSystemdInstallCmd = &cobra.Command{ util.HandleError(err, "Unable to parse hostnames or IPs") } - enrollMethod, _ := cmd.Flags().GetString("enroll-method") - if enrollMethod == "" { - enrollMethod = os.Getenv(localkmip.INFISICAL_KMIP_ENROLL_METHOD_KEY) - } - if enrollMethod != "" && enrollMethod != localkmip.EnrollMethodToken && enrollMethod != localkmip.EnrollMethodAws { - util.HandleError(fmt.Errorf("invalid --enroll-method %q: supported values are %q and %q", - enrollMethod, localkmip.EnrollMethodToken, localkmip.EnrollMethodAws)) - } + enrollMethod := resolveEnrollMethod(cmd) switch enrollMethod { case localkmip.EnrollMethodToken: From 2d1f0acaaa2fc421425368f34b7e9fdd35c3ffa7 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Mon, 8 Jun 2026 10:52:54 -0400 Subject: [PATCH 11/12] refactor(kmip): unify domain resolution across start and systemd install Both commands resolved the Infisical domain differently (start: flag -> stored -> logged-in; install: flag -> env). Extract one resolveDomain helper (flag -> stored -> logged-in -> env) used by both, per review feedback. Behavior preserved: stored/logged-in are empty at a fresh install so it stays flag -> env there, and env remains lowest priority at start. --- packages/cmd/kmip.go | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/cmd/kmip.go b/packages/cmd/kmip.go index 8af4bdca..65ddc48f 100644 --- a/packages/cmd/kmip.go +++ b/packages/cmd/kmip.go @@ -71,6 +71,22 @@ func resolveEnrollMethod(cmd *cobra.Command) string { return enrollMethod } +// resolveDomain resolves the Infisical instance domain from (in order) the --domain flag, the domain +// stored at enrollment, the logged-in user's domain, then the INFISICAL_API_URL env var. Returns the +// raw domain (empty if none); callers append the API path. +func resolveDomain(cmd *cobra.Command, serverName string) string { + if flagDomain, _ := cmd.Flags().GetString("domain"); flagDomain != "" { + return flagDomain + } + if storedDomain, _ := localkmip.LoadStoredDomain(serverName); storedDomain != "" { + return storedDomain + } + if configFile, err := util.GetConfigFile(); err == nil && configFile.LoggedInUserDomain != "" { + return configFile.LoggedInUserDomain + } + return os.Getenv(util.INFISICAL_API_URL_ENV_NAME) +} + const ( INFISICAL_KMIP_LISTEN_ADDRESS_ENV_NAME = "INFISICAL_KMIP_LISTEN_ADDRESS" INFISICAL_KMIP_SERVER_NAME_ENV_NAME = "INFISICAL_KMIP_SERVER_NAME" @@ -98,13 +114,8 @@ func startKmipServer(cmd *cobra.Command, args []string) { enrollMethod := resolveEnrollMethod(cmd) - // Resolve the Infisical domain: explicit flag, then the value stored at enrollment, then the logged-in user's domain. - if flagDomain, _ := cmd.Flags().GetString("domain"); flagDomain != "" { - config.INFISICAL_URL = util.AppendAPIEndpoint(flagDomain) - } else if storedDomain, _ := localkmip.LoadStoredDomain(serverName); storedDomain != "" { - config.INFISICAL_URL = util.AppendAPIEndpoint(storedDomain) - } else if configFile, cfgErr := util.GetConfigFile(); cfgErr == nil && configFile.LoggedInUserDomain != "" { - config.INFISICAL_URL = util.AppendAPIEndpoint(configFile.LoggedInUserDomain) + if domain := resolveDomain(cmd, serverName); domain != "" { + config.INFISICAL_URL = util.AppendAPIEndpoint(domain) } serverConfig := kmip.ServerConfig{ @@ -275,12 +286,10 @@ var kmipSystemdInstallCmd = &cobra.Command{ util.HandleError(fmt.Errorf("systemd service installation requires root/sudo privileges")) } - domain, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "domain", []string{util.INFISICAL_API_URL_ENV_NAME}, "") - if err != nil { - util.HandleError(err, "Unable to parse domain") - } + serverName := resolveKmipServerName(cmd, args) // Point the API client at the configured instance before any enrollment call is made. + domain := resolveDomain(cmd, serverName) if domain != "" { config.INFISICAL_URL = util.AppendAPIEndpoint(domain) } @@ -290,8 +299,6 @@ var kmipSystemdInstallCmd = &cobra.Command{ util.HandleError(err, "Unable to parse listen address") } - serverName := resolveKmipServerName(cmd, args) - 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") From dbe6409a7f353aac260f0a00f430f31e069242a8 Mon Sep 17 00:00:00 2001 From: bernie-g Date: Mon, 8 Jun 2026 12:23:08 -0400 Subject: [PATCH 12/12] chore(kmip): align --domain flag description across start and systemd install Both now read "Domain of your self-hosted Infisical instance", matching each other and the gateway/relay flag wording. --- packages/cmd/kmip.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cmd/kmip.go b/packages/cmd/kmip.go index 65ddc48f..2550b8dc 100644 --- a/packages/cmd/kmip.go +++ b/packages/cmd/kmip.go @@ -403,7 +403,7 @@ func init() { kmipStartCmd.Flags().String("enroll-method", "", "Enrollment method for the KMIP server: 'token' or 'aws'. When set, machine-identity flags are ignored.") kmipStartCmd.Flags().String("token", "", "Enrollment token (when --enroll-method=token)") kmipStartCmd.Flags().String("kmip-server-id", "", "KMIP server ID (when --enroll-method=aws)") - kmipStartCmd.Flags().String("domain", "", "Domain of your Infisical instance") + kmipStartCmd.Flags().String("domain", "", "Domain of your self-hosted Infisical instance") 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")