Cerberus: A Secure Access Request Management System with Go, GraphQL, OPA, and AWS S3
Building Cerberus: A Secure Access Request Management System with Go, GraphQL, OPA, and AWS S3
Why the Name “Cerberus”?
Cerberus is inspired by Greek mythology.
In Greek mythology, Cerberus was the multi-headed guardian dog that protected the gates of the underworld and controlled who could enter and leave.
I chose this name because the project is all about controlling access to protected resources.
Just as Cerberus guarded access in mythology, this application manages who can request access, who can approve it, and who is authorized to perform sensitive actions.
The name perfectly matches the purpose of the system: Access Management and Authorization.
The Problem I Wanted to Solve
In many organizations, employees need temporary access to internal systems, databases, dashboards, or production environments.
Without a proper system:
- Requests are handled through emails or chat messages.
- There is no approval workflow.
- Access decisions are difficult to track.
- Security audits become challenging.
- No centralized authorization exists.
To solve this problem, I built Cerberus.
Cerberus provides:
- User authentication
- Access request creation
- Screenshot uploads
- Approval workflows
- Audit logging
- Centralized authorization through Open Policy Agent (OPA)
High-Level Architecture
The architecture follows a layered design where each layer has a specific responsibility.
Frontend Client
|
v
GraphQL API
|
v
Resolvers
|
v
Services
|
v
OPA Authorization
|
v
Repositories
|
v
Ent ORM
|
v
MySQL
This separation keeps the codebase clean, testable, and scalable.
Complete System Architecture
The actual request flow inside Cerberus looks like this:
Client / Frontend
|
| HTTP POST /query
| Authorization: Bearer <JWT>
v
+------------------------------------------------------------------+
| Gin HTTP Server | | | | |
| CORS Middleware | | |
| Recovery Middleware | | |
| JWT Authentication Middleware | | | | |
| Routes | | |
| POST /query | | |
| GET /playground | | |
| GET /health | | |
| |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| GraphQL (gqlgen) | | |
| | | |
| Queries | | |
| - me |
| - users |
| - accessRequest |
| - accessRequests |
| - auditLogs | | |
| Mutations | | |
| - register |
| - login |
| - createAccessRequest |
| - uploadScreenshot |
| - approveRequest |
| - rejectRequest |
| - markUnderReview |
| - updateUserRole |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| Resolver Layer | | |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| Service Layer | | |
| AuthService | | |
| AccessRequestService | | |
| Business Rules | | |
| Workflow Management | | |
| Audit Logging | | |
| Authorization Calls | | |
+------------------------------------------------------------------+
|
+---------------+----------------+
| |
v v
OPA Client Repository Layer
| |
v v
Open Policy Agent Ent ORM
| |
v v
user.rego MySQL Database
role.rego
approval.rego
|
v
Authorization
Decision
|
v
AWS S3 Bucket
(Screenshot Storage)
Why I Chose This Architecture
When building backend applications, one common mistake is putting everything inside handlers or controllers.
As the project grows, the code becomes difficult to maintain.
I wanted a structure where:
- GraphQL handles API communication.
- Resolvers remain thin.
- Services contain business logic.
- Repositories handle database operations.
- OPA handles authorization.
- AWS S3 handles file storage.
Each component has a single responsibility.
This makes the project easier to test, debug, and scale.
Project Structure
To keep the codebase maintainable, I followed a layered architecture where each component has a single responsibility.
- GraphQL handles API communication
- Resolvers handle request mapping
- Services contain business logic
- OPA handles authorization
- Repositories handle database operations
- Ent ORM manages data access
- AWS S3 stores screenshots
The screenshot below shows the actual project structure used in Cerberus.

