This repository has been archived by the owner on Feb 16, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 10
/
secret.go
223 lines (187 loc) · 6.96 KB
/
secret.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
package api
import (
"net/http"
"strings"
"time"
"bitbucket.org/zombiezen/cardcpx/natsort"
"github.com/secrethub/secrethub-go/internals/api/uuid"
"github.com/secrethub/secrethub-go/internals/crypto"
"github.com/secrethub/secrethub-go/internals/errio"
)
// Errors
var (
ErrInvalidSecretName = errAPI.Code("invalid_secret_name").StatusError(
"secret names must be between 1 and 32 characters and "+
"may only contain letters, numbers, dashes (-), underscores (_), and dots (.)",
http.StatusBadRequest,
)
ErrInvalidSecretVersion = errAPI.Code("invalid_secret_version").StatusError(
"secret version can only be positive numbers or latest",
http.StatusBadRequest,
)
ErrInvalidNodeID = errAPI.Code("invalid_node_id").StatusError("the node id is invalid", http.StatusBadRequest)
ErrInvalidEncryptedSecretName = errAPI.Code("invalid_encrypted_secret_name").StatusError("invalid ciphertext for encrypted secret name", http.StatusBadRequest)
ErrInvalidSecretBlindName = errAPI.Code("invalid_secret_blind_name").StatusError("secret blind name is invalid", http.StatusBadRequest)
ErrInvalidSecretBlob = errAPI.Code("invalid_secret_blob").StatusError("secret blob is invalid", http.StatusBadRequest)
ErrNoSecretMembers = errAPI.Code("no_secret_members").StatusError("no secret members added to write request", http.StatusBadRequest)
ErrInvalidSecretKeyID = errAPI.Code("invalid_secret_key_id").StatusError("secret_key_id is invalid", http.StatusBadRequest)
ErrNotEncryptedForAccounts = errAPI.Code("not_encrypted_for_accounts").StatusError("missing data encrypted for accounts. This can occur when access rules are simultaneously created with resources controlled by the access rule. You may try again.", http.StatusConflict)
ErrNotUniquelyEncryptedForAccounts = errAPI.Code("not_uniquely_encrypted_for_accounts").StatusError("not uniquely encrypted for accounts", http.StatusBadRequest)
ErrCannotDeleteLastSecretVersion = errAPI.Code("cannot_delete_last_version").StatusError("Cannot delete the last version of a secret", http.StatusForbidden)
)
// EncryptedSecret represents an encrypted Secret
// It does not contain the encrypted data. Only the encrypted name.
type EncryptedSecret struct {
SecretID uuid.UUID `json:"secret_id"`
DirID uuid.UUID `json:"dir_id"`
RepoID uuid.UUID `json:"repo_id"`
EncryptedName crypto.CiphertextRSA `json:"encrypted_name"`
BlindName string `json:"blind_name"`
VersionCount int `json:"version_count"`
LatestVersion int `json:"latest_version"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
// Decrypt decrypts an EncryptedSecret into a Secret.
func (es *EncryptedSecret) Decrypt(accountKey *crypto.RSAPrivateKey) (*Secret, error) {
name, err := accountKey.Unwrap(es.EncryptedName)
if err != nil {
return nil, errio.Error(err)
}
return &Secret{
SecretID: es.SecretID,
DirID: es.DirID,
RepoID: es.RepoID,
BlindName: es.BlindName,
Name: string(name),
VersionCount: es.VersionCount,
LatestVersion: es.LatestVersion,
Status: es.Status,
CreatedAt: es.CreatedAt,
}, nil
}
// Secret represents a decrypted secret in SecretHub.
type Secret struct {
SecretID uuid.UUID `json:"secret_id"`
DirID uuid.UUID `json:"dir_id"`
RepoID uuid.UUID `json:"repo_id"`
Name string `json:"name"`
BlindName string `json:"blind_name"`
VersionCount int `json:"version_count"`
LatestVersion int `json:"latest_version"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
}
// HasName returns true when the secret version has the exact name.
func (s *Secret) HasName(name string) bool {
return strings.EqualFold(s.Name, name)
}
// CreateSecretRequest contains the request fields for creating a new secret,
// together with its first version, encrypted for accounts that need access.
type CreateSecretRequest struct {
BlindName string `json:"blind_name"`
EncryptedData crypto.CiphertextAES `json:"encrypted_data"`
EncryptedNames []EncryptedNameRequest `json:"encrypted_names"`
EncryptedKeys []EncryptedKeyRequest `json:"encrypted_keys"`
}
// Validate validates the request fields.
func (csr *CreateSecretRequest) Validate() error {
err := ValidateBlindName(csr.BlindName)
if err != nil {
return ErrInvalidSecretBlindName
}
if len(csr.EncryptedNames) < 1 {
return ErrNotEncryptedForAccounts
}
// Used to check if every account has an EncryptedName and an EncryptedKey and is Unique
accounts := make(map[uuid.UUID]int)
unique := make(map[uuid.UUID]int)
for _, encryptedName := range csr.EncryptedNames {
err = encryptedName.Validate()
if err != nil {
return err
}
accounts[encryptedName.AccountID]++
unique[encryptedName.AccountID]++
}
for _, count := range unique {
if count != 1 {
return ErrNotUniquelyEncryptedForAccounts
}
}
if len(csr.EncryptedKeys) < 1 {
return ErrNotEncryptedForAccounts
}
unique = make(map[uuid.UUID]int)
for _, encryptedKey := range csr.EncryptedKeys {
err = encryptedKey.Validate()
if err != nil {
return err
}
accounts[encryptedKey.AccountID]--
unique[encryptedKey.AccountID]++
}
for _, count := range unique {
if count != 1 {
return ErrNotUniquelyEncryptedForAccounts
}
}
for _, count := range accounts {
if count != 0 {
return ErrNotEncryptedForAccounts
}
}
return nil
}
// SortSecretByName makes a list of Secret sortable.
type SortSecretByName []*Secret
func (s SortSecretByName) Len() int {
return len(s)
}
func (s SortSecretByName) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s SortSecretByName) Less(i, j int) bool {
return natsort.Less(s[i].Name, s[j].Name)
}
// SecretAccessRequest contains the request fields to grant an account access to a secret.
type SecretAccessRequest struct {
Name EncryptedNameForNodeRequest `json:"name_member"`
Keys []SecretKeyMemberRequest `json:"keys"`
}
// Validate validates the request fields.
func (r *SecretAccessRequest) Validate() error {
err := r.Name.Validate()
if err != nil {
return errio.Error(err)
}
for _, key := range r.Keys {
err := key.Validate()
if err != nil {
return err
}
}
accountID := r.Name.AccountID
for _, key := range r.Keys {
if !uuid.Equal(key.AccountID, accountID) {
return ErrInvalidAccountID
}
}
return nil
}
// SecretKeyMemberRequest contains the request fields to grant access to a secret key.
type SecretKeyMemberRequest struct {
AccountID uuid.UUID `json:"account_id"`
SecretKeyID uuid.UUID `json:"secret_key_id"`
EncryptedKey crypto.CiphertextRSA `json:"encrypted_key"`
}
// Validate validates the request fields.
func (skmr *SecretKeyMemberRequest) Validate() error {
if skmr.AccountID.IsZero() {
return ErrInvalidAccountID
}
if skmr.SecretKeyID.IsZero() {
return ErrInvalidKeyID
}
return nil
}