1package alicloud
2
3import (
4	"context"
5	"encoding/json"
6	"errors"
7	"fmt"
8	"strings"
9	"time"
10
11	"github.com/hashicorp/go-uuid"
12	"github.com/hashicorp/vault/sdk/framework"
13	"github.com/hashicorp/vault/sdk/logical"
14)
15
16func (b *backend) pathListRoles() *framework.Path {
17	return &framework.Path{
18		Pattern: "role/?$",
19		Callbacks: map[logical.Operation]framework.OperationFunc{
20			logical.ListOperation: b.operationRolesList,
21		},
22		HelpSynopsis:    pathListRolesHelpSyn,
23		HelpDescription: pathListRolesHelpDesc,
24	}
25}
26
27func (b *backend) pathRole() *framework.Path {
28	return &framework.Path{
29		Pattern: "role/" + framework.GenericNameRegex("name"),
30		Fields: map[string]*framework.FieldSchema{
31			"name": {
32				Type:        framework.TypeLowerCaseString,
33				Description: "The name of the role.",
34			},
35			"role_arn": {
36				Type: framework.TypeString,
37				Description: `ARN of the role to be assumed. If provided, inline_policies and
38remote_policies should be blank. At creation time, this role must have configured trusted actors,
39and the access key and secret that will be used to assume the role (in /config) must qualify
40as a trusted actor.`,
41			},
42			"inline_policies": {
43				Type:        framework.TypeString,
44				Description: "JSON of policies to be dynamically applied to users of this role.",
45			},
46			"remote_policies": {
47				Type: framework.TypeStringSlice,
48				Description: `The name and type of each remote policy to be applied.
49Example: "name:AliyunRDSReadOnlyAccess,type:System".`,
50			},
51			"ttl": {
52				Type: framework.TypeDurationSecond,
53				Description: `Duration in seconds after which the issued token should expire. Defaults
54to 0, in which case the value will fallback to the system/mount defaults.`,
55			},
56			"max_ttl": {
57				Type:        framework.TypeDurationSecond,
58				Description: "The maximum allowed lifetime of tokens issued using this role.",
59			},
60		},
61		ExistenceCheck: b.operationRoleExistenceCheck,
62		Callbacks: map[logical.Operation]framework.OperationFunc{
63			logical.CreateOperation: b.operationRoleCreateUpdate,
64			logical.UpdateOperation: b.operationRoleCreateUpdate,
65			logical.ReadOperation:   b.operationRoleRead,
66			logical.DeleteOperation: b.operationRoleDelete,
67		},
68		HelpSynopsis:    pathRolesHelpSyn,
69		HelpDescription: pathRolesHelpDesc,
70	}
71}
72
73func (b *backend) operationRoleExistenceCheck(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) {
74	entry, err := readRole(ctx, req.Storage, data.Get("name").(string))
75	if err != nil {
76		return false, err
77	}
78	return entry != nil, nil
79}
80
81func (b *backend) operationRoleCreateUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
82	roleName := data.Get("name").(string)
83	if roleName == "" {
84		return nil, errors.New("name is required")
85	}
86
87	role, err := readRole(ctx, req.Storage, roleName)
88	if err != nil {
89		return nil, err
90	}
91	if role == nil && req.Operation == logical.UpdateOperation {
92		return nil, fmt.Errorf("no role found to update for %s", roleName)
93	} else if role == nil {
94		role = &roleEntry{}
95	}
96
97	if raw, ok := data.GetOk("role_arn"); ok {
98		role.RoleARN = raw.(string)
99	}
100	if raw, ok := data.GetOk("inline_policies"); ok {
101		policyDocsStr := raw.(string)
102
103		var policyDocs []map[string]interface{}
104		if err := json.Unmarshal([]byte(policyDocsStr), &policyDocs); err != nil {
105			return nil, err
106		}
107
108		// If any inline policies were set before, we need to clear them and consider
109		// these the new ones.
110		role.InlinePolicies = make([]*inlinePolicy, len(policyDocs))
111
112		for i, policyDoc := range policyDocs {
113			uid, err := uuid.GenerateUUID()
114			if err != nil {
115				return nil, err
116			}
117			uid = strings.Replace(uid, "-", "", -1)
118			role.InlinePolicies[i] = &inlinePolicy{
119				UUID:           uid,
120				PolicyDocument: policyDoc,
121			}
122		}
123	}
124	if raw, ok := data.GetOk("remote_policies"); ok {
125		strPolicies := raw.([]string)
126
127		// If any remote policies were set before, we need to clear them and consider
128		// these the new ones.
129		role.RemotePolicies = make([]*remotePolicy, len(strPolicies))
130
131		for i, strPolicy := range strPolicies {
132			policy := &remotePolicy{}
133			kvPairs := strings.Split(strPolicy, ",")
134			for _, kvPair := range kvPairs {
135				kvFields := strings.Split(kvPair, ":")
136				if len(kvFields) != 2 {
137					return nil, fmt.Errorf("unable to recognize pair in %s", kvPair)
138				}
139				switch kvFields[0] {
140				case "name":
141					policy.Name = kvFields[1]
142				case "type":
143					policy.Type = kvFields[1]
144				default:
145					return nil, fmt.Errorf("invalid key: %s", kvFields[0])
146				}
147			}
148			if policy.Name == "" {
149				return nil, fmt.Errorf("policy name is required in %s", strPolicy)
150			}
151			if policy.Type == "" {
152				return nil, fmt.Errorf("policy type is required in %s", strPolicy)
153			}
154			role.RemotePolicies[i] = policy
155		}
156	}
157	if raw, ok := data.GetOk("ttl"); ok {
158		role.TTL = time.Duration(raw.(int)) * time.Second
159	}
160	if raw, ok := data.GetOk("max_ttl"); ok {
161		role.MaxTTL = time.Duration(raw.(int)) * time.Second
162	}
163
164	// Now that the role is built, validate it.
165	if role.MaxTTL > 0 && role.TTL > role.MaxTTL {
166		return nil, errors.New("ttl exceeds max_ttl")
167	}
168	if role.Type() == roleTypeSTS {
169		if len(role.RemotePolicies) > 0 {
170			return nil, errors.New("remote_policies must be blank when an arn is present")
171		}
172		if len(role.InlinePolicies) > 0 {
173			return nil, errors.New("inline_policies must be blank when an arn is present")
174		}
175	} else if len(role.InlinePolicies)+len(role.RemotePolicies) == 0 {
176		return nil, errors.New("must include an arn, or at least one of inline_policies or remote_policies")
177	}
178
179	entry, err := logical.StorageEntryJSON("role/"+roleName, role)
180	if err != nil {
181		return nil, err
182	}
183	if err := req.Storage.Put(ctx, entry); err != nil {
184		return nil, err
185	}
186
187	// Let's create a response that we're only going to return if there are warnings.
188	resp := &logical.Response{}
189	if role.Type() == roleTypeSTS && (role.TTL > 0 || role.MaxTTL > 0) {
190		resp.AddWarning("role_arn is set so ttl and max_ttl will be ignored because they're not editable on STS tokens")
191	}
192	if role.TTL > b.System().MaxLeaseTTL() {
193		resp.AddWarning(fmt.Sprintf("ttl of %d exceeds the system max ttl of %d, the latter will be used during login", role.TTL, b.System().MaxLeaseTTL()))
194	}
195	if len(resp.Warnings) > 0 {
196		return resp, nil
197	}
198	// No warnings, let's return a 204.
199	return nil, nil
200}
201
202func (b *backend) operationRoleRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
203	roleName := data.Get("name").(string)
204	if roleName == "" {
205		return nil, errors.New("name is required")
206	}
207
208	role, err := readRole(ctx, req.Storage, roleName)
209	if err != nil {
210		return nil, err
211	}
212	if role == nil {
213		return nil, nil
214	}
215	return &logical.Response{
216		Data: map[string]interface{}{
217			"role_arn":        role.RoleARN,
218			"remote_policies": role.RemotePolicies,
219			"inline_policies": role.InlinePolicies,
220			"ttl":             role.TTL / time.Second,
221			"max_ttl":         role.MaxTTL / time.Second,
222		},
223	}, nil
224}
225
226func (b *backend) operationRoleDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
227	if err := req.Storage.Delete(ctx, "role/"+data.Get("name").(string)); err != nil {
228		return nil, err
229	}
230	return nil, nil
231}
232
233func (b *backend) operationRolesList(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
234	entries, err := req.Storage.List(ctx, "role/")
235	if err != nil {
236		return nil, err
237	}
238	return logical.ListResponse(entries), nil
239}
240
241func readRole(ctx context.Context, s logical.Storage, roleName string) (*roleEntry, error) {
242	role, err := s.Get(ctx, "role/"+roleName)
243	if err != nil {
244		return nil, err
245	}
246	if role == nil {
247		return nil, nil
248	}
249	result := &roleEntry{}
250	if err := role.DecodeJSON(result); err != nil {
251		return nil, err
252	}
253	return result, nil
254}
255
256type roleType int
257
258const (
259	roleTypeUnknown roleType = iota
260	roleTypeRAM
261	roleTypeSTS
262)
263
264func parseRoleType(nameOfRoleType string) (roleType, error) {
265	switch nameOfRoleType {
266	case "ram":
267		return roleTypeRAM, nil
268	case "sts":
269		return roleTypeSTS, nil
270	default:
271		return roleTypeUnknown, fmt.Errorf("received unknown role type: %s", nameOfRoleType)
272	}
273}
274
275func (t roleType) String() string {
276	switch t {
277	case roleTypeRAM:
278		return "ram"
279	case roleTypeSTS:
280		return "sts"
281	}
282	return "unknown"
283}
284
285type roleEntry struct {
286	RoleARN        string          `json:"role_arn"`
287	RemotePolicies []*remotePolicy `json:"remote_policies"`
288	InlinePolicies []*inlinePolicy `json:"inline_policies"`
289	TTL            time.Duration   `json:"ttl"`
290	MaxTTL         time.Duration   `json:"max_ttl"`
291}
292
293func (r *roleEntry) Type() roleType {
294	if r.RoleARN != "" {
295		return roleTypeSTS
296	}
297	return roleTypeRAM
298}
299
300// Policies don't have ARNs and instead, their unique combination of their name and type comprise
301// their unique identifier.
302type remotePolicy struct {
303	Name string `json:"name"`
304	Type string `json:"type"`
305}
306
307type inlinePolicy struct {
308	// UUID is used in naming the policy. The policy document has no fields
309	// that would reliably be there and make a beautiful, human-readable name.
310	// So instead, we generate a UUID for it and use that in the policy name,
311	// which is likewise returned when roles are read so generated policy names
312	// can be tied back to which policy document they're for.
313	UUID           string                 `json:"hash"`
314	PolicyDocument map[string]interface{} `json:"policy_document"`
315}
316
317const pathListRolesHelpSyn = "List the existing roles in this backend."
318
319const pathListRolesHelpDesc = "Roles will be listed by the role name."
320
321const pathRolesHelpSyn = `
322Read, write and reference policies and roles that API keys or STS credentials can be made for.
323`
324
325const pathRolesHelpDesc = `
326This path allows you to read and write roles that are used to
327create API keys or STS credentials.
328
329If you supply a role ARN, that role must have been created to allow trusted actors,
330and the access key and secret that will be used to call AssumeRole (configured at
331the /config path) must qualify as a trusted actor.
332
333If you instead supply inline and/or remote policies to be applied, a user and API
334key will be dynamically created. The remote policies will be applied to that user,
335and the inline policies will also be dynamically created and applied.
336
337To obtain an API key or STS credential after the role is created, if the
338backend is mounted at "alicloud" and you create a role at "alicloud/roles/deploy",
339then a user could request access credentials at "alicloud/creds/deploy".
340
341To validate the keys, attempt to read an access key after writing the policy.
342`
343