Skip to content
Open
28 changes: 20 additions & 8 deletions service/authorization/authorization.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,16 @@ func (as *AuthorizationService) getDecisions(ctx context.Context, dr *authorizat
continue
}

// Determine resource attribute ID for audit
resourceAttrID := ra.GetResourceAttributesId()
if resourceAttrID == "" && len(ra.GetAttributeValueFqns()) > 0 {
resourceAttrID = ra.GetAttributeValueFqns()[0]
}

// Start deferred audit event
auditEvent := as.logger.Audit.Decision(ctx, ec.GetId(), resourceAttrID, fqns)
defer auditEvent.Log(ctx)

//
// TODO: we should already have the subject mappings here and be able to just use OPA to trim down the known data attr values to the ones matched up with the entities
//
Expand Down Expand Up @@ -656,6 +666,10 @@ func (as *AuthorizationService) getDecisions(ctx context.Context, dr *authorizat
envEntityAttrValues[entityID] = e.GetAttributeValueFqns()
}
}

// Update audit with entitlements as they are computed
auditEvent.UpdateEntitlements(auditECEntitlements)

// call access-pdp
accessPDP := access.NewPdp(as.logger)
decisions, err := accessPDP.DetermineAccess(
Expand Down Expand Up @@ -689,6 +703,9 @@ func (as *AuthorizationService) getDecisions(ctx context.Context, dr *authorizat
Entitlements: entityEntitlementFqns,
})
}

// Update audit with entity decisions
auditEvent.UpdateEntityDecisions(auditEntityDecisions)
}

