1package kv 2 3import ( 4 "context" 5 "fmt" 6 "strings" 7 "time" 8 9 "github.com/golang/protobuf/ptypes" 10 "github.com/hashicorp/vault/sdk/framework" 11 "github.com/hashicorp/vault/sdk/helper/locksutil" 12 "github.com/hashicorp/vault/sdk/logical" 13) 14 15// pathMetadata returns the path configuration for CRUD operations on the 16// metadata endpoint 17func pathMetadata(b *versionedKVBackend) *framework.Path { 18 return &framework.Path{ 19 Pattern: "metadata/" + framework.MatchAllRegex("path"), 20 Fields: map[string]*framework.FieldSchema{ 21 "path": { 22 Type: framework.TypeString, 23 Description: "Location of the secret.", 24 }, 25 "cas_required": { 26 Type: framework.TypeBool, 27 Description: ` 28If true the key will require the cas parameter to be set on all write requests. 29If false, the backend’s configuration will be used.`, 30 }, 31 "max_versions": { 32 Type: framework.TypeInt, 33 Description: ` 34The number of versions to keep. If not set, the backend’s configured max 35version is used.`, 36 }, 37 "delete_version_after": { 38 Type: framework.TypeDurationSecond, 39 Description: ` 40The length of time before a version is deleted. If not set, the backend's 41configured delete_version_after is used. Cannot be greater than the 42backend's delete_version_after. A zero duration clears the current setting. 43A negative duration will cause an error. 44`, 45 }, 46 }, 47 Callbacks: map[logical.Operation]framework.OperationFunc{ 48 logical.UpdateOperation: b.upgradeCheck(b.pathMetadataWrite()), 49 logical.CreateOperation: b.upgradeCheck(b.pathMetadataWrite()), 50 logical.ReadOperation: b.upgradeCheck(b.pathMetadataRead()), 51 logical.DeleteOperation: b.upgradeCheck(b.pathMetadataDelete()), 52 logical.ListOperation: b.upgradeCheck(b.pathMetadataList()), 53 }, 54 55 ExistenceCheck: b.metadataExistenceCheck(), 56 57 HelpSynopsis: confHelpSyn, 58 HelpDescription: confHelpDesc, 59 } 60} 61 62func (b *versionedKVBackend) metadataExistenceCheck() framework.ExistenceFunc { 63 return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (bool, error) { 64 key := data.Get("path").(string) 65 66 meta, err := b.getKeyMetadata(ctx, req.Storage, key) 67 if err != nil { 68 // If we are returning a readonly error it means we are attempting 69 // to write the policy for the first time. This means no data exists 70 // yet and we can safely return false here. 71 if strings.Contains(err.Error(), logical.ErrReadOnly.Error()) { 72 return false, nil 73 } 74 75 return false, err 76 } 77 78 return meta != nil, nil 79 } 80} 81 82func (b *versionedKVBackend) pathMetadataList() framework.OperationFunc { 83 return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 84 key := data.Get("path").(string) 85 86 // Get an encrypted key storage object 87 wrapper, err := b.getKeyEncryptor(ctx, req.Storage) 88 if err != nil { 89 return nil, err 90 } 91 92 es := wrapper.Wrap(req.Storage) 93 94 // Use encrypted key storage to list the keys 95 keys, err := es.List(ctx, key) 96 return logical.ListResponse(keys), err 97 } 98} 99 100func (b *versionedKVBackend) pathMetadataRead() framework.OperationFunc { 101 return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 102 key := data.Get("path").(string) 103 104 meta, err := b.getKeyMetadata(ctx, req.Storage, key) 105 if err != nil { 106 return nil, err 107 } 108 if meta == nil { 109 return nil, nil 110 } 111 112 versions := make(map[string]interface{}, len(meta.Versions)) 113 for i, v := range meta.Versions { 114 versions[fmt.Sprintf("%d", i)] = map[string]interface{}{ 115 "created_time": ptypesTimestampToString(v.CreatedTime), 116 "deletion_time": ptypesTimestampToString(v.DeletionTime), 117 "destroyed": v.Destroyed, 118 } 119 } 120 121 var deleteVersionAfter time.Duration 122 if meta.GetDeleteVersionAfter() != nil { 123 deleteVersionAfter, err = ptypes.Duration(meta.GetDeleteVersionAfter()) 124 if err != nil { 125 return nil, err 126 } 127 } 128 129 return &logical.Response{ 130 Data: map[string]interface{}{ 131 "versions": versions, 132 "current_version": meta.CurrentVersion, 133 "oldest_version": meta.OldestVersion, 134 "created_time": ptypesTimestampToString(meta.CreatedTime), 135 "updated_time": ptypesTimestampToString(meta.UpdatedTime), 136 "max_versions": meta.MaxVersions, 137 "cas_required": meta.CasRequired, 138 "delete_version_after": deleteVersionAfter.String(), 139 }, 140 }, nil 141 } 142} 143 144func (b *versionedKVBackend) pathMetadataWrite() framework.OperationFunc { 145 return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 146 key := data.Get("path").(string) 147 if key == "" { 148 return logical.ErrorResponse("missing path"), nil 149 } 150 151 maxRaw, mOk := data.GetOk("max_versions") 152 casRaw, cOk := data.GetOk("cas_required") 153 deleteVersionAfterRaw, dvaOk := data.GetOk("delete_version_after") 154 155 // Fast path validation 156 if !mOk && !cOk && !dvaOk { 157 return nil, nil 158 } 159 160 config, err := b.config(ctx, req.Storage) 161 if err != nil { 162 return nil, err 163 } 164 165 var resp *logical.Response 166 if cOk && config.CasRequired && !casRaw.(bool) { 167 resp = &logical.Response{} 168 resp.AddWarning("\"cas_required\" set to false, but is mandated by backend config. This value will be ignored.") 169 } 170 171 lock := locksutil.LockForKey(b.locks, key) 172 lock.Lock() 173 defer lock.Unlock() 174 175 meta, err := b.getKeyMetadata(ctx, req.Storage, key) 176 if err != nil { 177 return nil, err 178 } 179 if meta == nil { 180 now := ptypes.TimestampNow() 181 meta = &KeyMetadata{ 182 Key: key, 183 Versions: map[uint64]*VersionMetadata{}, 184 CreatedTime: now, 185 UpdatedTime: now, 186 } 187 } 188 189 if mOk { 190 meta.MaxVersions = uint32(maxRaw.(int)) 191 } 192 if cOk { 193 meta.CasRequired = casRaw.(bool) 194 } 195 if dvaOk { 196 meta.DeleteVersionAfter = ptypes.DurationProto(time.Duration(deleteVersionAfterRaw.(int)) * time.Second) 197 } 198 199 err = b.writeKeyMetadata(ctx, req.Storage, meta) 200 return resp, err 201 } 202} 203 204func (b *versionedKVBackend) pathMetadataDelete() framework.OperationFunc { 205 return func(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { 206 key := data.Get("path").(string) 207 208 lock := locksutil.LockForKey(b.locks, key) 209 lock.Lock() 210 defer lock.Unlock() 211 212 meta, err := b.getKeyMetadata(ctx, req.Storage, key) 213 if err != nil { 214 return nil, err 215 } 216 if meta == nil { 217 return nil, nil 218 } 219 220 // Delete each version. 221 for id, _ := range meta.Versions { 222 versionKey, err := b.getVersionKey(ctx, key, id, req.Storage) 223 if err != nil { 224 return nil, err 225 } 226 227 err = req.Storage.Delete(ctx, versionKey) 228 if err != nil { 229 return nil, err 230 } 231 } 232 233 // Get an encrypted key storage object 234 wrapper, err := b.getKeyEncryptor(ctx, req.Storage) 235 if err != nil { 236 return nil, err 237 } 238 239 es := wrapper.Wrap(req.Storage) 240 241 // Use encrypted key storage to delete the key 242 err = es.Delete(ctx, key) 243 return nil, err 244 } 245} 246 247const metadataHelpSyn = `Allows interaction with key metadata and settings in the KV store.` 248const metadataHelpDesc = ` 249This endpoint allows for reading, information about a key in the key-value 250store, writing key settings, and permanently deleting a key and all versions. 251` 252