1package alicloud
2
3import (
4	"context"
5	"errors"
6
7	"github.com/hashicorp/vault/sdk/framework"
8	"github.com/hashicorp/vault/sdk/logical"
9)
10
11func (b *backend) pathConfig() *framework.Path {
12	return &framework.Path{
13		Pattern: "config",
14		Fields: map[string]*framework.FieldSchema{
15			"access_key": {
16				Type:        framework.TypeString,
17				Description: "Access key with appropriate permissions.",
18			},
19			"secret_key": {
20				Type:        framework.TypeString,
21				Description: "Secret key with appropriate permissions.",
22			},
23		},
24		Callbacks: map[logical.Operation]framework.OperationFunc{
25			// Your access key and secret are generated together at the same time,
26			// so you always need to clobber your previous ones. Thus, we don't need two separate operations.
27			// When we don't use an existence check, all operations come through as an update operation,
28			// which is why it's the one fulfilled here.
29			logical.UpdateOperation: b.operationConfigUpdate,
30			logical.ReadOperation:   b.operationConfigRead,
31			logical.DeleteOperation: b.operationConfigDelete,
32		},
33		HelpSynopsis:    pathConfigRootHelpSyn,
34		HelpDescription: pathConfigRootHelpDesc,
35	}
36}
37
38func (b *backend) operationConfigUpdate(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) {
39	// Access keys and secrets are generated in pairs. You would never need
40	// to update one or the other alone, always both together.
41	accessKey := ""
42	if accessKeyIfc, ok := data.GetOk("access_key"); ok {
43		accessKey = accessKeyIfc.(string)
44	} else {
45		return nil, errors.New("access_key is required")
46	}
47	secretKey := ""
48	if secretKeyIfc, ok := data.GetOk("secret_key"); ok {
49		secretKey = secretKeyIfc.(string)
50	} else {
51		return nil, errors.New("secret_key is required")
52	}
53	entry, err := logical.StorageEntryJSON("config", credConfig{
54		AccessKey: accessKey,
55		SecretKey: secretKey,
56	})
57	if err != nil {
58		return nil, err
59	}
60	if err := req.Storage.Put(ctx, entry); err != nil {
61		return nil, err
62	}
63	return nil, nil
64}
65
66func (b *backend) operationConfigRead(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
67	creds, err := readCredentials(ctx, req.Storage)
68	if err != nil {
69		return nil, err
70	}
71	if creds == nil {
72		return nil, nil
73	}
74
75	// "secret_key" is intentionally not returned by this endpoint
76	return &logical.Response{
77		Data: map[string]interface{}{
78			"access_key": creds.AccessKey,
79		},
80	}, nil
81}
82
83func (b *backend) operationConfigDelete(ctx context.Context, req *logical.Request, _ *framework.FieldData) (*logical.Response, error) {
84	if err := req.Storage.Delete(ctx, "config"); err != nil {
85		return nil, err
86	}
87	return nil, nil
88}
89
90func readCredentials(ctx context.Context, storage logical.Storage) (*credConfig, error) {
91	entry, err := storage.Get(ctx, "config")
92	if err != nil {
93		return nil, err
94	}
95	if entry == nil {
96		return nil, nil
97	}
98	creds := &credConfig{}
99	if err := entry.DecodeJSON(creds); err != nil {
100		return nil, err
101	}
102	return creds, nil
103}
104
105type credConfig struct {
106	AccessKey string `json:"access_key"`
107	SecretKey string `json:"secret_key"`
108}
109
110const pathConfigRootHelpSyn = `
111Configure the access key and secret to use for RAM and STS calls.
112`
113
114const pathConfigRootHelpDesc = `
115Before doing anything, the AliCloud backend needs credentials that are able
116to manage RAM users, policies, and access keys, and that can call STS AssumeRole.
117This endpoint is used to configure those credentials.
118`
119