The Most Important Decision: OPA Authorization
One of the biggest improvements I made was introducing Open Policy Agent (OPA).
Initially, authorization checks looked like this:
if role != "ADMIN" {
return forbidden
}
This works for small applications but becomes difficult to maintain as permissions grow. Instead, I moved authorization logic into OPA policies.
Now the backend asks OPA:
Can this user perform this action?
OPA evaluates policies and returns:
ALLOW
or
DENY
This keeps authorization centralized and independent of application code.
Enterprise Roles and Departments
Another important design decision was separating Roles and Departments.
Roles
- EMPLOYEE
- APPROVER
- MANAGER
- ADMIN
- SUPER_ADMIN
Roles define permissions.
Departments
- ENGINEERING
- SUPPORT
- FINANCE
- HR
- SALES
Departments define organizational ownership.
For example:
Role = APPROVER
Department = ENGINEERING
This user can approve Engineering requests but cannot approve Finance requests.
This model is much more flexible than treating departments as roles.
Approval Workflow
The request approval process follows a structured workflow.
- Employee creates an access request.
- Request is stored with PENDING status.
- Audit log entry is created.
- Employee uploads supporting screenshots.
- Screenshot is stored in AWS S3.
- Manager or Approver reviews the request.
- OPA evaluates authorization policies.
- Request is approved, rejected, or marked under review.
- Another audit log entry is created.
Employee
|
| Create Request
v
+----------------------+
| Access Request |
| Status = PENDING |
+----------------------+
|
| Upload Screenshot
v
+----------------------+
| AWS S3 Storage |
+----------------------+
|
| Review Request
v
+----------------------+
| Manager / Approver |
+----------------------+
|
| Authorization
v
+----------------------+
| Open Policy Agent |
| (OPA) |
+----------------------+
|
+----------+----------+
| |
v v
APPROVED REJECTED
| |
+----------+----------+
|
v
+----------------------+
| Audit Log Created |
+----------------------+
|
v
+----------------------+
| GraphQL Response |
+----------------------+
This workflow ensures every action is traceable and authorized.
AWS S3 for File Storage
Screenshots uploaded by users are not stored directly in the database.
Instead, files are uploaded to AWS S3 and only the generated URL is stored in MySQL.
Benefits:
- Reduced database size
- Better scalability
- Easier file management
- Secure cloud storage
This approach keeps the application lightweight while supporting large file uploads.
Audit Logging
One of the most important features of Cerberus is audit logging. Every security-sensitive action is recorded in the audit_logs table.
Examples include:
- REQUEST_CREATED
- REQUEST_APPROVED
- REQUEST_REJECTED
- REQUEST_UNDER_REVIEW
- SCREENSHOT_UPLOADED
- USER_ROLE_CHANGED
Each audit record stores:
- Action
- Actor Email
- Actor Role
- Metadata
- Timestamp
- Request ID
This provides complete visibility into request lifecycle events and helps during debugging, compliance reviews, and security investigations.
The screenshot below shows actual audit records generated during testing.

Security Measures
Security was a major focus while building Cerberus.
The application includes:
- Password hashing using bcrypt
- JWT-based authentication
- OPA-based authorization
- Role-Based Access Control (RBAC)
- Department-Based Access Control (DBAC)
- Audit logging
- GraphQL query complexity limits
- Production GraphQL hardening
These features help ensure that access is controlled, actions are auditable, and sensitive operations remain secure.
What I Learned
Building Cerberus taught me several important engineering concepts:
- GraphQL API design with gqlgen
- Clean layered architecture
- JWT authentication
- Open Policy Agent integration
- AWS S3 file storage
- Ent ORM modeling
- Audit logging strategies
- Enterprise IAM concepts
- Role-Based Access Control (RBAC)
- Department-Based Access Control (DBAC)
More importantly, I learned that good architecture matters more than adding features quickly.
A well-structured system becomes easier to maintain, extend, and secure as it grows.
Conclusion
Cerberus started as a simple access request application, but it evolved into a complete Identity and Access Management style backend.
The project combines:
- Go
- GraphQL
- Ent ORM
- MySQL
- JWT Authentication
- Open Policy Agent
- AWS S3
- Audit Logging
By separating responsibilities into dedicated layers and moving authorization into OPA, the system remains secure, maintainable, and scalable.
For anyone interested in backend engineering, IAM systems, or policy-driven authorization, building a project like Cerberus is an excellent learning experience.
메타데이터
- post_id
- abe4e2e0d6ed
- slug
- cerberus-a-secure-access-request-management-system-with-go-graphql-opa-and-aws-s3-abe4e2e0d6ed
- url
- https://medium.com/@tushardhama3/cerberus-a-secure-access-request-management-system-with-go-graphql-opa-and-aws-s3-abe4e2e0d6ed
- canonical_url
- https://medium.com/@tushardhama3/cerberus-a-secure-access-request-management-system-with-go-graphql-opa-and-aws-s3-abe4e2e0d6ed
- author_url
- https://medium.com/@tushardhama3
- status
- ok
- fetched_at
- 2026-06-10 09:45:17