forked from hashicorp/vault
-
Notifications
You must be signed in to change notification settings - Fork 0
/
alicloudkms.go
247 lines (209 loc) · 6.82 KB
/
alicloudkms.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
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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
package alicloudkms
import (
"context"
"encoding/base64"
"errors"
"fmt"
"os"
"sync/atomic"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/providers"
"github.com/aliyun/alibaba-cloud-sdk-go/services/kms"
"github.com/hashicorp/errwrap"
log "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/physical"
"github.com/hashicorp/vault/vault/seal"
)
const (
EnvAliCloudKMSSealKeyID = "VAULT_ALICLOUDKMS_SEAL_KEY_ID"
)
type AliCloudKMSSeal struct {
logger log.Logger
client kmsClient
domain string
keyID string
currentKeyID *atomic.Value
}
// Ensure that we are implementing AutoSealAccess
var _ seal.Access = (*AliCloudKMSSeal)(nil)
func NewSeal(logger log.Logger) *AliCloudKMSSeal {
k := &AliCloudKMSSeal{
logger: logger,
currentKeyID: new(atomic.Value),
}
k.currentKeyID.Store("")
return k
}
// SetConfig sets the fields on the AliCloudKMSSeal object based on
// values from the config parameter.
//
// Order of precedence AliCloud values:
// * Environment variable
// * Value from Vault configuration file
// * Instance metadata role (access key and secret key)
func (k *AliCloudKMSSeal) SetConfig(config map[string]string) (map[string]string, error) {
if config == nil {
config = map[string]string{}
}
// Check and set KeyID
switch {
case os.Getenv(EnvAliCloudKMSSealKeyID) != "":
k.keyID = os.Getenv(EnvAliCloudKMSSealKeyID)
case config["kms_key_id"] != "":
k.keyID = config["kms_key_id"]
default:
return nil, fmt.Errorf("'kms_key_id' not found for AliCloud KMS sealconfiguration")
}
region := ""
if k.client == nil {
// Check and set region.
region = os.Getenv("ALICLOUD_REGION")
if region == "" {
ok := false
if region, ok = config["region"]; !ok {
region = "cn-beijing"
}
}
// A domain isn't required, but it can be used to override the endpoint
// returned by the region. An example value for a domain would be:
// "kms.us-east-1.aliyuncs.com".
k.domain = os.Getenv("ALICLOUD_DOMAIN")
if k.domain == "" {
k.domain = config["domain"]
}
// Build the optional, configuration-based piece of the credential chain.
credConfig := &providers.Configuration{}
if accessKey, ok := config["access_key"]; ok {
credConfig.AccessKeyID = accessKey
}
if secretKey, ok := config["secret_key"]; ok {
credConfig.AccessKeySecret = secretKey
} else {
if accessSecret, ok := config["access_secret"]; ok {
credConfig.AccessKeySecret = accessSecret
}
}
credentialChain := []providers.Provider{
providers.NewEnvCredentialProvider(),
providers.NewConfigurationCredentialProvider(credConfig),
providers.NewInstanceMetadataProvider(),
}
credProvider := providers.NewChainProvider(credentialChain)
creds, err := credProvider.Retrieve()
if err != nil {
return nil, err
}
clientConfig := sdk.NewConfig()
clientConfig.Scheme = "https"
client, err := kms.NewClientWithOptions(region, clientConfig, creds)
if err != nil {
return nil, err
}
k.client = client
}
// Test the client connection using provided key ID
input := kms.CreateDescribeKeyRequest()
input.KeyId = k.keyID
input.Domain = k.domain
keyInfo, err := k.client.DescribeKey(input)
if err != nil {
return nil, errwrap.Wrapf("error fetching AliCloud KMS sealkey information: {{err}}", err)
}
if keyInfo == nil || keyInfo.KeyMetadata.KeyId == "" {
return nil, errors.New("no key information returned")
}
// Store the current key id. If using a key alias, this will point to the actual
// unique key that that was used for this encrypt operation.
k.currentKeyID.Store(keyInfo.KeyMetadata.KeyId)
// Map that holds non-sensitive configuration info
sealInfo := make(map[string]string)
sealInfo["region"] = region
sealInfo["kms_key_id"] = k.keyID
if k.domain != "" {
sealInfo["domain"] = k.domain
}
return sealInfo, nil
}
// Init is called during core.Initialize. No-op at the moment.
func (k *AliCloudKMSSeal) Init(_ context.Context) error {
return nil
}
// Finalize is called during shutdown. This is a no-op since
// AliCloudKMSSeal doesn't require any cleanup.
func (k *AliCloudKMSSeal) Finalize(_ context.Context) error {
return nil
}
// SealType returns the seal type for this particular seal implementation.
func (k *AliCloudKMSSeal) SealType() string {
return seal.AliCloudKMS
}
// KeyID returns the last known key id.
func (k *AliCloudKMSSeal) KeyID() string {
return k.currentKeyID.Load().(string)
}
// Encrypt is used to encrypt the master key using the the AliCloud CMK.
// This returns the ciphertext, and/or any errors from this
// call. This should be called after the KMS client has been instantiated.
func (k *AliCloudKMSSeal) Encrypt(_ context.Context, plaintext []byte) (*physical.EncryptedBlobInfo, error) {
if plaintext == nil {
return nil, fmt.Errorf("given plaintext for encryption is nil")
}
env, err := seal.NewEnvelope().Encrypt(plaintext)
if err != nil {
return nil, errwrap.Wrapf("error wrapping data: {{err}}", err)
}
input := kms.CreateEncryptRequest()
input.KeyId = k.keyID
input.Plaintext = base64.StdEncoding.EncodeToString(env.Key)
input.Domain = k.domain
output, err := k.client.Encrypt(input)
if err != nil {
return nil, errwrap.Wrapf("error encrypting data: {{err}}", err)
}
// Store the current key id.
keyID := output.KeyId
k.currentKeyID.Store(keyID)
ret := &physical.EncryptedBlobInfo{
Ciphertext: env.Ciphertext,
IV: env.IV,
KeyInfo: &physical.SealKeyInfo{
KeyID: keyID,
WrappedKey: []byte(output.CiphertextBlob),
},
}
return ret, nil
}
// Decrypt is used to decrypt the ciphertext. This should be called after Init.
func (k *AliCloudKMSSeal) Decrypt(_ context.Context, in *physical.EncryptedBlobInfo) ([]byte, error) {
if in == nil {
return nil, fmt.Errorf("given input for decryption is nil")
}
// KeyID is not passed to this call because AliCloud handles this
// internally based on the metadata stored with the encrypted data
input := kms.CreateDecryptRequest()
input.CiphertextBlob = string(in.KeyInfo.WrappedKey)
input.Domain = k.domain
output, err := k.client.Decrypt(input)
if err != nil {
return nil, errwrap.Wrapf("error decrypting data encryption key: {{err}}", err)
}
keyBytes, err := base64.StdEncoding.DecodeString(output.Plaintext)
if err != nil {
return nil, err
}
envInfo := &seal.EnvelopeInfo{
Key: keyBytes,
IV: in.IV,
Ciphertext: in.Ciphertext,
}
plaintext, err := seal.NewEnvelope().Decrypt(envInfo)
if err != nil {
return nil, errwrap.Wrapf("error decrypting data: {{err}}", err)
}
return plaintext, nil
}
type kmsClient interface {
Decrypt(request *kms.DecryptRequest) (response *kms.DecryptResponse, err error)
DescribeKey(request *kms.DescribeKeyRequest) (response *kms.DescribeKeyResponse, err error)
Encrypt(request *kms.EncryptRequest) (response *kms.EncryptResponse, err error)
}