1package vault 2 3import ( 4 "context" 5 "fmt" 6 "strings" 7 8 "github.com/golang/protobuf/ptypes" 9 "github.com/hashicorp/vault/helper/identity" 10 "github.com/hashicorp/vault/helper/namespace" 11 "github.com/hashicorp/vault/helper/storagepacker" 12 "github.com/hashicorp/vault/logical" 13 "github.com/hashicorp/vault/logical/framework" 14) 15 16// aliasPaths returns the API endpoints to operate on aliases. 17// Following are the paths supported: 18// entity-alias - To register/modify an alias 19// entity-alias/id - To read, modify, delete and list aliases based on their ID 20func aliasPaths(i *IdentityStore) []*framework.Path { 21 return []*framework.Path{ 22 { 23 Pattern: "entity-alias$", 24 Fields: map[string]*framework.FieldSchema{ 25 "id": { 26 Type: framework.TypeString, 27 Description: "ID of the entity alias. If set, updates the corresponding entity alias.", 28 }, 29 // entity_id is deprecated in favor of canonical_id 30 "entity_id": { 31 Type: framework.TypeString, 32 Description: `Entity ID to which this alias belongs. 33This field is deprecated, use canonical_id.`, 34 }, 35 "canonical_id": { 36 Type: framework.TypeString, 37 Description: "Entity ID to which this alias belongs", 38 }, 39 "mount_accessor": { 40 Type: framework.TypeString, 41 Description: "Mount accessor to which this alias belongs to; unused for a modify", 42 }, 43 "name": { 44 Type: framework.TypeString, 45 Description: "Name of the alias; unused for a modify", 46 }, 47 }, 48 Callbacks: map[logical.Operation]framework.OperationFunc{ 49 logical.UpdateOperation: i.handleAliasUpdateCommon(), 50 }, 51 52 HelpSynopsis: strings.TrimSpace(aliasHelp["alias"][0]), 53 HelpDescription: strings.TrimSpace(aliasHelp["alias"][1]), 54 }, 55 { 56 Pattern: "entity-alias/id/" + framework.GenericNameRegex("id"), 57 Fields: map[string]*framework.FieldSchema{ 58 "id": { 59 Type: framework.TypeString, 60 Description: "ID of the alias", 61 }, 62 // entity_id is deprecated 63 "entity_id": { 64 Type: framework.TypeString, 65 Description: `Entity ID to which this alias belongs to. 66This field is deprecated, use canonical_id.`, 67 }, 68 "canonical_id": { 69 Type: framework.TypeString, 70 Description: "Entity ID to which this alias should be tied to", 71 }, 72 "mount_accessor": { 73 Type: framework.TypeString, 74 Description: "(Unused)", 75 }, 76 "name": { 77 Type: framework.TypeString, 78 Description: "(Unused)", 79 }, 80 }, 81 Callbacks: map[logical.Operation]framework.OperationFunc{ 82 logical.UpdateOperation: i.handleAliasUpdateCommon(), 83 logical.ReadOperation: i.pathAliasIDRead(), 84 logical.DeleteOperation: i.pathAliasIDDelete(), 85 }, 86 87 HelpSynopsis: strings.TrimSpace(aliasHelp["alias-id"][0]), 88 HelpDescription: strings.TrimSpace(aliasHelp["alias-id"][1]), 89 }, 90 { 91 Pattern: "entity-alias/id/?$", 92 Callbacks: map[logical.Operation]framework.OperationFunc{ 93 logical.ListOperation: i.pathAliasIDList(), 94 }, 95 96 HelpSynopsis: strings.TrimSpace(aliasHelp["alias-id-list"][0]), 97 HelpDescription: strings.TrimSpace(aliasHelp["alias-id-list"][1]), 98 }, 99 } 100} 101 102// handleAliasUpdateCommon is used to update an alias 103func (i *IdentityStore) handleAliasUpdateCommon() framework.OperationFunc { 104 return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 105 var err error 106 var alias *identity.Alias 107 var entity *identity.Entity 108 var previousEntity *identity.Entity 109 110 i.lock.Lock() 111 defer i.lock.Unlock() 112 113 // Check for update or create 114 aliasID := d.Get("id").(string) 115 if aliasID != "" { 116 alias, err = i.MemDBAliasByID(aliasID, true, false) 117 if err != nil { 118 return nil, err 119 } 120 if alias == nil { 121 return logical.ErrorResponse("invalid alias id"), nil 122 } 123 } else { 124 alias = &identity.Alias{} 125 } 126 127 // Get entity id 128 canonicalID := d.Get("canonical_id").(string) 129 if canonicalID == "" { 130 // For backwards compatibility 131 canonicalID = d.Get("entity_id").(string) 132 } 133 134 // Get alias name 135 if aliasName := d.Get("name").(string); aliasName == "" { 136 if alias.Name == "" { 137 return logical.ErrorResponse("missing alias name"), nil 138 } 139 } else { 140 alias.Name = aliasName 141 } 142 143 // Get mount accessor 144 if mountAccessor := d.Get("mount_accessor").(string); mountAccessor == "" { 145 if alias.MountAccessor == "" { 146 return logical.ErrorResponse("missing mount_accessor"), nil 147 } 148 } else { 149 alias.MountAccessor = mountAccessor 150 } 151 152 mountValidationResp := i.core.router.validateMountByAccessor(alias.MountAccessor) 153 if mountValidationResp == nil { 154 return logical.ErrorResponse(fmt.Sprintf("invalid mount accessor %q", alias.MountAccessor)), nil 155 } 156 if mountValidationResp.MountLocal { 157 return logical.ErrorResponse(fmt.Sprintf("mount_accessor %q is of a local mount", alias.MountAccessor)), nil 158 } 159 160 // Verify that the combination of alias name and mount is not 161 // already tied to a different alias 162 aliasByFactors, err := i.MemDBAliasByFactors(mountValidationResp.MountAccessor, alias.Name, false, false) 163 if err != nil { 164 return nil, err 165 } 166 if aliasByFactors != nil { 167 // If it's a create we won't have an alias ID so this will correctly 168 // bail. If it's an update alias will be the same as aliasbyfactors so 169 // we don't need to transfer any info over 170 if aliasByFactors.ID != alias.ID { 171 return logical.ErrorResponse("combination of mount and alias name is already in use"), nil 172 } 173 174 // Fetch the entity to which the alias is tied. We don't need to append 175 // here, so the only further checking is whether the canonical ID is 176 // different 177 entity, err = i.MemDBEntityByAliasID(alias.ID, true) 178 if err != nil { 179 return nil, err 180 } 181 if entity == nil { 182 return nil, fmt.Errorf("existing alias is not associated with an entity") 183 } 184 } else if alias.ID != "" { 185 // This is an update, not a create; if we have an associated entity 186 // already, load it 187 entity, err = i.MemDBEntityByAliasID(alias.ID, true) 188 if err != nil { 189 return nil, err 190 } 191 } 192 193 resp := &logical.Response{} 194 195 // If we found an existing alias we won't hit this condition because 196 // canonicalID being empty will result in nil being returned in the block 197 // above, so in this case we know that creating a new entity is the right 198 // thing. 199 if canonicalID == "" { 200 entity = &identity.Entity{ 201 Aliases: []*identity.Alias{ 202 alias, 203 }, 204 } 205 } else { 206 // If we can look up by the given canonical ID, see if this is a 207 // transfer; otherwise if we found no previous entity but we find one 208 // here, use it. 209 canonicalEntity, err := i.MemDBEntityByID(canonicalID, true) 210 if err != nil { 211 return nil, err 212 } 213 if canonicalEntity == nil { 214 return logical.ErrorResponse("invalid canonical ID"), nil 215 } 216 if entity == nil { 217 // If entity is nil, we didn't find a previous alias from factors, 218 // so append to this entity 219 entity = canonicalEntity 220 entity.Aliases = append(entity.Aliases, alias) 221 } else if entity.ID != canonicalEntity.ID { 222 // In this case we found an entity from alias factors or given 223 // alias ID but it's not the same, so it's a migration 224 previousEntity = entity 225 entity = canonicalEntity 226 227 for aliasIndex, item := range previousEntity.Aliases { 228 if item.ID == alias.ID { 229 previousEntity.Aliases = append(previousEntity.Aliases[:aliasIndex], previousEntity.Aliases[aliasIndex+1:]...) 230 break 231 } 232 } 233 234 entity.Aliases = append(entity.Aliases, alias) 235 resp.AddWarning(fmt.Sprintf("alias is being transferred from entity %q to %q", previousEntity.ID, entity.ID)) 236 } 237 } 238 239 // ID creation and other validations; This is more useful for new entities 240 // and may not perform anything for the existing entities. Placing the 241 // check here to make the flow common for both new and existing entities. 242 err = i.sanitizeEntity(ctx, entity) 243 if err != nil { 244 return nil, err 245 } 246 247 // Explicitly set to empty as in the past we incorrectly saved it 248 alias.MountPath = "" 249 alias.MountType = "" 250 251 // Set the canonical ID in the alias index. This should be done after 252 // sanitizing entity. 253 alias.CanonicalID = entity.ID 254 255 // ID creation and other validations 256 err = i.sanitizeAlias(ctx, alias) 257 if err != nil { 258 return nil, err 259 } 260 261 for index, item := range entity.Aliases { 262 if item.ID == alias.ID { 263 entity.Aliases[index] = alias 264 } 265 } 266 267 // Index entity and its aliases in MemDB and persist entity along with 268 // aliases in storage. If the alias is being transferred over from 269 // one entity to another, previous entity needs to get refreshed in MemDB 270 // and persisted in storage as well. 271 if err := i.upsertEntity(ctx, entity, previousEntity, true); err != nil { 272 return nil, err 273 } 274 275 // Return ID of both alias and entity 276 resp.Data = map[string]interface{}{ 277 "id": alias.ID, 278 "canonical_id": entity.ID, 279 } 280 281 return resp, nil 282 } 283} 284 285// pathAliasIDRead returns the properties of an alias for a given 286// alias ID 287func (i *IdentityStore) pathAliasIDRead() framework.OperationFunc { 288 return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 289 aliasID := d.Get("id").(string) 290 if aliasID == "" { 291 return logical.ErrorResponse("missing alias id"), nil 292 } 293 294 alias, err := i.MemDBAliasByID(aliasID, false, false) 295 if err != nil { 296 return nil, err 297 } 298 299 return i.handleAliasReadCommon(ctx, alias) 300 } 301} 302 303func (i *IdentityStore) handleAliasReadCommon(ctx context.Context, alias *identity.Alias) (*logical.Response, error) { 304 if alias == nil { 305 return nil, nil 306 } 307 308 ns, err := namespace.FromContext(ctx) 309 if err != nil { 310 return nil, err 311 } 312 if ns.ID != alias.NamespaceID { 313 return nil, nil 314 } 315 316 respData := map[string]interface{}{} 317 respData["id"] = alias.ID 318 respData["canonical_id"] = alias.CanonicalID 319 respData["mount_accessor"] = alias.MountAccessor 320 respData["metadata"] = alias.Metadata 321 respData["name"] = alias.Name 322 respData["merged_from_canonical_ids"] = alias.MergedFromCanonicalIDs 323 324 if mountValidationResp := i.core.router.validateMountByAccessor(alias.MountAccessor); mountValidationResp != nil { 325 respData["mount_path"] = mountValidationResp.MountPath 326 respData["mount_type"] = mountValidationResp.MountType 327 } 328 329 // Convert protobuf timestamp into RFC3339 format 330 respData["creation_time"] = ptypes.TimestampString(alias.CreationTime) 331 respData["last_update_time"] = ptypes.TimestampString(alias.LastUpdateTime) 332 333 return &logical.Response{ 334 Data: respData, 335 }, nil 336} 337 338// pathAliasIDDelete deletes the alias for a given alias ID 339func (i *IdentityStore) pathAliasIDDelete() framework.OperationFunc { 340 return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 341 aliasID := d.Get("id").(string) 342 if aliasID == "" { 343 return logical.ErrorResponse("missing alias ID"), nil 344 } 345 346 i.lock.Lock() 347 defer i.lock.Unlock() 348 349 // Create a MemDB transaction to delete entity 350 txn := i.db.Txn(true) 351 defer txn.Abort() 352 353 // Fetch the alias 354 alias, err := i.MemDBAliasByIDInTxn(txn, aliasID, false, false) 355 if err != nil { 356 return nil, err 357 } 358 359 // If there is no alias for the ID, do nothing 360 if alias == nil { 361 return nil, nil 362 } 363 364 ns, err := namespace.FromContext(ctx) 365 if err != nil { 366 return nil, err 367 } 368 if ns.ID != alias.NamespaceID { 369 return nil, logical.ErrUnsupportedPath 370 } 371 372 // Fetch the associated entity 373 entity, err := i.MemDBEntityByAliasIDInTxn(txn, alias.ID, true) 374 if err != nil { 375 return nil, err 376 } 377 378 // If there is no entity tied to a valid alias, something is wrong 379 if entity == nil { 380 return nil, fmt.Errorf("alias not associated to an entity") 381 } 382 383 aliases := []*identity.Alias{ 384 alias, 385 } 386 387 // Delete alias from the entity object 388 err = i.deleteAliasesInEntityInTxn(txn, entity, aliases) 389 if err != nil { 390 return nil, err 391 } 392 393 // Update the entity index in the entities table 394 err = i.MemDBUpsertEntityInTxn(txn, entity) 395 if err != nil { 396 return nil, err 397 } 398 399 // Persist the entity object 400 entityAsAny, err := ptypes.MarshalAny(entity) 401 if err != nil { 402 return nil, err 403 } 404 item := &storagepacker.Item{ 405 ID: entity.ID, 406 Message: entityAsAny, 407 } 408 409 err = i.entityPacker.PutItem(item) 410 if err != nil { 411 return nil, err 412 } 413 414 // Committing the transaction *after* successfully updating entity in 415 // storage 416 txn.Commit() 417 418 return nil, nil 419 } 420} 421 422// pathAliasIDList lists the IDs of all the valid aliases in the identity 423// store 424func (i *IdentityStore) pathAliasIDList() framework.OperationFunc { 425 return func(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) { 426 return i.handleAliasListCommon(ctx, false) 427 } 428} 429 430var aliasHelp = map[string][2]string{ 431 "alias": { 432 "Create a new alias.", 433 "", 434 }, 435 "alias-id": { 436 "Update, read or delete an alias ID.", 437 "", 438 }, 439 "alias-id-list": { 440 "List all the alias IDs.", 441 "", 442 }, 443} 444