Skip to content

Commit 027785f

Browse files
committed
Add Audit Service to CE and PDP in OE federator
1 parent 8b29986 commit 027785f

19 files changed

Lines changed: 1137 additions & 85 deletions

File tree

audit-service/README.md

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -221,10 +221,45 @@ go build -o audit-service
221221
go build -ldflags="-X main.Version=1.0.0 -X main.GitCommit=$(git rev-parse HEAD)" -o audit-service
222222
```
223223

224-
## Deployment
224+
## Optional Deployment
225225

226-
### Docker
226+
The Audit Service is **optional** and can be deployed separately from the main OpenDIF services. Services (Orchestration Engine, Portal Backend) will function normally without audit logging enabled.
227227

228+
### Enabling/Disabling Audit Logging
229+
230+
#### Option 1: Disable via Environment Variable
231+
Set `ENABLE_AUDIT=false` in your service configuration:
232+
```bash
233+
# In orchestration-engine/.env or portal-backend/.env
234+
ENABLE_AUDIT=false
235+
```
236+
237+
#### Option 2: Omit Audit Service URL
238+
Leave `CHOREO_AUDIT_CONNECTION_SERVICEURL` unset or empty:
239+
```bash
240+
# Services will automatically disable audit logging if URL is not configured
241+
# CHOREO_AUDIT_CONNECTION_SERVICEURL=
242+
```
243+
244+
#### Option 3: Enable Audit Logging
245+
Set the audit service URL:
246+
```bash
247+
# In orchestration-engine/.env or portal-backend/.env
248+
CHOREO_AUDIT_CONNECTION_SERVICEURL=http://localhost:3001
249+
ENABLE_AUDIT=true # Optional, defaults to true if URL is provided
250+
```
251+
252+
### Deployment Steps
253+
254+
#### Step 1: Deploy Audit Service (Optional)
255+
256+
**Using Docker Compose:**
257+
```bash
258+
cd audit-service
259+
docker compose up -d
260+
```
261+
262+
**Using Docker:**
228263
```bash
229264
# Build image
230265
docker build -t audit-service .
@@ -246,19 +281,73 @@ docker run -d \
246281
audit-service
247282
```
248283

249-
### Docker Compose
284+
**Using Standalone Binary:**
285+
```bash
286+
cd audit-service
287+
go build -o audit-service
288+
./audit-service
289+
```
290+
291+
#### Step 2: Configure Services to Use Audit Service
292+
293+
**For Orchestration Engine:**
294+
```bash
295+
# In exchange/orchestration-engine/.env
296+
CHOREO_AUDIT_CONNECTION_SERVICEURL=http://localhost:3001
297+
ENABLE_AUDIT=true
298+
```
250299

300+
**For Portal Backend:**
251301
```bash
252-
# Start service
302+
# In portal-backend/.env
303+
CHOREO_AUDIT_CONNECTION_SERVICEURL=http://localhost:3001
304+
ENABLE_AUDIT=true
305+
```
306+
307+
#### Step 3: Verify Audit Logging
308+
309+
1. Make a request to Orchestration Engine or Portal Backend
310+
2. Check audit service logs: `GET http://localhost:3001/api/audit-logs`
311+
3. Verify events are being logged with trace IDs
312+
313+
### Docker Compose Usage
314+
315+
The audit service has its own `docker-compose.yml` for standalone deployment:
316+
317+
```bash
318+
# Deploy audit service separately
319+
cd audit-service
253320
docker compose up -d
254321

255322
# View logs
256323
docker compose logs -f
257324

258325
# Stop service
259326
docker compose down
327+
328+
# Or include in your main docker-compose.yml (optional)
329+
# See audit-service/docker-compose.yml for reference
260330
```
261331

332+
**Note:** The main `exchange/docker-compose.yml` does not include audit-service by default. Deploy it separately or add it manually if needed.
333+
334+
### Environment Variables for Enabling/Disabling
335+
336+
| Service | Environment Variable | Default | Description |
337+
|---------|---------------------|---------|------------|
338+
| Orchestration Engine | `CHOREO_AUDIT_CONNECTION_SERVICEURL` | Empty | Audit service base URL |
339+
| Orchestration Engine | `ENABLE_AUDIT` | `true` (if URL set) | Enable/disable audit logging |
340+
| Portal Backend | `CHOREO_AUDIT_CONNECTION_SERVICEURL` | Empty | Audit service base URL |
341+
| Portal Backend | `ENABLE_AUDIT` | `true` (if URL set) | Enable/disable audit logging |
342+
343+
### Graceful Degradation
344+
345+
- Services continue to function normally if audit service is unavailable
346+
- No errors are thrown when audit service URL is not configured
347+
- Audit operations are asynchronous (fire-and-forget) to avoid blocking requests
348+
- Services can be started before audit service is ready
349+
350+
262351
### Production Considerations
263352

