diff --git a/cas/vaultcas/auth/approle/approle.go b/cas/vaultcas/auth/approle/approle.go index 38d3c51c..d842bae0 100644 --- a/cas/vaultcas/auth/approle/approle.go +++ b/cas/vaultcas/auth/approle/approle.go @@ -2,6 +2,7 @@ package approle import ( "encoding/json" + "errors" "fmt" "github.com/hashicorp/vault/api/auth/approle" @@ -12,6 +13,8 @@ import ( type AuthOptions struct { RoleID string `json:"roleID,omitempty"` SecretID string `json:"secretID,omitempty"` + SecretIDFile string `json:"secretIDFile,omitempty"` + SecretIDEnv string `json:"secretIDEnv,omitempty"` IsWrappingToken bool `json:"isWrappingToken,omitempty"` } @@ -33,8 +36,25 @@ func NewApproleAuthMethod(mountPath string, options json.RawMessage) (*approle.A loginOptions = append(loginOptions, approle.WithWrappingToken()) } - sid := approle.SecretID{ - FromString: opts.SecretID, + if opts.RoleID == "" { + return nil, errors.New("you must set roleID") + } + + var sid approle.SecretID + if opts.SecretID != "" { + sid = approle.SecretID{ + FromString: opts.SecretID, + } + } else if opts.SecretIDFile != "" { + sid = approle.SecretID{ + FromFile: opts.SecretIDFile, + } + } else if opts.SecretIDEnv != "" { + sid = approle.SecretID{ + FromEnv: opts.SecretIDEnv, + } + } else { + return nil, errors.New("you must set one of secretID, secretIDFile or secretIDEnv") } approleAuth, err = approle.NewAppRoleAuth(opts.RoleID, &sid, loginOptions...) diff --git a/cas/vaultcas/auth/approle/approle_test.go b/cas/vaultcas/auth/approle/approle_test.go index ab7e6a97..ec4d523f 100644 --- a/cas/vaultcas/auth/approle/approle_test.go +++ b/cas/vaultcas/auth/approle/approle_test.go @@ -1,16 +1,171 @@ package approle import ( + "context" "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" "testing" + + vault "github.com/hashicorp/vault/api" ) -func TestKubernetes_NewKubernetesAuthMethod(t *testing.T) { - mountPath := "approle" - raw := `{"roleID": "roleID", "secretID": "secretIDwrapped", "isWrappedToken": true}` +func testCAHelper(t *testing.T) (*url.URL, *vault.Client) { + t.Helper() - _, err := NewApproleAuthMethod(mountPath, json.RawMessage(raw)) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.RequestURI == "/v1/auth/approle/login": + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{ + "auth": { + "client_token": "hvs.0000" + } + }`) + case r.RequestURI == "/v1/auth/custom-approle/login": + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{ + "auth": { + "client_token": "hvs.9999" + } + }`) + default: + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, `{"error":"not found"}`) + } + })) + t.Cleanup(func() { + srv.Close() + }) + u, err := url.Parse(srv.URL) if err != nil { + srv.Close() t.Fatal(err) } + + config := vault.DefaultConfig() + config.Address = srv.URL + + client, err := vault.NewClient(config) + if err != nil { + srv.Close() + t.Fatal(err) + } + + return u, client +} + +func TestApprole_LoginMountPaths(t *testing.T) { + caURL, _ := testCAHelper(t) + + config := vault.DefaultConfig() + config.Address = caURL.String() + client, _ := vault.NewClient(config) + + tests := []struct { + name string + mountPath string + token string + }{ + { + name: "ok default mount path", + mountPath: "", + token: "hvs.0000", + }, + { + name: "ok explicit mount path", + mountPath: "approle", + token: "hvs.0000", + }, + { + name: "ok custom mount path", + mountPath: "custom-approle", + token: "hvs.9999", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + method, err := NewApproleAuthMethod(tt.mountPath, json.RawMessage(`{"RoleID":"roleID","SecretID":"secretID","IsWrappingToken":false}`)) + if err != nil { + t.Errorf("NewApproleAuthMethod() error = %v", err) + return + } + + secret, err := client.Auth().Login(context.Background(), method) + if err != nil { + t.Errorf("Login() error = %v", err) + return + } + + token, _ := secret.TokenID() + if token != tt.token { + t.Errorf("Token error got %v, expected %v", token, tt.token) + return + } + }) + } +} + +func TestApprole_NewApproleAuthMethod(t *testing.T) { + tests := []struct { + name string + mountPath string + raw string + wantErr bool + }{ + { + "ok secret-id string", + "", + `{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000"}`, + false, + }, + { + "ok secret-id string and wrapped", + "", + `{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "isWrappedToken": true}`, + false, + }, + { + "ok secret-id string and wrapped with custom mountPath", + "approle2", + `{"RoleID": "0000-0000-0000-0000", "SecretID": "0000-0000-0000-0000", "isWrappedToken": true}`, + false, + }, + { + "ok secret-id file", + "", + `{"RoleID": "0000-0000-0000-0000", "SecretIDFile": "./secret-id"}`, + false, + }, + { + "ok secret-id env", + "", + `{"RoleID": "0000-0000-0000-0000", "SecretIDEnv": "VAULT_APPROLE_SECRETID"}`, + false, + }, + { + "fail mandatory role-id", + "", + `{}`, + true, + }, + { + "fail mandatory secret-id any", + "", + `{"RoleID": "0000-0000-0000-0000"}`, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := NewApproleAuthMethod(tt.mountPath, json.RawMessage(tt.raw)) + if (err != nil) != tt.wantErr { + t.Errorf("Approle.NewApproleAuthMethod() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } } diff --git a/cas/vaultcas/auth/kubernetes/kubernetes.go b/cas/vaultcas/auth/kubernetes/kubernetes.go index 0c4db62f..267bcdca 100644 --- a/cas/vaultcas/auth/kubernetes/kubernetes.go +++ b/cas/vaultcas/auth/kubernetes/kubernetes.go @@ -2,6 +2,7 @@ package kubernetes import ( "encoding/json" + "errors" "fmt" "github.com/hashicorp/vault/api/auth/kubernetes" @@ -31,6 +32,11 @@ func NewKubernetesAuthMethod(mountPath string, options json.RawMessage) (*kubern if opts.TokenPath != "" { loginOptions = append(loginOptions, kubernetes.WithServiceAccountTokenPath(opts.TokenPath)) } + + if opts.Role == "" { + return nil, errors.New("you must set role") + } + kubernetesAuth, err = kubernetes.NewKubernetesAuth( opts.Role, loginOptions..., diff --git a/cas/vaultcas/auth/kubernetes/kubernetes_test.go b/cas/vaultcas/auth/kubernetes/kubernetes_test.go index 604f1898..55be904d 100644 --- a/cas/vaultcas/auth/kubernetes/kubernetes_test.go +++ b/cas/vaultcas/auth/kubernetes/kubernetes_test.go @@ -1,21 +1,149 @@ package kubernetes import ( + "context" "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" "path" "path/filepath" "runtime" "testing" + + vault "github.com/hashicorp/vault/api" ) -func TestKubernetes_NewKubernetesAuthMethod(t *testing.T) { - _, filename, _, _ := runtime.Caller(0) - tokenPath := filepath.Join(path.Dir(filename), "token") - mountPath := "kubernetes" - raw := `{"role": "SomeRoleName", "tokenPath": "` + tokenPath + `"}` +func testCAHelper(t *testing.T) (*url.URL, *vault.Client) { + t.Helper() - _, err := NewKubernetesAuthMethod(mountPath, json.RawMessage(raw)) + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch { + case r.RequestURI == "/v1/auth/kubernetes/login": + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{ + "auth": { + "client_token": "hvs.0000" + } + }`) + case r.RequestURI == "/v1/auth/custom-kubernetes/login": + w.WriteHeader(http.StatusOK) + fmt.Fprintf(w, `{ + "auth": { + "client_token": "hvs.9999" + } + }`) + default: + w.WriteHeader(http.StatusNotFound) + fmt.Fprintf(w, `{"error":"not found"}`) + } + })) + t.Cleanup(func() { + srv.Close() + }) + u, err := url.Parse(srv.URL) if err != nil { + srv.Close() t.Fatal(err) } + + config := vault.DefaultConfig() + config.Address = srv.URL + + client, err := vault.NewClient(config) + if err != nil { + srv.Close() + t.Fatal(err) + } + + return u, client +} + +func TestApprole_LoginMountPaths(t *testing.T) { + caURL, _ := testCAHelper(t) + _, filename, _, _ := runtime.Caller(0) + tokenPath := filepath.Join(path.Dir(filename), "token") + + config := vault.DefaultConfig() + config.Address = caURL.String() + client, _ := vault.NewClient(config) + + tests := []struct { + name string + mountPath string + token string + }{ + { + name: "ok default mount path", + mountPath: "", + token: "hvs.0000", + }, + { + name: "ok explicit mount path", + mountPath: "kubernetes", + token: "hvs.0000", + }, + { + name: "ok custom mount path", + mountPath: "custom-kubernetes", + token: "hvs.9999", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + method, err := NewKubernetesAuthMethod(tt.mountPath, json.RawMessage(`{"role": "SomeRoleName", "tokenPath": "`+tokenPath+`"}`)) + if err != nil { + t.Errorf("NewApproleAuthMethod() error = %v", err) + return + } + + secret, err := client.Auth().Login(context.Background(), method) + if err != nil { + t.Errorf("Login() error = %v", err) + return + } + + token, _ := secret.TokenID() + if token != tt.token { + t.Errorf("Token error got %v, expected %v", token, tt.token) + return + } + }) + } +} + +func TestApprole_NewApproleAuthMethod(t *testing.T) { + _, filename, _, _ := runtime.Caller(0) + tokenPath := filepath.Join(path.Dir(filename), "token") + + tests := []struct { + name string + mountPath string + raw string + wantErr bool + }{ + { + "ok secret-id string", + "", + `{"role": "SomeRoleName", "tokenPath": "` + tokenPath + `"}`, + false, + }, + { + "fail mandatory role", + "", + `{}`, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, err := NewKubernetesAuthMethod(tt.mountPath, json.RawMessage(tt.raw)) + if (err != nil) != tt.wantErr { + t.Errorf("Kubernetes.NewKubernetesAuthMethod() error = %v, wantErr %v", err, tt.wantErr) + return + } + }) + } } diff --git a/cas/vaultcas/vaultcas_test.go b/cas/vaultcas/vaultcas_test.go index 3c1f09a3..0ea0c4b1 100644 --- a/cas/vaultcas/vaultcas_test.go +++ b/cas/vaultcas/vaultcas_test.go @@ -182,8 +182,6 @@ func TestNew_register(t *testing.T) { CertificateAuthority: caURL.String(), CertificateAuthorityFingerprint: testRootFingerprint, Config: json.RawMessage(`{ - "PKIMountPath": "pki", - "PKIRoleDefault": "pki-role", "AuthType": "approle", "AuthOptions": {"RoleID":"roleID","SecretID":"secretID","IsWrappingToken":false} }`), @@ -204,8 +202,6 @@ func TestVaultCAS_CreateCertificate(t *testing.T) { PKIRoleRSA: "rsa", PKIRoleEC: "ec", PKIRoleEd25519: "ed25519", - AuthType: "approle", - AuthOptions: json.RawMessage(`{"RoleID":"roleID","SecretID":"secretID","IsWrappingToken":false}`), } type fields struct { @@ -336,8 +332,6 @@ func TestVaultCAS_RevokeCertificate(t *testing.T) { PKIRoleRSA: "rsa", PKIRoleEC: "ec", PKIRoleEd25519: "ed25519", - AuthType: "approle", - AuthOptions: json.RawMessage(`{"RoleID":"roleID","SecretID":"secretID","IsWrappingToken":false}`), } type fields struct { @@ -406,8 +400,6 @@ func TestVaultCAS_RenewCertificate(t *testing.T) { PKIRoleRSA: "rsa", PKIRoleEC: "ec", PKIRoleEd25519: "ed25519", - AuthType: "approle", - AuthOptions: json.RawMessage(`{"RoleID":"roleID","SecretID":"secretID","IsWrappingToken":false}`), } type fields struct {