Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: support multi vault addr and namespace in one argo #590

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ You can also use the `argocd app diff` command passing the `--hard-refresh` flag
### Caveats

#### Caching the Hashicorp Vault Token
The plugin tries to cache the Vault token obtained from logging into Vault on the `argocd-repo-server`'s container's disk, at `~/.avp/config.json` for the duration of the token's lifetime. This of course requires that the container user is able to write to that path. Some environments, like Openshift, will force a random user for containers to run with; therefore this feature will not work, and the plugin will attempt to login to Vault on every run. This can be fixed by ensuring the `argocd-repo-server`'s container runs with the user `argocd`.
The plugin tries to cache the Vault token obtained from logging into Vault on the `argocd-repo-server`'s container's disk, at `~/.avp/config[_<vault addr hash>][_<vault_namespace>].json` for the duration of the token's lifetime. This of course requires that the container user is able to write to that path. Some environments, like Openshift, will force a random user for containers to run with; therefore this feature will not work, and the plugin will attempt to login to Vault on every run. This can be fixed by ensuring the `argocd-repo-server`'s container runs with the user `argocd`.

#### Running argocd-vault-plugin in a sidecar container
As mentioned in the [Installation page](../installation), Argo CD has a newer method of installing custom plugins via sidecar containers to the `argocd-repo-server` deployment. Here are some caveats with running in this configuration:
Expand Down
4 changes: 2 additions & 2 deletions pkg/auth/vault/approle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestAppRoleLogin(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

cachedToken, err := utils.ReadExistingToken()
cachedToken, err := utils.ReadExistingToken(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand All @@ -30,7 +30,7 @@ func TestAppRoleLogin(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

newCachedToken, err := utils.ReadExistingToken()
newCachedToken, err := utils.ReadExistingToken(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/auth/vault/github_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestGithubLogin(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

cachedToken, err := utils.ReadExistingToken()
cachedToken, err := utils.ReadExistingToken(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand All @@ -31,7 +31,7 @@ func TestGithubLogin(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

newCachedToken, err := utils.ReadExistingToken()
newCachedToken, err := utils.ReadExistingToken(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/auth/vault/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestKubernetesAuth(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

cachedToken, err := utils.ReadExistingToken()
cachedToken, err := utils.ReadExistingToken(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand All @@ -63,7 +63,7 @@ func TestKubernetesAuth(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

newCachedToken, err := utils.ReadExistingToken()
newCachedToken, err := utils.ReadExistingToken(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/auth/vault/userpass_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestUserPassLogin(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

cachedToken, err := utils.ReadExistingToken()
cachedToken, err := utils.ReadExistingToken(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand All @@ -29,7 +29,7 @@ func TestUserPassLogin(t *testing.T) {
t.Fatalf("expected no errors but got: %s", err)
}

newCachedToken, err := utils.ReadExistingToken()
newCachedToken, err := utils.ReadExistingToken(cluster.Cores[0].Client)
if err != nil {
t.Fatalf("expected cached vault token but got: %s", err)
}
Expand Down
27 changes: 23 additions & 4 deletions pkg/utils/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,35 @@ import (
"os"
"path/filepath"
"time"
"crypto/sha1"
"encoding/base64"

"github.com/hashicorp/vault/api"
"github.com/spf13/viper"
)

func ReadExistingToken() ([]byte, error) {
func GetConfigFileName(vaultClient *api.Client) (string) {
var config_prefix = "config"
var config_ext = ".json"
var config_name = "_" + vaultClient.Namespace()

hasher := sha1.New()
hasher.Write([]byte(vaultClient.Address()))
var config_addr_name = "_" + base64.URLEncoding.EncodeToString(hasher.Sum(nil))

config := config_prefix + config_addr_name + config_name + config_ext

return config
}

func ReadExistingToken(vaultClient *api.Client) ([]byte, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}


avpConfigPath := filepath.Join(home, ".avp", "config.json")
avpConfigPath := filepath.Join(home, ".avp", GetConfigFileName(vaultClient))
if _, err := os.Stat(avpConfigPath); err != nil {
return nil, err
}
Expand All @@ -45,7 +62,7 @@ func ReadExistingToken() ([]byte, error) {
// LoginWithCachedToken takes a VaultType interface and tries to log in with the previously cached token,
// And sets the token in the client
func LoginWithCachedToken(vaultClient *api.Client) error {
byteValue, err := ReadExistingToken()
byteValue, err := ReadExistingToken(vaultClient)
if err != nil {
return err
}
Expand Down Expand Up @@ -86,13 +103,15 @@ func SetToken(vaultClient *api.Client, token string) error {
}

data := map[string]interface{}{
"vault_addr": os.Getenv("VAULT_ADDR"),
"vault_namespace": os.Getenv("VAULT_NAMESPACE"),
"vault_token": token,
}
file, err := json.MarshalIndent(data, "", " ")
if err != nil {
return fmt.Errorf("Could not marshal token data: %s", err.Error())
}
err = os.WriteFile(filepath.Join(path, "config.json"), file, 0644)
err = os.WriteFile(filepath.Join(path, GetConfigFileName(vaultClient)), file, 0644)
if err != nil {
return fmt.Errorf("Could not write token to file, will need to login to Vault on subsequent runs: %s", err.Error())
}
Expand Down
15 changes: 9 additions & 6 deletions pkg/utils/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,25 @@ import (
"testing"
"time"

"github.com/hashicorp/vault/api"
"github.com/argoproj-labs/argocd-vault-plugin/pkg/helpers"
"github.com/argoproj-labs/argocd-vault-plugin/pkg/utils"
)

func writeToken(token string) error {
func writeToken(token string, client *api.Client) error {
home, err := os.UserHomeDir()
if err != nil {
return err
}
path := filepath.Join(home, ".avp")
os.Mkdir(path, 0755)
data := map[string]interface{}{
"vault_addr": os.Getenv("VAULT_ADDR"),
"vault_namespace": os.Getenv("VAULT_NAMESPACE"),
"vault_token": token,
}
file, _ := json.MarshalIndent(data, "", " ")
err = os.WriteFile(filepath.Join(path, "config.json"), file, 0644)
err = os.WriteFile(filepath.Join(path, utils.GetConfigFileName(client)), file, 0644)
if err != nil {
return err
}
Expand All @@ -43,9 +46,9 @@ func removeToken() error {
return nil
}

func readToken() interface{} {
func readToken(client *api.Client) interface{} {
home, _ := os.UserHomeDir()
path := filepath.Join(home, ".avp", "config.json")
path := filepath.Join(home, ".avp", utils.GetConfigFileName(client))
dat, _ := os.ReadFile(path)
var result map[string]interface{}
json.Unmarshal([]byte(dat), &result)
Expand All @@ -57,7 +60,7 @@ func TestCheckExistingToken(t *testing.T) {
defer ln.Close()

t.Run("will set token if valid", func(t *testing.T) {
err := writeToken(roottoken)
err := writeToken(roottoken, client)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -88,7 +91,7 @@ func TestCheckExistingToken(t *testing.T) {
}

dir, _ := os.UserHomeDir()
expected := fmt.Sprintf("stat %s/.avp/config.json: no such file or directory", dir)
expected := fmt.Sprintf("stat %s/.avp/%s: no such file or directory", dir, utils.GetConfigFileName(client))
if err.Error() != expected {
t.Errorf("expected: %s, got: %s.", expected, err.Error())
}
Expand Down