-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #62 from ministryofjustice/decode-k8s-secrets
Add feature - decode k8s secrets
- Loading branch information
Showing
6 changed files
with
178 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
package decodeSecret | ||
|
||
import ( | ||
"bytes" | ||
"encoding/base64" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"os/exec" | ||
) | ||
|
||
type secretDecoder struct{} | ||
|
||
type DecodeSecretOptions struct { | ||
Secret string | ||
Namespace string | ||
} | ||
|
||
func DecodeSecret(opts *DecodeSecretOptions) error { | ||
jsn := retrieveSecret(opts.Namespace, opts.Secret) | ||
|
||
sd := secretDecoder{} | ||
|
||
err, str := sd.processJson(jsn) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
fmt.Printf(str) | ||
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"), "" | ||
} | ||
|
||
var result map[string]interface{} | ||
json.Unmarshal([]byte(jsn), &result) | ||
|
||
data := result["data"].(map[string]interface{}) | ||
|
||
err := decodeKeys(data) | ||
if err != nil { | ||
return err, "" | ||
} | ||
|
||
err, str := formatJson(result) | ||
if err != nil { | ||
return err, "" | ||
} | ||
|
||
return nil, str | ||
} | ||
|
||
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 { | ||
return "ERROR: base64 decode failed" | ||
} | ||
return fmt.Sprintf("%s", str) | ||
} | ||
|
||
func formatJson(result map[string]interface{}) (error, string) { | ||
str, err := json.MarshalIndent(result, "", " ") | ||
if err != nil { | ||
return err, "" | ||
} | ||
|
||
return nil, fmt.Sprintf("%s\n", str) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
} | ||
} |