Repository navigation
Expand file tree
/
Copy pathpath_creds.go
More file actions
178 lines (149 loc) · 5.06 KB
/
Copy pathpath_creds.go
File metadata and controls
178 lines (149 loc) · 5.06 KB
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package kuma
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"
"time"
"github.com/gobwas/glob"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)
const (
// nolint:gosec
pathCredsHelpSyn = `Generate a Kuma token from a specific Vault role.`
pathCredsHelpDesc = `This path generates a Kuma token based on a particular role.`
dayHours = float64(24)
)
// pathCreds extends the Vault API with a `/creds`
// endpoint for a role.
func pathCreds(b *kumaBackend) *framework.Path {
return &framework.Path{
Pattern: "creds/" + framework.GenericNameRegex("name"),
Fields: map[string]*framework.FieldSchema{
"name": {
Type: framework.TypeLowerCaseString,
Description: "Name of the role",
Required: true,
},
"token_name": {
Type: framework.TypeLowerCaseString,
Description: "Name of the token, must match role value. If ommitted, value from role is used",
Required: false,
},
},
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathCredsRead,
logical.UpdateOperation: b.pathCredsRead,
},
HelpSynopsis: pathCredsHelpSyn,
HelpDescription: pathCredsHelpDesc,
}
}
// pathCredentialsRead creates a new Kuma Token each time it is called if a
// role exists.
func (b *kumaBackend) pathCredsRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
roleName := d.Get("name").(string)
roleEntry, err := b.getRole(ctx, req.Storage, roleName)
if err != nil {
return nil, fmt.Errorf("error retrieving role: %w", err)
}
if roleEntry == nil {
return nil, errors.New("error retrieving role: role is nil")
}
return b.createCreds(ctx, req, roleName, roleEntry)
}
// createCreds creates a new Kuma access token to store into the Vault backend, generates
// a response with the robot account information, and checks the TTL and MaxTTL attributes.
func (b *kumaBackend) createCreds(
ctx context.Context,
req *logical.Request,
roleName string,
role *kumaRoleEntry) (*logical.Response, error) {
name := req.GetString("token_name")
if name == "" {
// if name is not specified check to see if the role does not have a globbed pattern and use that, else return an error
re := `[\{\}\!\[\]\*\?]`
r := regexp.MustCompile(re)
if r.MatchString(role.TokenName) {
return logical.ErrorResponse(
"unable to generate token, error: when token_name in the role %s contains a globbed pattern %s, you must pass the 'token_name' parameter with an absolute value when creating credentials",
name,
role.TokenName), nil
}
name = role.TokenName
} else {
// if name is specified check that it matches the role, role allows globbed patterns
g := glob.MustCompile(role.TokenName)
if !g.Match(name) {
return logical.ErrorResponse("unable to generate token, error: token_name %s must match the globbed pattern in the role %s", name, role.TokenName), nil
}
}
b.Logger().Info("Create new token for dataplane", "name", name, "operation", req.Operation)
// fetch a client instance, client is a property of backend but there is not guarantee that
// it has been instantiated
client, err := b.getClient(ctx, req.Storage)
if err != nil {
return nil, err
}
if client == nil {
return nil, fmt.Errorf("error getting Kuma client")
}
token := ""
tokenType := ""
// if tags generate a dataplane token
if len(role.Tags) > 0 {
b.Logger().Info("Generate dataplane token", "tags", role.Tags)
t, err := client.dpTokenClient.Generate(name, role.Mesh, role.Tags, ProxyTypeDataplane, role.MaxTTL)
if err != nil {
return logical.ErrorResponse("unable to generate dataplane token, error:", err), nil
}
token = t
tokenType = kumaTokenDataplane
}
// if groups generate a user token
if len(role.Groups) > 0 {
b.Logger().Info("Generate user token", "groups", role.Groups)
t, err := client.userTokenClient.Generate(name, role.Groups, role.MaxTTL)
if err != nil {
return logical.ErrorResponse("unable to generate user token, error:", err), nil
}
token = t
tokenType = kumaTokenUser
}
// parse the token to get the jti, we need this to revoke tokens
parts := strings.Split(token, ".")
var body map[string]interface{}
bodyBytes, _ := base64.RawURLEncoding.DecodeString(parts[1])
json.Unmarshal(bodyBytes, &body)
tokenID := ""
if tid, ok := body["jti"].(string); ok {
tokenID = tid
} else {
return logical.ErrorResponse("generated token does not contain a jti"), nil
}
// The response is divided into two objects (1) internal data and (2) data.
// add the jti and the expiry time in seconds to the collection so we can use if as part of the revocation process
resp := b.Secret(kumaTokenAccountType).Response(
map[string]interface{}{
"token": token,
},
map[string]interface{}{
"role": roleName,
"jti": tokenID,
"expiry": time.Now().Add(role.MaxTTL).UnixNano(),
"type": tokenType,
"mesh": role.Mesh,
},
)
if role.TTL > 0 {
resp.Secret.TTL = role.TTL
}
if role.MaxTTL > 0 {
resp.Secret.MaxTTL = role.MaxTTL
}
return resp, nil
}