decisionResp := &authorization.DecisionResponse{
Expand All @@ -706,18 +723,13 @@ func (as *AuthorizationService) getDecisions(ctx context.Context, dr *authorizat
decisionResp.ResourceAttributesId = ra.GetAttributeValueFqns()[0]
}

// Log audit with final decision
auditDecision := audit.GetDecisionResultDeny
if decision == authorization.DecisionResponse_DECISION_PERMIT {
auditDecision = audit.GetDecisionResultPermit
}
as.logger.Audit.GetDecision(ctx, audit.GetDecisionEventParams{
Decision: auditDecision,
EntityChainEntitlements: auditECEntitlements,
EntityChainID: decisionResp.GetEntityChainId(),
EntityDecisions: auditEntityDecisions,
FQNs: fqns,
ResourceAttributeID: decisionResp.GetResourceAttributesId(),
})
auditEvent.Success(ctx, auditDecision)

response[responseIdx] = decisionResp
}
}
Expand Down
53 changes: 31 additions & 22 deletions service/internal/access/v2/just_in_time_pdp.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ func (p *JustInTimePDP) GetDecision(

case *authzV2.EntityIdentifier_RegisteredResourceValueFqn:
regResValueFQN := strings.ToLower(entityIdentifier.GetRegisteredResourceValueFqn())

auditEvent := p.logger.Audit.DecisionV2(ctx, regResValueFQN, action.GetName())
defer auditEvent.Log(ctx)

// Registered resources do not have entity representations, so only one decision is made
decision, entitlements, err := p.pdp.GetDecisionRegisteredResource(ctx, regResValueFQN, action, resources)
if err != nil {
Expand All @@ -190,10 +194,10 @@ func (p *JustInTimePDP) GetDecision(
decision.AllPermitted = entitledWithAnyObligationsSatisfied
decision.Results = resourceDecisions

p.auditDecision(
// Enrich audit with computed information and mark as successful
p.enrichAuditDecision(
ctx,
regResValueFQN,
action,
auditEvent,
entitledWithAnyObligationsSatisfied,
entitlements,
fulfillableObligationValueFQNs,
Expand All @@ -209,11 +213,20 @@ func (p *JustInTimePDP) GetDecision(
return nil, fmt.Errorf("failed to resolve entity identifier: %w", err)
}

auditEvents := make([]*audit.GetDecisionV2Event, 0, len(entityRepresentations))
for _, entityRep := range entityRepresentations {
auditEvent := p.logger.Audit.DecisionV2(ctx, entityRep.GetOriginalId(), action.GetName())
defer auditEvent.Log(ctx)
auditEvents = append(auditEvents, auditEvent)
}

// Get a decision on each entity representation and consolidate into an overall decision
var resourceDecisionsAcrossAllEntityReps []ResourceDecision
allPermitted := true

for _, entityRep := range entityRepresentations {
for i, entityRep := range entityRepresentations {
auditEvent := auditEvents[i]

entityRepresentationDecision, entitlements, err := p.pdp.GetDecision(ctx, entityRep, action, resources)
if err != nil {
return nil, fmt.Errorf("failed to get decision for entityRepresentation with original id [%s]: %w", entityRep.GetOriginalId(), err)
Expand All @@ -239,12 +252,11 @@ func (p *JustInTimePDP) GetDecision(
return nil, fmt.Errorf("failed to apply obligations and consolidate for entity representation [%s]: %w", entityRep.GetOriginalId(), err)
}

// Audit decision for this entity representation
// Enrich audit for this entity representation with computed information and mark as successful
entityAllPermitted := entityRepresentationDecision.AllPermitted && allObligationsSatisfied
p.auditDecision(
p.enrichAuditDecision(
ctx,
entityRep.GetOriginalId(),
action,
auditEvent,
entityAllPermitted,
entitlements,
fulfillableObligationValueFQNs,
Expand Down Expand Up @@ -431,32 +443,29 @@ func (p *JustInTimePDP) resolveEntitiesFromRequestToken(
return p.resolveEntitiesFromToken(ctx, token, skipEnvironmentEntities)
}

// auditDecision logs a GetDecisionV2 audit event with obligation information.
// enrichAuditDecision enriches a deferred audit event with computed information and marks it as successful.
// The auditResourceDecisions parameter should contain the full obligation context including
// for non-entitled resources, which is intentionally excluded from the actual response.
func (p *JustInTimePDP) auditDecision(
func (p *JustInTimePDP) enrichAuditDecision(
ctx context.Context,
entityID string,
action *policy.Action,
auditEvent *audit.GetDecisionV2Event,
allPermitted bool,
entitlements map[string][]*policy.Action,
fulfillableObligationValueFQNs []string,
obligationDecision obligations.ObligationPolicyDecision,
auditResourceDecisions []ResourceDecision,
) {
// Determine audit decision result
// Update audit with computed information
auditEvent.UpdateEntitlements(entitlements)
auditEvent.UpdateResourceDecisions(auditResourceDecisions)
auditEvent.UpdateObligations(fulfillableObligationValueFQNs, obligationDecision.AllObligationsSatisfied)

// Determine final audit decision result
auditDecision := audit.GetDecisionResultDeny
if allPermitted {
auditDecision = audit.GetDecisionResultPermit
}

p.logger.Audit.GetDecisionV2(ctx, audit.GetDecisionV2EventParams{
EntityID: entityID,
ActionName: action.GetName(),
Decision: auditDecision,
Entitlements: entitlements,
FulfillableObligationValueFQNs: fulfillableObligationValueFQNs,
ObligationsSatisfied: obligationDecision.AllObligationsSatisfied,
ResourceDecisions: auditResourceDecisions,
})
// Mark as successful with final decision
auditEvent.Success(ctx, auditDecision)
}
4 changes: 2 additions & 2 deletions service/kas/access/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ type PolicyBody struct {
}

// Audit helper methods
func ConvertToAuditKasPolicy(policy Policy) audit.KasPolicy {
return audit.KasPolicy{
func ConvertToAuditKasPolicy(policy Policy) *audit.KasPolicy {
return &audit.KasPolicy{
UUID: policy.UUID,
Body: audit.KasPolicyBody{
DataAttributes: convertToAuditKasBodyDataAttributes(policy.Body.DataAttributes),
Expand Down
Loading
Loading