From a60bef43d9e4a163798449559099e5cd353c9ff0 Mon Sep 17 00:00:00 2001 From: Rodrigo Menezes Date: Wed, 20 Sep 2023 13:06:01 -0700 Subject: [PATCH] Allow authentication with Kubernetes JWT token (#138) Co-authored-by: Jonas Vinther --- README.md | 20 +++++++++----- cmd/cmd.go | 20 ++++++++++++++ cmd/delete.go | 4 ++- cmd/export.go | 5 +++- cmd/import.go | 4 ++- docs/examples/kubernetes/cronjob/README.md | 7 +++++ go.mod | 1 + go.sum | 2 ++ pkg/vaultengine/vaultclient.go | 31 ++++++++++++++++++---- 9 files changed, 80 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index e745a2f..f073d3f 100644 --- a/README.md +++ b/README.md @@ -302,15 +302,23 @@ Usage: medusa [command] Available Commands: + completion Generate the autocompletion script for the specified shell + decrypt Decrypt an encrypted Vault output file into plaintext in stdout + delete Recursively delete all secrets below the given path + encrypt Encrypt a Vault export file onto stdout or to an output file export Export Vault secrets as yaml help Help about any command import Import a yaml file into a Vault instance + version Print the version number of Medusa Flags: - -a, --address string Address of the Vault server - -h, --help help for medusa - -k, --insecure Allow insecure server connections when using SSL - -t, --token string Vault authentication token - -Use "medusa [command] --help" for more information about a command. + -a, --address string Address of the Vault server + -h, --help help for medusa + -k, --insecure Allow insecure server connections when using SSL + --kubernetes Authenticate using the Kubernetes JWT token + -n, --namespace string Namespace within the Vault server (Enterprise only) + -r, --role string Vault role for Kubernetes JWT authentication + -t, --token string Vault authentication token + +Use "medusa [command] --help" for more information about a command ``` diff --git a/cmd/cmd.go b/cmd/cmd.go index 7fdde14..4d8643f 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -32,6 +32,24 @@ Created by Jonas Vinther & Henrik Høegh.`, } } + role, _ := cmd.Flags().GetString("role") + if viper.IsSet("VAULT_ROLE") && role == "" { + value := viper.Get("VAULT_ROLE").(string) + err := cmd.Flags().Set("role", value) + if err != nil { + return err + } + } + + kubernetes, _ := cmd.Flags().GetBool("kubernetes") + if viper.IsSet("KUBERNETES") && kubernetes == false { + value := viper.GetBool("KUBERNETES") + err := cmd.Flags().Set("kubernetes", strconv.FormatBool(value)) + if err != nil { + return err + } + } + insecure, _ := cmd.Flags().GetBool("insecure") if viper.IsSet("VAULT_SKIP_VERIFY") && insecure == false { value := viper.GetBool("VAULT_SKIP_VERIFY") @@ -62,6 +80,8 @@ func Execute() error { func init() { rootCmd.PersistentFlags().StringP("address", "a", "", "Address of the Vault server") rootCmd.PersistentFlags().StringP("token", "t", "", "Vault authentication token") + rootCmd.PersistentFlags().StringP("role", "r", "", "Vault role for Kubernetes JWT authentication") + rootCmd.PersistentFlags().BoolP("kubernetes", "", false, "Authenticate using the Kubernetes JWT token") rootCmd.PersistentFlags().BoolP("insecure", "k", false, "Allow insecure server connections when using SSL") rootCmd.PersistentFlags().StringP("namespace", "n", "", "Namespace within the Vault server (Enterprise only)") diff --git a/cmd/delete.go b/cmd/delete.go index 269656c..aec8294 100644 --- a/cmd/delete.go +++ b/cmd/delete.go @@ -25,12 +25,14 @@ var deleteCmd = &cobra.Command{ vaultAddr, _ := cmd.Flags().GetString("address") vaultToken, _ := cmd.Flags().GetString("token") insecure, _ := cmd.Flags().GetBool("insecure") + vaultRole, _ := cmd.Flags().GetString("role") + kubernetes, _ := cmd.Flags().GetBool("kubernetes") namespace, _ := cmd.Flags().GetString("namespace") engineType, _ := cmd.Flags().GetString("engine-type") isApproved, _ := cmd.Flags().GetBool("auto-approve") // Setup Vault client - client := vaultengine.NewClient(vaultAddr, vaultToken, insecure, namespace) + client := vaultengine.NewClient(vaultAddr, vaultToken, insecure, namespace, vaultRole, kubernetes) engine, path, err := client.MountpathSplitPrefix(path) if err != nil { fmt.Println(err) diff --git a/cmd/export.go b/cmd/export.go index 716f607..6a0355f 100644 --- a/cmd/export.go +++ b/cmd/export.go @@ -3,6 +3,7 @@ package cmd import ( "errors" "fmt" + "github.com/jonasvinther/medusa/pkg/encrypt" "github.com/jonasvinther/medusa/pkg/vaultengine" @@ -27,6 +28,8 @@ var exportCmd = &cobra.Command{ path := args[0] vaultAddr, _ := cmd.Flags().GetString("address") vaultToken, _ := cmd.Flags().GetString("token") + vaultRole, _ := cmd.Flags().GetString("role") + kubernetes, _ := cmd.Flags().GetBool("kubernetes") insecure, _ := cmd.Flags().GetBool("insecure") namespace, _ := cmd.Flags().GetString("namespace") engineType, _ := cmd.Flags().GetString("engine-type") @@ -34,7 +37,7 @@ var exportCmd = &cobra.Command{ exportFormat, _ := cmd.Flags().GetString("format") output, _ := cmd.Flags().GetString("output") - client := vaultengine.NewClient(vaultAddr, vaultToken, insecure, namespace) + client := vaultengine.NewClient(vaultAddr, vaultToken, insecure, namespace, vaultRole, kubernetes) engine, path, err := client.MountpathSplitPrefix(path) if err != nil { fmt.Println(err) diff --git a/cmd/import.go b/cmd/import.go index 93c7627..8b0c20d 100644 --- a/cmd/import.go +++ b/cmd/import.go @@ -31,12 +31,14 @@ var importCmd = &cobra.Command{ vaultAddr, _ := cmd.Flags().GetString("address") vaultToken, _ := cmd.Flags().GetString("token") insecure, _ := cmd.Flags().GetBool("insecure") + vaultRole, _ := cmd.Flags().GetString("role") + kubernetes, _ := cmd.Flags().GetBool("kubernetes") namespace, _ := cmd.Flags().GetString("namespace") engineType, _ := cmd.Flags().GetString("engine-type") doDecrypt, _ := cmd.Flags().GetBool("decrypt") privateKey, _ := cmd.Flags().GetString("private-key") - client := vaultengine.NewClient(vaultAddr, vaultToken, insecure, namespace) + client := vaultengine.NewClient(vaultAddr, vaultToken, insecure, namespace, vaultRole, kubernetes) engine, prefix, err := client.MountpathSplitPrefix(path) if err != nil { fmt.Println(err) diff --git a/docs/examples/kubernetes/cronjob/README.md b/docs/examples/kubernetes/cronjob/README.md index e6d460d..da2cb27 100644 --- a/docs/examples/kubernetes/cronjob/README.md +++ b/docs/examples/kubernetes/cronjob/README.md @@ -131,6 +131,13 @@ medusa-1615982100-5tfl8 0/1 Completed 0 60s medusa-1615982160-4b527 0/1 Completed 0 9s ``` +### Using Kubernetes authentication +If you are using the kubernetes authentication method in Vault, it is also possible to use the kubernetes provided JWT token inside a Pod and auth role in order to authenticate. + +```yaml +command: ["./medusa", "export", "$(VAULT_PATH)", "--kubernetes", "--role=default", "-o", "/backup/backup.vault"] +``` + ### Further customization This only serves as an example as to how you could use `Medusa` to take a backup of Vault from a given location. diff --git a/go.mod b/go.mod index 92761e4..2c24570 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( github.com/hashicorp/vault/api v1.10.0 + github.com/hashicorp/vault/api/auth/kubernetes v0.5.0 github.com/manifoldco/promptui v0.9.0 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.16.0 diff --git a/go.sum b/go.sum index 3f89c63..bfe0a76 100644 --- a/go.sum +++ b/go.sum @@ -871,6 +871,8 @@ github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4 github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= github.com/hashicorp/vault/api v1.10.0 h1:/US7sIjWN6Imp4o/Rj1Ce2Nr5bki/AXi9vAW3p2tOJQ= github.com/hashicorp/vault/api v1.10.0/go.mod h1:jo5Y/ET+hNyz+JnKDt8XLAdKs+AM0G5W0Vp1IrFI8N8= +github.com/hashicorp/vault/api/auth/kubernetes v0.5.0 h1:CXO0fD7M3iCGovP/UApeHhPcH4paDFKcu7AjEXi94rI= +github.com/hashicorp/vault/api/auth/kubernetes v0.5.0/go.mod h1:afrElBIO9Q4sHFVuVWgNevG4uAs1bT2AZFA9aEiI608= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= diff --git a/pkg/vaultengine/vaultclient.go b/pkg/vaultengine/vaultclient.go index 6f4366d..c18c750 100644 --- a/pkg/vaultengine/vaultclient.go +++ b/pkg/vaultengine/vaultclient.go @@ -1,10 +1,12 @@ package vaultengine import ( + "context" "errors" "strings" vault "github.com/hashicorp/vault/api" + auth "github.com/hashicorp/vault/api/auth/kubernetes" ) // Client describes the arguments that is needed to to establish a connecting to a Vault instance @@ -14,17 +16,22 @@ type Client struct { namespace string engine string engineType string + role string + kubernetes bool insecure bool vc *vault.Client } // NewClient creates a instance of the VaultClient struct -func NewClient(addr, token string, insecure bool, namespace string) *Client { +func NewClient(addr, token string, insecure bool, namespace string, role string, kubernetes bool) *Client { client := &Client{ - token: token, - addr: addr, - insecure: insecure, - namespace: namespace} + token: token, + addr: addr, + insecure: insecure, + namespace: namespace, + role: role, + kubernetes: kubernetes, + } client.newVaultClient() @@ -99,5 +106,19 @@ func (client *Client) newVaultClient() error { client.vc.SetToken(client.token) } + // Authenticate using Kubernetes JWT if kubernetes flag is set + if client.kubernetes { + kubernetesAuth, err := auth.NewKubernetesAuth(client.role) + if err != nil { + return err + } + + authInfo, err := vc.Auth().Login(context.Background(), kubernetesAuth) + if err != nil { + return err + } + client.vc.SetToken(authInfo.Auth.ClientToken) + } + return nil }