264353
1. **Database**: Use PostgreSQL for production deployments

audit-service/audit-service

16.7 MB
Binary file not shown.

audit-service/config/config.go

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,32 +32,34 @@ type Config struct {
3232
Enums AuditEnums `yaml:"enums"`
3333
}
3434

35-
// DefaultEnums provides default enum values if config file is not found
36-
var DefaultEnums = AuditEnums{
37-
EventTypes: []string{
38-
"POLICY_CHECK",
39-
"MANAGEMENT_EVENT",
40-
"USER_MANAGEMENT",
41-
"DATA_FETCH",
42-
"CONSENT_CHECK",
43-
},
44-
EventActions: []string{
45-
"CREATE",
46-
"READ",
47-
"UPDATE",
48-
"DELETE",
49-
},
50-
ActorTypes: []string{
51-
"SERVICE",
52-
"ADMIN",
53-
"MEMBER",
54-
"SYSTEM",
55-
},
56-
TargetTypes: []string{
57-
"SERVICE",
58-
"RESOURCE",
59-
},
60-
}
35+
var (
36+
// DefaultEnums provides default enum values if config file is not found
37+
// Note: OpenDIF-specific event types (ORCHESTRATION_REQUEST_RECEIVED, POLICY_CHECK, CONSENT_CHECK, PROVIDER_FETCH)
38+
// should be added to config/enums.yaml for project-specific configurations
39+
DefaultEnums = AuditEnums{
40+
EventTypes: []string{
41+
"MANAGEMENT_EVENT",
42+
"USER_MANAGEMENT",
43+
"DATA_FETCH",
44+
},
45+
EventActions: []string{
46+
"CREATE",
47+
"READ",
48+
"UPDATE",
49+
"DELETE",
50+
},
51+
ActorTypes: []string{
52+
"SERVICE",
53+
"ADMIN",
54+
"MEMBER",
55+
"SYSTEM",
56+
},
57+
TargetTypes: []string{
58+
"SERVICE",
59+
"RESOURCE",
60+
},
61+
}
62+
)
6163

6264
// LoadEnums loads enum configuration from YAML file
6365
// If the file is not found or cannot be read, returns default enums

audit-service/config/enums.yaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,22 @@
55
enums:
66
# Event Type: User-defined custom names for event classification
77
# Examples: POLICY_CHECK, MANAGEMENT_EVENT, USER_MANAGEMENT, etc.
8+
#
9+
# OpenDIF Core specific event types:
10+
# - ORCHESTRATION_REQUEST_RECEIVED: GraphQL request received by Orchestration Engine
11+
# - POLICY_CHECK: Policy decision result logged by Policy Decision Point (one record per API call)
12+
# - CONSENT_CHECK: Consent check result logged by Consent Engine (one record per API call)
13+
# - PROVIDER_FETCH: Provider data fetch result logged by Orchestration Engine (one record per API call)
814
eventTypes:
15+
# OpenDIF Core specific event types
16+
- ORCHESTRATION_REQUEST_RECEIVED
917
- POLICY_CHECK
18+
- CONSENT_CHECK
19+
- PROVIDER_FETCH
20+
# Generic event types (available by default)
1021
- MANAGEMENT_EVENT
1122
- USER_MANAGEMENT
1223
- DATA_FETCH
13-
- CONSENT_CHECK
1424

1525
# Event Action: CRUD operations
1626
eventActions:

audit-service/v1/database/gorm_repository.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ func (r *GormRepository) GetAuditLogs(ctx context.Context, filters *AuditLogFilt
7777
}
7878

