From 1dbcb4d7dee01baa8f61482dca4e5c7e23202ddf Mon Sep 17 00:00:00 2001 From: Nick Canzoneri Date: Wed, 24 Feb 2021 17:03:06 -0500 Subject: [PATCH 1/2] Add library function to call reload secure settings API --- es.go | 61 ++++++++++++++++++++++++++++++++++++++++++++++++- es_test.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/es.go b/es.go index 685d14b..25787c8 100644 --- a/es.go +++ b/es.go @@ -267,6 +267,22 @@ type Token struct { Position int `json:"position"` } +type ReloadSecureSettingsResponse struct { + Summary struct { + Total int `json:"total"` + Failed int `json:"failed"` + Successful int `json:"successful"` + } `json:"_nodes"` + ClusterName string `json:"cluster_name"` + Nodes map[string]struct { + Name string `json:"name"` + ReloadException *struct { + Type string `json:"type"` + Reason string `json:"reason"` + } `json:"reload_exception"` + } `json:"nodes"` +} + //Initialize a new vulcanizer client to use. // Deprecated: NewClient has been deprecated in favor of using struct initialization. func NewClient(host string, port int) *Client { @@ -1405,5 +1421,48 @@ func (s *Snapshot) GetEndTime() string { } // This will avoid returning incorrect values like "1970-01-01T00:00:00.000Z" return "" - +} + +//Reload secure node settings +// +//Use case: Call the reload secure settings API https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-reload-secure-settings.html +func (c *Client) ReloadSecureSettings() (ReloadSecureSettingsResponse, error) { + var response ReloadSecureSettingsResponse + err := handleErrWithStruct(c.buildPostRequest("_nodes/reload_secure_settings"), &response) + + if err != nil { + return ReloadSecureSettingsResponse{}, err + } + + return response, nil +} + +//Reload secure node settings with password +// +//Use case: Call the reload secure settings API with a supplied password https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-nodes-reload-secure-settings.html +func (c *Client) ReloadSecureSettingsWithPassword(password string) (ReloadSecureSettingsResponse, error) { + + if password == "" { + return ReloadSecureSettingsResponse{}, fmt.Errorf("Keystore password is required.") + } + + requestBody := struct { + Password string `json:"secure_settings_password"` + }{ + Password: password, + } + + agent := c.buildPostRequest("_nodes/reload_secure_settings"). + Set("Content-Type", "application/json"). + Send(requestBody) + + var response ReloadSecureSettingsResponse + + err := handleErrWithStruct(agent, &response) + + if err != nil { + return ReloadSecureSettingsResponse{}, err + } + + return response, nil } diff --git a/es_test.go b/es_test.go index c2c38d3..2078185 100644 --- a/es_test.go +++ b/es_test.go @@ -1882,3 +1882,70 @@ func TestGetShardRecoveryRemaining(t *testing.T) { assert.Equal(t, estRemaining, time.Hour*6) } + +func TestReloadSecureSettings(t *testing.T) { + serverSetup := &ServerSetup{ + Method: "POST", + Path: "/_nodes/reload_secure_settings", + Response: `{"_nodes":{"total":2,"successful":2,"failed":0},"cluster_name":"vulcanizer-elasticsearch-v7","nodes":{"iJeJx6ydSbKf_cvzDt1_gg":{"name":"vulcanizer-elasticsearch-v7"},"GXtqL0WdSguHQdo2xHNX_A":{"name":"vulcanizer-elasticsearch-v7-2","reload_exception":{"type":"illegal_state_exception","reason":"Keystore is missing"}}}}`, + } + + host, port, ts := setupTestServers(t, []*ServerSetup{serverSetup}) + defer ts.Close() + client := NewClient(host, port) + + response, err := client.ReloadSecureSettings() + + if err != nil { + t.Errorf("Unexpected error, get %s", err) + } + + if response.Summary.Successful != 2 { + t.Errorf("Expected response to parse 2 successful nodes from summary, got %#v", response) + } + + goodNode := response.Nodes["iJeJx6ydSbKf_cvzDt1_gg"] + badNode := response.Nodes["GXtqL0WdSguHQdo2xHNX_A"] + + if goodNode.Name != "vulcanizer-elasticsearch-v7" && goodNode.ReloadException != nil { + t.Errorf("Expected to parse good node response correctly, got %#v", goodNode) + } + + if badNode.Name != "vulcanizer-elasticsearch-v7-2" && badNode.ReloadException.Reason != "Keystore is missing" { + t.Errorf("Expected to parse bad node response correctly, got %#v", goodNode) + } +} + +func TestReloadSecureSettingsWithPassword(t *testing.T) { + serverSetup := &ServerSetup{ + Method: "POST", + Path: "/_nodes/reload_secure_settings", + Body: `{"secure_settings_password":"123456"}`, + Response: `{"_nodes":{"total":2,"successful":2,"failed":0},"cluster_name":"vulcanizer-elasticsearch-v7","nodes":{"iJeJx6ydSbKf_cvzDt1_gg":{"name":"vulcanizer-elasticsearch-v7"},"GXtqL0WdSguHQdo2xHNX_A":{"name":"vulcanizer-elasticsearch-v7-2","reload_exception":{"type":"illegal_state_exception","reason":"Keystore is missing"}}}}`, + } + + host, port, ts := setupTestServers(t, []*ServerSetup{serverSetup}) + defer ts.Close() + client := NewClient(host, port) + + response, err := client.ReloadSecureSettingsWithPassword("123456") + + if err != nil { + t.Errorf("Unexpected error, get %s", err) + } + + if response.Summary.Successful != 2 { + t.Errorf("Expected response to parse 2 successful nodes from summary, got %#v", response) + } + + goodNode := response.Nodes["iJeJx6ydSbKf_cvzDt1_gg"] + badNode := response.Nodes["GXtqL0WdSguHQdo2xHNX_A"] + + if goodNode.Name != "vulcanizer-elasticsearch-v7" && goodNode.ReloadException != nil { + t.Errorf("Expected to parse good node response correctly, got %#v", goodNode) + } + + if badNode.Name != "vulcanizer-elasticsearch-v7-2" && badNode.ReloadException.Reason != "Keystore is missing" { + t.Errorf("Expected to parse bad node response correctly, got %#v", goodNode) + } +} From 8949b6fd25db2dd85065b482f9c99b361e5c7e5f Mon Sep 17 00:00:00 2001 From: Nick Canzoneri Date: Wed, 24 Feb 2021 17:39:43 -0500 Subject: [PATCH 2/2] Add CLI command to reload secure settings --- pkg/cli/settings.go | 55 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/pkg/cli/settings.go b/pkg/cli/settings.go index 2c5968e..d0763f0 100644 --- a/pkg/cli/settings.go +++ b/pkg/cli/settings.go @@ -9,9 +9,16 @@ import ( ) func init() { + setupReloadSecureCommand() + rootCmd.AddCommand(cmdSettings) } +func setupReloadSecureCommand() { + cmdSettingsReloadSecure.Flags().StringP("keystore_password", "", "", "Keystore password to reload the secure settings if enabled") + cmdSettings.AddCommand(cmdSettingsReloadSecure) +} + func printSettings(settings []vulcanizer.Setting, name string) { if len(settings) == 0 { fmt.Printf("No %s are set.\n", name) @@ -53,3 +60,51 @@ var cmdSettings = &cobra.Command{ printSettings(clusterSettings.TransientSettings, "transient settings") }, } + +var cmdSettingsReloadSecure = &cobra.Command{ + Use: "reload", + Short: "Reload the secure settings.", + Long: `This command calls the reload secure settings API on all nodes.`, + Run: func(cmd *cobra.Command, args []string) { + + v := getClient() + + password, err := cmd.Flags().GetString("keystore_password") + if err != nil { + fmt.Printf("Could not retrieve required argument: keystore_password. Error: %s\n", err) + os.Exit(1) + } + + var reloadResponse vulcanizer.ReloadSecureSettingsResponse + var reloadError error + + if password == "" { + reloadResponse, reloadError = v.ReloadSecureSettings() + } else { + reloadResponse, reloadError = v.ReloadSecureSettingsWithPassword(password) + } + + if reloadError != nil { + fmt.Printf("Error reloading secure settings settings: %s\n", reloadError) + os.Exit(1) + } + + header := []string{"Node", "Reload status"} + rows := [][]string{} + + for _, node := range reloadResponse.Nodes { + row := []string{node.Name} + + if node.ReloadException == nil { + row = append(row, "Successfully reloaded") + } else { + row = append(row, fmt.Sprintf("Exception type: %s, reason: %s", node.ReloadException.Type, node.ReloadException.Reason)) + } + + rows = append(rows, row) + } + + table := renderTable(rows, header) + fmt.Println(table) + }, +}