-
Notifications
You must be signed in to change notification settings - Fork 785
/
vault_factory.go
149 lines (134 loc) · 4.91 KB
/
vault_factory.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
package vault
import (
"fmt"
"time"
"github.com/banzaicloud/bank-vaults/operator/pkg/client/clientset/versioned"
"github.com/hashicorp/vault/api"
"github.com/jenkins-x/jx/pkg/jx/cmd/common"
"github.com/jenkins-x/jx/pkg/kube/serviceaccount"
"github.com/jenkins-x/jx/pkg/util"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
const (
// maxRetries controls the maximum number of time retry when 5xx error occurs. Default to 2 (for a total
// of three retires)
maxRetries = 2
// healthReadyTimeout define the maximum duration to wait for vault to become initialized and unsealed
healthhRetyTimeout = 2 * time.Minute
// healthInitialRetryDelay define the initial delay before starting the retries
healthInitialRetryDelay = 10 * time.Second
)
type VaultClientFactory struct {
Options common.OptionsInterface
Selector Selector
kubeClient kubernetes.Interface
defaultNamespace string
}
// NewInteractiveVaultClientFactory creates a VaultClientFactory that allows the user to pick vaults if necessary
func NewInteractiveVaultClientFactory(options common.OptionsInterface) (*VaultClientFactory, error) {
factory := &VaultClientFactory{
Options: options,
}
var err error
factory.kubeClient, factory.defaultNamespace, err = options.KubeClientAndNamespace()
if err != nil {
return factory, err
}
factory.Selector, err = NewVaultSelector(options)
if err != nil {
return factory, err
}
return factory, nil
}
// NewVaultClientFactory Creates a new VaultClientFactory with different options to the above. It doesnt' have CLI support so
// will fail if it needs interactive input (unlikely)
func NewVaultClientFactory(kubeClient kubernetes.Interface, vaultOperatorClient versioned.Interface, defaultNamespace string) (*VaultClientFactory, error) {
return &VaultClientFactory{
kubeClient: kubeClient,
defaultNamespace: defaultNamespace,
Selector: &vaultSelector{
kubeClient: kubeClient,
vaultOperatorClient: vaultOperatorClient,
},
}, nil
}
// NewVaultClient creates a new api.Client
// if namespace is nil, then the default namespace of the factory will be used
// if the name is nil, and only one vault is found, then that vault will be used. Otherwise the user will be prompted to
// select a vault for the client.
func (v *VaultClientFactory) NewVaultClient(name string, namespace string) (*api.Client, error) {
config, jwt, role, err := v.GetConfigData(name, namespace)
if err != nil {
return nil, err
}
vaultClient, err := api.NewClient(config)
if err != nil {
return nil, errors.Wrap(err, "crating vault client")
}
err = waitForVault(vaultClient, healthInitialRetryDelay, healthhRetyTimeout)
if err != nil {
return nil, errors.Wrap(err, "wait for vault to be initialized and unsealed")
}
token, err := getTokenFromVault(role, jwt, vaultClient)
if err != nil {
return nil, errors.Wrapf(err, "getting Vault authentication token")
}
vaultClient.SetToken(token)
return vaultClient, nil
}
// GetConfigData generates the information necessary to configure an api.Client object
// Returns the api.Config object, the JWT needed to create the auth user in vault, and an error if present
func (v *VaultClientFactory) GetConfigData(name string, namespace string) (config *api.Config, jwt string, saName string, err error) {
if namespace == "" {
namespace = v.defaultNamespace
}
vlt, err := v.Selector.GetVault(name, namespace)
if err != nil {
return nil, "", "", err
}
serviceAccount, err := v.getServiceAccountFromVault(vlt)
token, err := serviceaccount.GetServiceAccountToken(v.kubeClient, namespace, serviceAccount.Name)
cfg := &api.Config{
Address: vlt.URL,
MaxRetries: maxRetries,
}
return cfg, token, serviceAccount.Name, err
}
func (v *VaultClientFactory) getServiceAccountFromVault(vault *Vault) (*v1.ServiceAccount, error) {
return v.kubeClient.CoreV1().ServiceAccounts(vault.Namespace).Get(vault.AuthServiceAccountName, meta_v1.GetOptions{})
}
func waitForVault(vaultClient *api.Client, initialDelay, timeout time.Duration) error {
return util.RetryWithInitialDelaySlower(initialDelay, timeout, func() error {
hr, err := vaultClient.Sys().Health()
if err == nil && hr != nil && hr.Initialized && !hr.Sealed {
return nil
}
if err != nil {
return errors.Wrap(err, "reading vault health")
}
if hr != nil {
return fmt.Errorf("vault health: initialized=%t, sealed=%t", hr.Initialized, hr.Sealed)
}
return errors.New("failed to read vault health")
})
}
func getTokenFromVault(role string, jwt string, vaultClient *api.Client) (string, error) {
if role == "" {
return "", errors.New("role cannot be empty")
}
if jwt == "" {
return "", errors.New("JWT cannot be empty empty")
}
m := map[string]interface{}{
"jwt": jwt,
"role": role,
}
sec, err := vaultClient.Logical().Write("/auth/kubernetes/login", m)
if err != nil {
return "", err
}
return sec.Auth.ClientToken, err
}