7979
// Apply pagination and ordering
80+
// Note: Results are ordered by timestamp DESC (newest first) for general queries.
81+
// For trace-specific queries, use GetAuditLogsByTraceID which orders by ASC (chronological).
8082
limit := filters.Limit
8183
if limit <= 0 {
8284
limit = 100 // default

audit-service/v1/testutil/mock_repository.go

Lines changed: 112 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package testutil
22

33
import (
44
"context"
5+
"sort"
56

67
"github.com/google/uuid"
78
"github.com/gov-dx-sandbox/audit-service/v1/database"
@@ -30,14 +31,121 @@ func (m *MockRepository) CreateAuditLog(ctx context.Context, log *v1models.Audit
3031
return log, nil
3132
}
3233

33-
// GetAuditLogsByTraceID returns empty results (can be extended for more complex test scenarios)
34+
// GetAuditLogsByTraceID retrieves all audit logs for a given trace ID
35+
// Results are ordered by timestamp ASC (chronological order)
3436
func (m *MockRepository) GetAuditLogsByTraceID(ctx context.Context, traceID string) ([]v1models.AuditLog, error) {
35-
return nil, nil
37+
// Parse traceID string to UUID for comparison
38+
traceUUID, err := uuid.Parse(traceID)
39+
if err != nil {
40+
return []v1models.AuditLog{}, nil // Return empty slice for invalid UUID
41+
}
42+
43+
var filteredLogs []v1models.AuditLog
44+
for _, log := range m.logs {
45+
if log.TraceID != nil && *log.TraceID == traceUUID {
46+
filteredLogs = append(filteredLogs, *log)
47+
}
48+
}
49+
50+
// Sort by timestamp ASC (chronological order)
51+
sort.Slice(filteredLogs, func(i, j int) bool {
52+
return filteredLogs[i].Timestamp.Before(filteredLogs[j].Timestamp)
53+
})
54+
55+
if filteredLogs == nil {
56+
return []v1models.AuditLog{}, nil
57+
}
58+
59+
return filteredLogs, nil
3660
}
3761

38-
// GetAuditLogs returns empty results (can be extended for more complex test scenarios)
62+
// GetAuditLogs retrieves audit logs with optional filtering
63+
// Results are ordered by timestamp DESC (newest first) and paginated
3964
func (m *MockRepository) GetAuditLogs(ctx context.Context, filters *database.AuditLogFilters) ([]v1models.AuditLog, int64, error) {
40-
return nil, 0, nil
65+
if filters == nil {
66+
filters = &database.AuditLogFilters{}
67+
}
68+
69+
// Filter logs based on provided criteria
70+
var filteredLogs []v1models.AuditLog
71+
for _, log := range m.logs {
72+
matches := true
73+
74+
// Filter by TraceID
75+
if filters.TraceID != nil && *filters.TraceID != "" {
76+
traceUUID, err := uuid.Parse(*filters.TraceID)
77+
if err != nil {
78+
continue // Skip if traceID is invalid
79+
}
80+
if log.TraceID == nil || *log.TraceID != traceUUID {
81+
matches = false
82+
}
83+
}
84+
85+
// Filter by EventType
86+
if matches && filters.EventType != nil && *filters.EventType != "" {
87+
if log.EventType == nil || *log.EventType != *filters.EventType {
88+
matches = false
89+
}
90+
}
91+
92+
// Filter by EventAction
93+
if matches && filters.EventAction != nil && *filters.EventAction != "" {
94+
if log.EventAction == nil || *log.EventAction != *filters.EventAction {
95+
matches = false
96+
}
97+
}
98+
99+
// Filter by Status
100+
if matches && filters.Status != nil && *filters.Status != "" {
101+
if log.Status != *filters.Status {
102+
matches = false
103+
}
104+
}
105+
106+
if matches {
107+
filteredLogs = append(filteredLogs, *log)
108+
}
109+
}
110+
111+
// Get total count before pagination
112+
total := int64(len(filteredLogs))
113+
114+
// Sort by timestamp DESC (newest first)
115+
sort.Slice(filteredLogs, func(i, j int) bool {
116+
return filteredLogs[i].Timestamp.After(filteredLogs[j].Timestamp)
117+
})
118+
119+
// Apply pagination
120+
limit := filters.Limit
121+
if limit <= 0 {
122+
limit = 100 // default
123+
}
124+
if limit > 1000 {
125+
limit = 1000 // max
126+
}
127+
128+
offset := filters.Offset
129+
if offset < 0 {
130+
offset = 0
131+
}
132+
133+
// Apply offset and limit
134+
start := offset
135+
end := offset + limit
136+
if start > len(filteredLogs) {
137+
start = len(filteredLogs)
138+
}
139+
if end > len(filteredLogs) {
140+
end = len(filteredLogs)
141+
}
142+
143+
if start >= end {
144+
return []v1models.AuditLog{}, total, nil
145+
}
146+
147+
paginatedLogs := filteredLogs[start:end]
148+
return paginatedLogs, total, nil
41149
}
42150

43151
// GetLogs returns all logs stored in the mock (useful for test assertions)

0 commit comments

Comments
 (0)