Enforced provider and projectID in db call#6303
Conversation
evankanderson
left a comment
There was a problem hiding this comment.
It looks like you may also have a merge conflict, but this looks good other than a few small comments -- I'd like to avoid adding remote (serial) lookups in loop contexts -- I didn't look in enough detail to know for sure here if that was happening..
8e99b54 to
5c1b61d
Compare
evankanderson
left a comment
There was a problem hiding this comment.
I think you just force-pushed to the wrong branch. :-/
5c1b61d to
6933e2f
Compare
Unfortunatly! now solved it |
1063a3e to
d7067a1
Compare
|
@evankanderson i've verified the tests pass locally ,but fails ci |
evankanderson
left a comment
There was a problem hiding this comment.
One small concern about duplicating getAuthorizedProjects logic twice, and some code that I think is functionally dead and should probably be removed.
| } | ||
|
|
||
| ewp, err := ps.getEntityWithProperties(ctx, *ent, opts) | ||
| projects, err := ps.getAuthorizedProjects(ctx, ent.ProjectID) |
There was a problem hiding this comment.
Verified that this is only called by getEntityInner, which is only called by the strategies in handlers/strategies/entity for handling updates of resources which were auto-created due to parent resources.
There was a problem hiding this comment.
(In response to the question whether this was called in a loop)
There was a problem hiding this comment.
I had actually removed getAuthorizedProjects in my latest commit
676c0cb to
32fc7d8
Compare
d655ebe to
2c90a2a
Compare
|
(This has a merge conflict again, sorry!) |
2c90a2a to
6699a36
Compare
resolved it now |
Signed-off-by: DharunMR <maddharun56@gmail.com>
6699a36 to
4a67fc4
Compare
evankanderson
left a comment
There was a problem hiding this comment.
This PR is pretty "big" (hard for me to fit in my head) -- is there a good way to break it into smaller chunks?
| ) AS exists; | ||
|
|
||
| -- GetEntitiesByProvider retrieves all entities of a given provider. | ||
| -- GetEntitiesByProvider retrieves all entities of a given provider scoped by project hierarchy. |
There was a problem hiding this comment.
Providers are already scoped to a project (and its children, though we don't have all the hierarchy decisions ironed out yet).
| -- name: FlushCache :one | ||
| DELETE FROM flush_cache |
There was a problem hiding this comment.
I don't believe there's a user-controlled way to affect the entity cache, so it's not clear we need to enforce project checks here (and it may add cost).
|
|
||
| // Fetch the entity with properties | ||
| ewp, err := s.props.EntityWithPropertiesByID(ctx, entities[0].ID, nil) | ||
| ewp, err := s.props.EntityWithPropertiesByID(ctx, entities[0].ID, projectID, provider.ID, nil) |
There was a problem hiding this comment.
FWIW, it looks like we've already constrained the entity to the current project and provider on line 78-85.
|
|
||
| // Fetch artifact entity | ||
| ewp, err := s.props.EntityWithPropertiesByID(ctx, parsedArtifactID, nil) | ||
| ewp, err := s.props.EntityWithPropertiesByID(ctx, parsedArtifactID, projectID, provider.ID, nil) |
There was a problem hiding this comment.
It looks like we're adding some database queries to replace the lines 155-158. I'm not sure how I feel about that.
|
|
||
| // Call service to get entity | ||
| entity, err := s.entityService.GetEntityByID(ctx, entityID, projectID) | ||
| entity, err := s.entityService.GetEntityByID(ctx, entityID, projectID, provider.ID) |
There was a problem hiding this comment.
It looks like we only check the provider here because of the changes to the DB queries? We're mostly concerned about the ability of a user in one project to "peek" into another project that they don't have access to -- we assume that users in a given project have equivalent permissions on all the entities in that project (though I supposed this will also now return an error if you try to look up an entity from provider A, but pass provider B's name in the context).
| } | ||
|
|
||
| // Telemetry logging | ||
| logger.BusinessRecord(ctx).Provider = providerName // Added provider telemetry! |
| var topic string | ||
|
|
||
| msg, err = getRepositoryReconciliationMessage(ctx, s.store, entity.GetId(), entityCtx) | ||
| msg, err = getRepositoryReconciliationMessage(ctx, s.store, entity.GetId(), entityCtx, dbProvider.ID) |
There was a problem hiding this comment.
It looks like we're again adding some database lookups to protect against the same limit as 101-105 in the old code. I think this might be an improvement on making accidental errors, but the cost feels higher than I'd like.
Is there a way that we can split the methods into "safe to be called with user values" and "only to be called after entity validation"? I suspect we don't have a good way right now because we tend to vend our internal database IDs directly through the API to end users. 🤦
| if len(repoEnts) == 0 { | ||
| return nil, nil | ||
| } | ||
|
|
||
| entityIDs := make([]uuid.UUID, len(repoEnts)) | ||
| for i, ent := range repoEnts { | ||
| entityIDs[i] = ent.ID | ||
| } | ||
|
|
||
| dbProps, err := qtx.GetPropertiesForEntities(ctx, db.GetPropertiesForEntitiesParams{ | ||
| EntityIds: entityIDs, | ||
| Projects: []uuid.UUID{projectID}, | ||
| ProviderID: providerID, | ||
| }) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("error fetching bulk properties: %w", err) | ||
| } | ||
|
|
||
| propsMap := make(map[uuid.UUID][]db.Property) | ||
| for _, p := range dbProps { | ||
| propsMap[p.EntityID] = append(propsMap[p.EntityID], p) | ||
| } | ||
|
|
||
| ents = make([]*models.EntityWithProperties, 0, len(repoEnts)) | ||
| for _, ent := range repoEnts { | ||
| ewp, err := r.propSvc.EntityWithPropertiesByID(ctx, ent.ID, | ||
| service.CallBuilder().WithStoreOrTransaction(qtx)) | ||
| modelProps, err := models.DbPropsToModel(propsMap[ent.ID]) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("error fetching properties for repository: %w", err) | ||
| } | ||
|
|
||
| if err := r.propSvc.RetrieveAllPropertiesForEntity(ctx, ewp, r.providerManager, | ||
| service.ReadBuilder().WithStoreOrTransaction(qtx).TolerateStaleData()); err != nil { | ||
| return nil, fmt.Errorf("error fetching properties for repository: %w", err) | ||
| return nil, fmt.Errorf("failed to convert properties for %s: %w", ent.ID, err) | ||
| } | ||
|
|
||
| ents = append(ents, ewp) | ||
| ents = append(ents, models.NewEntityWithProperties(ent, modelProps)) | ||
| } |
There was a problem hiding this comment.
This looks like a performance refactor (batching property lookups). That seems handy, but separate from other parts of this PR?
|
This PR needs additional information before we can continue. It is now marked as stale because it has been open for 30 days with no activity. Please provide the necessary details to continue or it will be closed in 30 days. |
Summary
This PR implements strict security boundaries for entity retrieval and property management by enforcing the Security Triple (EntityID, ProjectID, ProviderID) across the database and service layers.
Previously, certain database calls and service methods relied on incomplete identifiers, which theoretically could allow for cross-provider or cross-project data access if an ID was known. This refactor ensures that every entity-related operation is strictly bounded by both the project and the provider that owns the entity.
Key Changes:
Database Layer (SQL)
Enforced Constraints: Updated GetEntityByID, GetEntityByName, and DeleteEntity queries in entities.sql to require project_id and provider_id. Property Security: Updated property-related queries (GetProperty, GetAllPropertiesForEntity, DeleteProperty) to utilize JOIN or USING clauses against entity_instances, ensuring property operations cannot bypass entity ownership boundaries. Optimized Flush Cache: Updated ListFlushCache to join with entity_instances, allowing background workers to retrieve the provider_id directly from the database without extra Go-side lookups.
Service Layer (Go)
Property Service: Refactored EntityWithPropertiesByID and related methods to accept and validate the full Security Triple. Repository Service: Updated GetRepositoryById, GetRepositoryByName, and Delete methods to pass mandatory security parameters. EEA & Background Workers: Updated the Event Aggregator and reconcilers to use the ProviderID now provided by the enhanced flush cache queries.
API & Middleware
Controlplane: Updated API handlers (Artifacts, Repositories, Profiles) to translate provider names from the request context into UUIDs via providerStore.GetByName to satisfy the new database constraints.
Fixes Enforce provider and project ID in
GetEntityByIDdb call #4417Fixes Enforce provider and project ID in EntityWithProperties in the properties service #4419
Testing
Test Suite
Ran go test ./... (100% passing) to verify that the embedded database correctly respects the new project_id slices and that all handlers propagate context properly.Manual Verification