Skip to content

Commit f12549f

Browse files
authored
Merge pull request #2 from DoodleScheduling/kubedb-vault-secrets
Kubedb vault secrets
2 parents 7931e76 + 6d2d5d9 commit f12549f

File tree

14 files changed

+558
-59
lines changed

14 files changed

+558
-59
lines changed

common/vault/cache.go renamed to common/cache.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package vault
1+
package common
22

33
type Cache struct {
44
cache map[string]*Vault

common/db.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package common
2+
3+
import "github.com/doodlescheduling/kubedb/api/v1beta1"
4+
5+
type DatabaseCredentials []DatabaseCredential
6+
type DatabaseCredential struct {
7+
UserName string `json:"username"`
8+
Vault v1beta1.Vault `json:"vault"`
9+
}

common/vault.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package common
2+
3+
import (
4+
"context"
5+
"errors"
6+
"github.com/doodlescheduling/kubedb/api/v1beta1"
7+
"github.com/doodlescheduling/kubedb/common/vault"
8+
"github.com/doodlescheduling/kubedb/common/vault/kubernetes"
9+
"github.com/go-logr/logr"
10+
"github.com/rs/xid"
11+
"os"
12+
13+
vaultapi "github.com/hashicorp/vault/api"
14+
)
15+
16+
type VaultRequest struct {
17+
Path string
18+
UserField string
19+
SecretField string
20+
}
21+
22+
type VaultResponse struct {
23+
User string
24+
Secret string
25+
}
26+
27+
type Vault struct {
28+
Host string
29+
}
30+
31+
func NewVault(host string) (*Vault, error) {
32+
// TODO implement Vault integration
33+
return &Vault{
34+
Host: host,
35+
}, nil
36+
}
37+
38+
func (v *Vault) Get(cred *DatabaseCredential, username string, logger logr.Logger) (VaultResponse, error) {
39+
40+
// goran vault
41+
//h, err := FromCredential(binding, logger)
42+
h, err := FromCredential(cred, logger)
43+
44+
r := VaultRequest{
45+
Path: cred.Vault.Path,
46+
UserField: cred.Vault.UserField,
47+
SecretField: cred.Vault.SecretField,
48+
}
49+
50+
data, response, err := processRequest(h, r, username)
51+
52+
if err != nil {
53+
return response, err
54+
}
55+
56+
return VaultResponse{
57+
User: data[r.UserField].(string),
58+
Secret: data[r.SecretField].(string),
59+
}, err
60+
}
61+
62+
func processRequest(h *VaultHandler, r VaultRequest, databaseName string) (map[string]interface{}, VaultResponse, error) {
63+
data, err := h.Read(r.Path)
64+
if err != nil {
65+
return nil, VaultResponse{}, err
66+
}
67+
var rewrite = false
68+
_, existingField := data[r.UserField]
69+
if !existingField {
70+
data[r.UserField] = databaseName
71+
rewrite = true
72+
}
73+
_, existingField = data[r.SecretField]
74+
if !existingField {
75+
data[r.SecretField] = xid.New().String()
76+
rewrite = true
77+
}
78+
if rewrite {
79+
_, err = h.c.Logical().Write(r.Path, data)
80+
if err != nil {
81+
return nil, VaultResponse{}, err
82+
}
83+
}
84+
return data, VaultResponse{}, nil
85+
}
86+
87+
// part from k8svault controller
88+
89+
// Common errors
90+
var (
91+
ErrVaultAddrNotFound = errors.New("Neither vault address nor a default vault address found")
92+
ErrK8sSecretFieldNotAvailable = errors.New("K8s secret field to be mapped does not exist")
93+
ErrUnsupportedAuthType = errors.New("Unsupported vault authentication")
94+
ErrVaultConfig = errors.New("Failed to setup default vault configuration")
95+
)
96+
97+
func ConvertPostgreSQLDatabaseCredential(cred v1beta1.PostgreSQLDatabaseCredential) *DatabaseCredential {
98+
return &DatabaseCredential{
99+
UserName: cred.UserName,
100+
Vault: cred.Vault,
101+
}
102+
}
103+
104+
func ConvertMongoDBDatabaseCredential(cred v1beta1.MongoDBDatabaseCredential) *DatabaseCredential {
105+
return &DatabaseCredential{
106+
UserName: cred.UserName,
107+
Vault: cred.Vault,
108+
}
109+
}
110+
111+
// Setup vault client & authentication from binding
112+
func setupAuth(h *VaultHandler) error {
113+
auth := vault.NewAuthHandler(&vault.AuthHandlerConfig{
114+
Logger: h.logger,
115+
Client: h.c,
116+
})
117+
118+
var method vault.AuthMethod
119+
120+
m, err := authKubernetes(h)
121+
if err != nil {
122+
return err
123+
}
124+
125+
method = m
126+
127+
if err := auth.Authenticate(context.TODO(), method); err != nil {
128+
return err
129+
}
130+
131+
return nil
132+
}
133+
134+
// Wrapper around vault kubernetes auth (taken from vault agent)
135+
// Injects env variables if not set on the binding
136+
func authKubernetes(h *VaultHandler) (vault.AuthMethod, error) {
137+
role := os.Getenv("VAULT_ROLE")
138+
tokenPath := os.Getenv("VAULT_TOKEN_PATH")
139+
140+
return kubernetes.NewKubernetesAuthMethod(&vault.AuthConfig{
141+
Logger: h.logger,
142+
MountPath: "/auth/kubernetes",
143+
Config: map[string]interface{}{
144+
"role": role,
145+
"token_path": tokenPath,
146+
},
147+
})
148+
}
149+
150+
// FromCredential creates a vault client handler
151+
// If the binding holds no vault address it will fallback to the env VAULT_ADDRESS
152+
func FromCredential(credential *DatabaseCredential, logger logr.Logger) (*VaultHandler, error) {
153+
cfg := vaultapi.DefaultConfig()
154+
155+
if cfg == nil {
156+
return nil, ErrVaultConfig
157+
}
158+
159+
if credential.Vault.Host != "" {
160+
cfg.Address = credential.Vault.Host
161+
}
162+
163+
client, err := vaultapi.NewClient(cfg)
164+
if err != nil {
165+
return nil, err
166+
}
167+
168+
h := &VaultHandler{
169+
cfg: cfg,
170+
c: client,
171+
logger: logger,
172+
}
173+
174+
logger.Info("setup vault client", "vault", cfg.Address)
175+
176+
if err = setupAuth(h); err != nil {
177+
return nil, err
178+
}
179+
180+
return h, nil
181+
}
182+
183+
// VaultHandler
184+
type VaultHandler struct {
185+
c *vaultapi.Client
186+
cfg *vaultapi.Config
187+
auth *vault.AuthHandler
188+
logger logr.Logger
189+
}
190+
191+
// Read vault path and return data map
192+
// Return empty map if no data exists
193+
func (h *VaultHandler) Read(path string) (map[string]interface{}, error) {
194+
s, err := h.c.Logical().Read(path)
195+
if err != nil {
196+
return nil, err
197+
}
198+
199+
// Return empty map if no data exists
200+
if s == nil || s.Data == nil {
201+
return make(map[string]interface{}), nil
202+
}
203+
204+
return s.Data, nil
205+
}

common/vault/auth.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// This package implements authentication for vault
2+
// Most of this code within this package has been borrowed and shrinked from the vault client agent.
3+
// See https://github.com/hashicorp/vault/blob/master/command/agent/auth/auth.go
4+
5+
// Note this package uses API which is in the vault stable release but it was not released in the api package,
6+
// see https://github.com/hashicorp/vault/issues/10490
7+
8+
package vault
9+
10+
import (
11+
"context"
12+
"errors"
13+
"net/http"
14+
15+
"github.com/go-logr/logr"
16+
vaultapi "github.com/hashicorp/vault/api"
17+
)
18+
19+
// AuthMethod is the interface that auto-auth methods implement for the agent
20+
// to use.
21+
type AuthMethod interface {
22+
// Authenticate returns a mount path, header, request body, and error.
23+
// The header may be nil if no special header is needed.
24+
Authenticate(context.Context, *vaultapi.Client) (string, http.Header, map[string]interface{}, error)
25+
NewCreds() chan struct{}
26+
CredSuccess()
27+
Shutdown()
28+
}
29+
30+
// AuthMethodWithClient is an extended interface that can return an API client
31+
// for use during the authentication call.
32+
type AuthMethodWithClient interface {
33+
AuthMethod
34+
AuthClient(client *vaultapi.Client) (*vaultapi.Client, error)
35+
}
36+
37+
type AuthConfig struct {
38+
Logger logr.Logger
39+
MountPath string
40+
Config map[string]interface{}
41+
}
42+
43+
// AuthHandler is responsible for keeping a token alive and renewed and passing
44+
// new tokens to the sink server
45+
type AuthHandler struct {
46+
logger logr.Logger
47+
client *vaultapi.Client
48+
}
49+
50+
type AuthHandlerConfig struct {
51+
Logger logr.Logger
52+
Client *vaultapi.Client
53+
}
54+
55+
func NewAuthHandler(conf *AuthHandlerConfig) *AuthHandler {
56+
ah := &AuthHandler{
57+
logger: conf.Logger,
58+
client: conf.Client,
59+
}
60+
61+
return ah
62+
}
63+
64+
func (ah *AuthHandler) Authenticate(ctx context.Context, am AuthMethod) error {
65+
if am == nil {
66+
return errors.New("auth handler: nil auth method")
67+
}
68+
69+
path, _, data, err := am.Authenticate(ctx, ah.client)
70+
71+
if err != nil {
72+
ah.logger.Error(err, "error getting path or data from method")
73+
return err
74+
}
75+
76+
var clientToUse *vaultapi.Client
77+
78+
switch am.(type) {
79+
case AuthMethodWithClient:
80+
clientToUse, err = am.(AuthMethodWithClient).AuthClient(ah.client)
81+
if err != nil {
82+
ah.logger.Error(err, "error creating client for authentication call")
83+
return err
84+
}
85+
default:
86+
clientToUse = ah.client
87+
}
88+
89+
/*for key, values := range header {
90+
for _, value := range values {
91+
clientToUse.AddHeader(key, value)
92+
}
93+
}*/
94+
95+
secret, err := clientToUse.Logical().Write(path, data)
96+
97+
// Check errors/sanity
98+
if err != nil {
99+
ah.logger.Error(err, "error authenticating")
100+
return err
101+
}
102+
103+
if secret == nil || secret.Auth == nil {
104+
ah.logger.Error(err, "authentication returned nil auth info")
105+
return err
106+
}
107+
108+
if secret.Auth.ClientToken == "" {
109+
ah.logger.Error(err, "authentication returned empty client token")
110+
return err
111+
}
112+
113+
ah.logger.Info("authentication successful")
114+
ah.client.SetToken(secret.Auth.ClientToken)
115+
am.CredSuccess()
116+
117+
return nil
118+
}

0 commit comments

Comments
 (0)