-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
kubernetes.go
136 lines (116 loc) · 3.84 KB
/
kubernetes.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
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package kubernetes
import (
"context"
"fmt"
"os"
"github.com/hashicorp/vault/api"
)
type KubernetesAuth struct {
roleName string
mountPath string
serviceAccountToken string
}
var _ api.AuthMethod = (*KubernetesAuth)(nil)
type LoginOption func(a *KubernetesAuth) error
const (
defaultMountPath = "kubernetes"
defaultServiceAccountTokenPath = "/var/run/secrets/kubernetes.io/serviceaccount/token"
)
// NewKubernetesAuth creates a KubernetesAuth struct which can be passed to
// the client.Auth().Login method to authenticate to Vault. The roleName
// parameter should be the name of the role in Vault that was created with
// this app's Kubernetes service account bound to it.
//
// The Kubernetes service account token JWT is retrieved from
// /var/run/secrets/kubernetes.io/serviceaccount/token by default. To change this
// path, pass the WithServiceAccountTokenPath option. To instead pass the
// JWT directly as a string, or to read the value from an environment
// variable, use WithServiceAccountToken and WithServiceAccountTokenEnv respectively.
//
// Supported options: WithMountPath, WithServiceAccountTokenPath, WithServiceAccountTokenEnv, WithServiceAccountToken
func NewKubernetesAuth(roleName string, opts ...LoginOption) (*KubernetesAuth, error) {
if roleName == "" {
return nil, fmt.Errorf("no role name was provided")
}
a := &KubernetesAuth{
roleName: roleName,
mountPath: defaultMountPath,
}
// Loop through each option
for _, opt := range opts {
// Call the option giving the instantiated
// *KubernetesAuth as the argument
err := opt(a)
if err != nil {
return nil, fmt.Errorf("error with login option: %w", err)
}
}
if a.serviceAccountToken == "" {
token, err := readTokenFromFile(defaultServiceAccountTokenPath)
if err != nil {
return nil, fmt.Errorf("error reading service account token from default location: %w", err)
}
a.serviceAccountToken = token
}
// return the modified auth struct instance
return a, nil
}
func (a *KubernetesAuth) Login(ctx context.Context, client *api.Client) (*api.Secret, error) {
if ctx == nil {
ctx = context.Background()
}
loginData := map[string]interface{}{
"jwt": a.serviceAccountToken,
"role": a.roleName,
}
path := fmt.Sprintf("auth/%s/login", a.mountPath)
resp, err := client.Logical().WriteWithContext(ctx, path, loginData)
if err != nil {
return nil, fmt.Errorf("unable to log in with Kubernetes auth: %w", err)
}
return resp, nil
}
func WithMountPath(mountPath string) LoginOption {
return func(a *KubernetesAuth) error {
a.mountPath = mountPath
return nil
}
}
// WithServiceAccountTokenPath allows you to specify a different path to
// where your application's Kubernetes service account token is mounted,
// instead of the default of /var/run/secrets/kubernetes.io/serviceaccount/token
func WithServiceAccountTokenPath(pathToToken string) LoginOption {
return func(a *KubernetesAuth) error {
token, err := readTokenFromFile(pathToToken)
if err != nil {
return fmt.Errorf("unable to read service account token from file: %w", err)
}
a.serviceAccountToken = token
return nil
}
}
func WithServiceAccountToken(jwt string) LoginOption {
return func(a *KubernetesAuth) error {
a.serviceAccountToken = jwt
return nil
}
}
func WithServiceAccountTokenEnv(envVar string) LoginOption {
return func(a *KubernetesAuth) error {
token := os.Getenv(envVar)
if token == "" {
return fmt.Errorf("service account token was specified with an environment variable with an empty value")
}
a.serviceAccountToken = token
return nil
}
}
func readTokenFromFile(filepath string) (string, error) {
jwt, err := os.ReadFile(filepath)
if err != nil {
return "", fmt.Errorf("unable to read file containing service account token: %w", err)
}
return string(jwt), nil
}