From 2a426577affd4a10a751032717f429ecde3c70ca Mon Sep 17 00:00:00 2001 From: David Salgado Date: Thu, 3 Dec 2020 17:57:37 +0900 Subject: [PATCH 1/8] WiP: Add `decode-secret` command So far, this just takes two parameters and passes them to the DecodeSecret function. --- pkg/commands/commands.go | 1 + pkg/commands/decodeSecret.go | 31 +++++++++++++++++++++++++++++++ pkg/decodeSecret/decodeSecret.go | 17 +++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 pkg/commands/decodeSecret.go create mode 100644 pkg/decodeSecret/decodeSecret.go diff --git a/pkg/commands/commands.go b/pkg/commands/commands.go index a5b937b7..a09ab56a 100644 --- a/pkg/commands/commands.go +++ b/pkg/commands/commands.go @@ -10,4 +10,5 @@ func AddCommands(topLevel *cobra.Command) { addKubecfgCmd(topLevel) addVersion(topLevel) addEnvironmentCmd(topLevel) + addDecodeSecret(topLevel) } diff --git a/pkg/commands/decodeSecret.go b/pkg/commands/decodeSecret.go new file mode 100644 index 00000000..10efcc69 --- /dev/null +++ b/pkg/commands/decodeSecret.go @@ -0,0 +1,31 @@ +package commands + +import ( + "github.com/MakeNowJust/heredoc" + decodeSecret "github.com/ministryofjustice/cloud-platform-cli/pkg/decodeSecret" + "github.com/spf13/cobra" +) + +func addDecodeSecret(topLevel *cobra.Command) { + opts := &decodeSecret.DecodeSecretOptions{} + + cmd := &cobra.Command{ + Use: "decode-secret", + Short: `Decode a kubernetes secret`, + Example: heredoc.Doc(` +$ cloud-platform decode-secret -n mynamespace -s mysecret + `), + PreRun: upgradeIfNotLatest, + RunE: func(cmd *cobra.Command, args []string) error { + return decodeSecret.DecodeSecret(opts) + }, + } + + cmd.Flags().StringVarP(&opts.Secret, "secret", "s", "", "Secret name") + cmd.MarkFlagRequired("secret") + + cmd.Flags().StringVarP(&opts.Namespace, "namespace", "n", "", "Namespace name") + cmd.MarkFlagRequired("namespace") + + topLevel.AddCommand(cmd) +} diff --git a/pkg/decodeSecret/decodeSecret.go b/pkg/decodeSecret/decodeSecret.go new file mode 100644 index 00000000..30b44725 --- /dev/null +++ b/pkg/decodeSecret/decodeSecret.go @@ -0,0 +1,17 @@ +package decodeSecret + +import ( + "fmt" +) + +type DecodeSecretOptions struct { + Secret string + Namespace string +} + +func DecodeSecret(opts *DecodeSecretOptions) error { + fmt.Println("Hello from DecodeSecret") + fmt.Println(opts.Namespace) + fmt.Println(opts.Secret) + return nil +} From 3022de988f136f6496250d86847a55203bc7b973 Mon Sep 17 00:00:00 2001 From: David Salgado Date: Fri, 4 Dec 2020 09:58:41 +0900 Subject: [PATCH 2/8] WiP: Decode the keys of a kubernetes secret Given a namespace and secret name, get the JSON representation of the secret, base64-decode the values of the `data` keys, and pretty-print the result --- pkg/decodeSecret/decodeSecret.go | 73 ++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/pkg/decodeSecret/decodeSecret.go b/pkg/decodeSecret/decodeSecret.go index 30b44725..ae019015 100644 --- a/pkg/decodeSecret/decodeSecret.go +++ b/pkg/decodeSecret/decodeSecret.go @@ -1,7 +1,11 @@ package decodeSecret import ( + "bytes" + "encoding/base64" + "encoding/json" "fmt" + "os/exec" ) type DecodeSecretOptions struct { @@ -9,9 +13,72 @@ type DecodeSecretOptions struct { Namespace string } +type SecretData struct { + Key string + Value string +} + func DecodeSecret(opts *DecodeSecretOptions) error { - fmt.Println("Hello from DecodeSecret") - fmt.Println(opts.Namespace) - fmt.Println(opts.Secret) + jsn := retrieveSecret(opts.Namespace, opts.Secret) + + var result map[string]interface{} + json.Unmarshal([]byte(jsn), &result) + + data := result["data"].(map[string]interface{}) + + err := decodeKeys(data) + if err != nil { + fmt.Println("Error: ", err) + return err + } + + prettyPrint(result) + return nil +} + +func retrieveSecret(namespace, secret string) string { + cmd := exec.Command("kubectl", "--namespace", namespace, "get", "secret", secret, "-o", "json") + + var out bytes.Buffer + cmd.Stdout = &out + + err := cmd.Run() + if err != nil { + fmt.Println("Error: ", err) + return "" + } + + return out.String() +} + +func decodeKeys(data map[string]interface{}) error { + for k, v := range data { + switch v.(type) { + case string: + data[k] = base64decode(v) + default: + return fmt.Errorf("Expected key %s of secret to be a string, but it wasn't\n", k) + } + } + return nil +} + +func base64decode(i interface{}) string { + str, e := base64.StdEncoding.DecodeString(i.(string)) + if e != nil { + fmt.Println(e) + return "" + } + return fmt.Sprintf("%s", str) +} + +func prettyPrint(result map[string]interface{}) error { + str, err := json.MarshalIndent(result, "", " ") + if err != nil { + fmt.Println("Error: ", err) + return err + } + + fmt.Printf("%s\n", str) return nil } From a4d6b4d6d91f6baa92b0049015c2ebafcfbcfc95 Mon Sep 17 00:00:00 2001 From: David Salgado Date: Fri, 4 Dec 2020 10:18:52 +0900 Subject: [PATCH 3/8] Make the code easier to test --- pkg/decodeSecret/decodeSecret.go | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/pkg/decodeSecret/decodeSecret.go b/pkg/decodeSecret/decodeSecret.go index ae019015..a3c5fb0b 100644 --- a/pkg/decodeSecret/decodeSecret.go +++ b/pkg/decodeSecret/decodeSecret.go @@ -21,6 +21,17 @@ type SecretData struct { func DecodeSecret(opts *DecodeSecretOptions) error { jsn := retrieveSecret(opts.Namespace, opts.Secret) + err, str := processJson(jsn) + if err != nil { + fmt.Println("Error: ", err) + return err + } + + fmt.Printf(str) + return nil +} + +func processJson(jsn string) (error, string) { var result map[string]interface{} json.Unmarshal([]byte(jsn), &result) @@ -28,12 +39,15 @@ func DecodeSecret(opts *DecodeSecretOptions) error { err := decodeKeys(data) if err != nil { - fmt.Println("Error: ", err) - return err + return err, "" } - prettyPrint(result) - return nil + err, str := formatJson(result) + if err != nil { + return err, "" + } + + return nil, str } func retrieveSecret(namespace, secret string) string { @@ -67,18 +81,16 @@ func base64decode(i interface{}) string { str, e := base64.StdEncoding.DecodeString(i.(string)) if e != nil { fmt.Println(e) - return "" + return "ERROR: base64 decode failed" } return fmt.Sprintf("%s", str) } -func prettyPrint(result map[string]interface{}) error { +func formatJson(result map[string]interface{}) (error, string) { str, err := json.MarshalIndent(result, "", " ") if err != nil { - fmt.Println("Error: ", err) - return err + return err, "" } - fmt.Printf("%s\n", str) - return nil + return nil, fmt.Sprintf("%s\n", str) } From 2b11b4edccf054d141d90a78f70e233b72403cda Mon Sep 17 00:00:00 2001 From: David Salgado Date: Fri, 4 Dec 2020 10:25:32 +0900 Subject: [PATCH 4/8] Remove unused struct --- pkg/decodeSecret/decodeSecret.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/pkg/decodeSecret/decodeSecret.go b/pkg/decodeSecret/decodeSecret.go index a3c5fb0b..e22cd3ab 100644 --- a/pkg/decodeSecret/decodeSecret.go +++ b/pkg/decodeSecret/decodeSecret.go @@ -13,11 +13,6 @@ type DecodeSecretOptions struct { Namespace string } -type SecretData struct { - Key string - Value string -} - func DecodeSecret(opts *DecodeSecretOptions) error { jsn := retrieveSecret(opts.Namespace, opts.Secret) From 93a81c88907d310fb8fe49c9d0e729d96057455b Mon Sep 17 00:00:00 2001 From: David Salgado Date: Fri, 4 Dec 2020 10:54:22 +0900 Subject: [PATCH 5/8] Add some tests --- pkg/decodeSecret/decodeSecret.go | 15 ++++++--- pkg/decodeSecret/decodeSecret_test.go | 45 +++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) create mode 100644 pkg/decodeSecret/decodeSecret_test.go diff --git a/pkg/decodeSecret/decodeSecret.go b/pkg/decodeSecret/decodeSecret.go index e22cd3ab..9df51d48 100644 --- a/pkg/decodeSecret/decodeSecret.go +++ b/pkg/decodeSecret/decodeSecret.go @@ -4,10 +4,13 @@ import ( "bytes" "encoding/base64" "encoding/json" + "errors" "fmt" "os/exec" ) +type secretDecoder struct{} + type DecodeSecretOptions struct { Secret string Namespace string @@ -16,9 +19,10 @@ type DecodeSecretOptions struct { func DecodeSecret(opts *DecodeSecretOptions) error { jsn := retrieveSecret(opts.Namespace, opts.Secret) - err, str := processJson(jsn) + sd := secretDecoder{} + + err, str := sd.processJson(jsn) if err != nil { - fmt.Println("Error: ", err) return err } @@ -26,7 +30,11 @@ func DecodeSecret(opts *DecodeSecretOptions) error { return nil } -func processJson(jsn string) (error, string) { +func (sd *secretDecoder) processJson(jsn string) (error, string) { + if jsn == "" { + return errors.New("failed to retrieve secret from namespace"), "" + } + var result map[string]interface{} json.Unmarshal([]byte(jsn), &result) @@ -75,7 +83,6 @@ func decodeKeys(data map[string]interface{}) error { func base64decode(i interface{}) string { str, e := base64.StdEncoding.DecodeString(i.(string)) if e != nil { - fmt.Println(e) return "ERROR: base64 decode failed" } return fmt.Sprintf("%s", str) diff --git a/pkg/decodeSecret/decodeSecret_test.go b/pkg/decodeSecret/decodeSecret_test.go new file mode 100644 index 00000000..6ff6e61b --- /dev/null +++ b/pkg/decodeSecret/decodeSecret_test.go @@ -0,0 +1,45 @@ +package decodeSecret + +import "testing" + +func TestDecodeSecret(t *testing.T) { + jsn := `{ "data": { "key1": "d2liYmxl", "key2": "d29iYmxl" } }` + + expected := `{ + "data": { + "key1": "wibble", + "key2": "wobble" + } +} +` + sd := secretDecoder{} + err, actual := sd.processJson(jsn) + if err != nil || actual != expected { + t.Errorf("Expected:\n%s\nGot:\n%s\n", expected, actual) + } +} + +func TestBadBase64(t *testing.T) { + jsn := `{ "data": { "key1": "1", "key2": "2" } }` + + expected := `{ + "data": { + "key1": "ERROR: base64 decode failed", + "key2": "ERROR: base64 decode failed" + } +} +` + sd := secretDecoder{} + err, actual := sd.processJson(jsn) + if err != nil || actual != expected { + t.Errorf("Expected:\n%s\nGot:\n%s\n", expected, actual) + } +} + +func TestNoSuchSecret(t *testing.T) { + sd := secretDecoder{} + err, _ := sd.processJson("") + if err == nil { + t.Errorf("Expected an error") + } +} From c33cc8aeda04716197acdec29c93e921d205a94e Mon Sep 17 00:00:00 2001 From: David Salgado Date: Fri, 4 Dec 2020 10:56:14 +0900 Subject: [PATCH 6/8] Bump the version number --- pkg/commands/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/commands/version.go b/pkg/commands/version.go index 0cb2417d..f7c28a8f 100644 --- a/pkg/commands/version.go +++ b/pkg/commands/version.go @@ -12,7 +12,7 @@ import ( ) // This MUST match the number of the latest release on github -var Version = "1.6.4" +var Version = "1.6.5" const owner = "ministryofjustice" const repoName = "cloud-platform-cli" From d2dd10e50e99aa27415fb2a17235d5da7499fd43 Mon Sep 17 00:00:00 2001 From: David Salgado Date: Fri, 4 Dec 2020 11:05:23 +0900 Subject: [PATCH 7/8] Update setup-sonar-scanner github action to v3 v1 gives this error: ``` Error: Unable to process command '::add-path::/opt/hostedtoolcache/sonar-scanner/4.2.0.1873/x64/sonar-scanner-4.2.0.1873-linux/bin' successfully. Error: The `add-path` command is disabled. Please upgrade to using Environment Files or opt into unsecure command execution by setting the `ACTIONS_ALLOW_UNSECURE_COMMANDS` environment variable to `true`. For more information see: https://github.blog/changelog/2020-10-01-github-actions-deprecating-set-env-and-add-path-commands/ ``` --- .github/workflows/sonar-scan.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonar-scan.yml b/.github/workflows/sonar-scan.yml index 3e25479c..da1b19a8 100644 --- a/.github/workflows/sonar-scan.yml +++ b/.github/workflows/sonar-scan.yml @@ -6,12 +6,12 @@ on: jobs: run-scan: - name: Sonarqube Scan + name: Sonarqube Scan runs-on: ubuntu-latest steps: - name: Setup sonarqube - uses: warchant/setup-sonar-scanner@v1 + uses: warchant/setup-sonar-scanner@v3 - name: Checkout Code uses: actions/checkout@v2 From 0f559985e1c799e017f87619a34def3679d32b15 Mon Sep 17 00:00:00 2001 From: David Salgado Date: Fri, 4 Dec 2020 11:09:15 +0900 Subject: [PATCH 8/8] Move retrieveSecret above processJson This reflects the order in which the functions are called, which should make it easier to read the code --- pkg/decodeSecret/decodeSecret.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pkg/decodeSecret/decodeSecret.go b/pkg/decodeSecret/decodeSecret.go index 9df51d48..e436d00c 100644 --- a/pkg/decodeSecret/decodeSecret.go +++ b/pkg/decodeSecret/decodeSecret.go @@ -30,6 +30,21 @@ func DecodeSecret(opts *DecodeSecretOptions) error { return nil } +func retrieveSecret(namespace, secret string) string { + cmd := exec.Command("kubectl", "--namespace", namespace, "get", "secret", secret, "-o", "json") + + var out bytes.Buffer + cmd.Stdout = &out + + err := cmd.Run() + if err != nil { + fmt.Println("Error: ", err) + return "" + } + + return out.String() +} + func (sd *secretDecoder) processJson(jsn string) (error, string) { if jsn == "" { return errors.New("failed to retrieve secret from namespace"), "" @@ -53,21 +68,6 @@ func (sd *secretDecoder) processJson(jsn string) (error, string) { return nil, str } -func retrieveSecret(namespace, secret string) string { - cmd := exec.Command("kubectl", "--namespace", namespace, "get", "secret", secret, "-o", "json") - - var out bytes.Buffer - cmd.Stdout = &out - - err := cmd.Run() - if err != nil { - fmt.Println("Error: ", err) - return "" - } - - return out.String() -} - func decodeKeys(data map[string]interface{}) error { for k, v := range data { switch v.(type) {