Service Hierarchy Security Considerations Guide
Purpose: Authorisation, cascade protection, and security patterns for service hierarchy operations.
Authorisation Checks for Hierarchy Operations
Multi-Tenant Isolation (CRITICAL)
Requirement: All hierarchy queries MUST filter by sellerId to prevent cross-tenant data access.
No tenant filter
async getHierarchy(parentId: string): Promise<ServiceTemplate[]> {
return this.templateRepo.find({
where: { parentServiceTemplateId: parentId },
});
}
Tenant-scoped query
async getHierarchy(parentId: string, sellerId: string): Promise<ServiceTemplate[]> {
return this.templateRepo.find({
where: {
parentServiceTemplateId: parentId,
sellerId: sellerId, // MANDATORY
},
});
}
RBAC for Hierarchy Operations
| Operation | Required Role | Rationale |
|---|---|---|
| View hierarchy | catalog.read | Read-only access |
| Create template | catalog.write | Modify catalogue |
| Modify parent | catalog.admin | Structural change |
| Delete template | catalog.admin | Destructive action |
| Activate service | service.write | Customer operation |
| Terminate cascade | service.admin | Bulk operation |
@Controller('catalog/templates')
export class ServiceTemplateController {
@Get(':code/hierarchy')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('catalog.read')
async getHierarchy(@Param('code') code: string, @CurrentUser() user: User) {
return this.service.getHierarchy(code, user.sellerId);
}
@Put(':code/parent')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('catalog.admin')
async updateParent(
@Param('code') code: string,
@Body() dto: UpdateParentDto,
@CurrentUser() user: User
) {
return this.service.updateParent(code, dto.parentCode, user.sellerId);
}
}
Customer Context Validation
// Verify customer belongs to tenant before hierarchy operations
async validateCustomerContext(
customerId: string,
sellerId: string
): Promise<void> {
const customer = await this.customerRepo.findOne({
where: { id: customerId, sellerId },
});
if (!customer) {
throw new ForbiddenException(
'Customer not found in current tenant context'
);
}
}
Cascade Termination Protection
HRT Protection for Cascade Operations
High-Risk Transactions (HRT) require additional verification before cascade operations.
@Injectable()
export class CascadeTerminationService {
async terminateWithChildren(
serviceId: string,
context: OperationContext
): Promise<TerminationResult> {
// 1. Check if HRT protection required
const service = await this.serviceRepo.findOne({ where: { id: serviceId } });
const childCount = await this.getActiveChildCount(serviceId);
if (childCount > 0) {
// 2. Require HRT verification for cascade
if (!context.hrtVerified) {
throw new HrtRequiredException(
`Cascade termination affects ${childCount} child services. HRT verification required.`
);
}
}
// 3. Verify MFA if required
if (this.requiresMfa(service)) {
if (!context.mfaVerified) {
throw new MfaRequiredException(
'MFA verification required for termination'
);
}
}
// 4. Perform cascade termination
return this.executeTermination(serviceId, context);
}
}
Cascade Operation Limits
const CASCADE_LIMITS = {
maxChildrenForAutoTerminate: 10, // Auto-approve if <= 10 children
maxChildrenForHrtOnly: 50, // HRT only if <= 50 children
maxChildrenAbsolute: 100, // Block if > 100 children (admin review required)
};
async validateCascadeOperation(serviceId: string): Promise<CascadeValidation> {
const childCount = await this.getActiveChildCount(serviceId);
if (childCount <= CASCADE_LIMITS.maxChildrenForAutoTerminate) {
return { allowed: true, requiresHrt: false, requiresApproval: false };
}
if (childCount <= CASCADE_LIMITS.maxChildrenForHrtOnly) {
return { allowed: true, requiresHrt: true, requiresApproval: false };
}
if (childCount <= CASCADE_LIMITS.maxChildrenAbsolute) {
return { allowed: true, requiresHrt: true, requiresApproval: true };
}
return {
allowed: false,
reason: `Cannot cascade to ${childCount} children. Contact support.`,
};
}
Cross-Customer Isolation
Instance-Level Isolation
// Always verify service belongs to customer
async getServiceInstance(
instanceId: string,
customerId: string,
sellerId: string
): Promise<ServiceInstance> {
const instance = await this.instanceRepo.findOne({
where: {
id: instanceId,
customerId: customerId,
sellerId: sellerId, // Triple isolation
},
});
if (!instance) {
throw new NotFoundException('Service instance not found');
}
return instance;
}
// Verify parent-child relationship is within same customer
async validateParentChildRelation(
parentId: string,
childId: string,
customerId: string
): Promise<void> {
const parent = await this.getServiceInstance(parentId, customerId);
const child = await this.getServiceInstance(childId, customerId);
if (parent.customerId !== child.customerId) {
throw new SecurityException(
'Parent and child must belong to same customer'
);
}
}
SQL Injection Prevention
String interpolation
const query = `
SELECT * FROM cat_service_template
WHERE code = '${userInput}'
`;
Parameterised query
const templates = await this.templateRepo.find({
where: { code: userInput }, // TypeORM handles escaping
});
// Raw query with parameters
const result = await this.dataSource.query(
`SELECT * FROM cat_service_template WHERE code = $1`,
[userInput]
);
Permission Inheritance Patterns
Hierarchical Permission Model
interface HierarchyPermissions {
canView: boolean;
canActivate: boolean;
canModify: boolean;
canTerminate: boolean;
cascadePermissions: boolean; // If true, applies to children
}
async getEffectivePermissions(
userId: string,
serviceCode: string
): Promise<HierarchyPermissions> {
// 1. Get direct permissions
const directPerms = await this.permissionService.getUserPermissions(
userId,
serviceCode
);
// 2. If no direct permissions, check parent
const template = await this.templateRepo.findOne({
where: { code: serviceCode },
relations: ['parent'],
});
if (!directPerms && template.parent) {
const parentPerms = await this.getEffectivePermissions(
userId,
template.parent.code
);
if (parentPerms.cascadePermissions) {
return parentPerms;
}
}
return directPerms || this.getDefaultPermissions();
}
Role-Based Cascade Control
| User Role | Can View Hierarchy | Can Modify Structure | Can Terminate Cascade |
|---|---|---|---|
| Customer | Own services only | No | No |
| Support | Assigned customers | No | Single services |
| Manager | All customers | Templates only | With approval |
| Admin | All | All | All |
Audit Logging for Hierarchy Changes
Required Audit Events
enum HierarchyAuditEvent {
TEMPLATE_CREATED = 'HIERARCHY.TEMPLATE_CREATED',
TEMPLATE_MODIFIED = 'HIERARCHY.TEMPLATE_MODIFIED',
PARENT_CHANGED = 'HIERARCHY.PARENT_CHANGED',
TEMPLATE_DELETED = 'HIERARCHY.TEMPLATE_DELETED',
CASCADE_ACTIVATION = 'HIERARCHY.CASCADE_ACTIVATION',
CASCADE_SUSPENSION = 'HIERARCHY.CASCADE_SUSPENSION',
CASCADE_TERMINATION = 'HIERARCHY.CASCADE_TERMINATION',
}
Audit Log Structure
interface HierarchyAuditLog {
id: string;
timestamp: Date;
event: HierarchyAuditEvent;
actor: {
userId: string;
userEmail: string;
role: string;
ipAddress: string;
};
target: {
serviceCode: string;
serviceId: string;
customerId?: string;
sellerId: string;
};
changes: {
field: string;
oldValue: any;
newValue: any;
}[];
context: {
hrtVerified: boolean;
mfaVerified: boolean;
cascadeCount: number;
};
}
Logging Implementation
@Injectable()
export class HierarchyAuditService {
async logParentChange(
template: ServiceTemplate,
oldParent: string | null,
newParent: string | null,
context: OperationContext
): Promise<void> {
await this.auditRepo.save({
timestamp: new Date(),
event: HierarchyAuditEvent.PARENT_CHANGED,
actor: {
userId: context.userId,
userEmail: context.userEmail,
role: context.role,
ipAddress: context.ipAddress,
},
target: {
serviceCode: template.code,
serviceId: template.id,
sellerId: context.sellerId,
},
changes: [{
field: 'parentServiceTemplate',
oldValue: oldParent,
newValue: newParent,
}],
context: {
hrtVerified: context.hrtVerified || false,
mfaVerified: context.mfaVerified || false,
cascadeCount: 0,
},
});
}
async logCascadeTermination(
rootService: ServiceInstance,
terminatedChildren: ServiceInstance[],
context: OperationContext
): Promise<void> {
await this.auditRepo.save({
timestamp: new Date(),
event: HierarchyAuditEvent.CASCADE_TERMINATION,
actor: { /* ... */ },
target: {
serviceCode: rootService.template.code,
serviceId: rootService.id,
customerId: rootService.customerId,
sellerId: context.sellerId,
},
changes: terminatedChildren.map(child => ({
field: 'status',
oldValue: 'ACTIVE',
newValue: 'TERMINATED',
childServiceCode: child.template.code,
})),
context: {
hrtVerified: context.hrtVerified,
mfaVerified: context.mfaVerified,
cascadeCount: terminatedChildren.length,
},
});
}
}
OWASP Top 10 Considerations
A01:2021 - Broken Access Control
Risk: Unauthorised access to hierarchy data across tenants.
Mitigations:
- ✅ sellerId filter on all queries
- ✅ Role-based access control
- ✅ Customer context validation
A02:2021 - Cryptographic Failures
Risk: Exposure of sensitive hierarchy data in transit/at rest.
Mitigations:
- ✅ TLS 1.3 for all API calls
- ✅ Encrypted database connections
- ✅ No sensitive data in service codes
A03:2021 - Injection
Risk: SQL injection through hierarchy queries.
Mitigations:
- ✅ Parameterised queries only
- ✅ Input validation on service codes
- ✅ TypeORM query builder
A04:2021 - Insecure Design
Risk: Hierarchy allows privilege escalation.
Mitigations:
- ✅ Separate template vs instance permissions
- ✅ Cascade limits
- ✅ HRT for destructive operations
A07:2021 - Identification and Authentication Failures
Risk: Unauthenticated hierarchy modification.
Mitigations:
- ✅ JWT authentication required
- ✅ MFA for sensitive operations
- ✅ Session validation
A09:2021 - Security Logging and Monitoring Failures
Risk: Hierarchy changes not tracked.
Mitigations:
- ✅ Comprehensive audit logging
- ✅ Cascade operation logging
- ✅ Tamper-proof audit trail
Privacy Act Compliance
PII in Hierarchy Data
Assessment: Service hierarchy data typically does NOT contain PII.
| Data Type | Contains PII | Protection Required |
|---|---|---|
| Service template codes | No | None |
| Service descriptions | No | None |
| Hierarchy relationships | No | None |
| Service instance IDs | No (UUID) | None |
| Customer IDs | Yes (identifier) | Tenant isolation |
| Activation dates | No | None |
Data Retention
// Hierarchy audit logs must be retained per Privacy Act
const HIERARCHY_AUDIT_RETENTION = {
standard: 7 * 365 * 24 * 60 * 60 * 1000, // 7 years
pii: 7 * 365 * 24 * 60 * 60 * 1000, // 7 years
};
// Purge old audit logs
async purgeExpiredAuditLogs(): Promise<void> {
const cutoff = new Date(Date.now() - HIERARCHY_AUDIT_RETENTION.standard);
await this.auditRepo.delete({
timestamp: LessThan(cutoff),
});
}
SOCI Act Compliance
Critical Infrastructure Considerations
Assessment: Service hierarchy IS critical infrastructure for telecommunications.
Requirements:
- Risk Management: Document hierarchy security risks
- Incident Response: Procedures for hierarchy compromise
- Access Control: Role-based access with MFA
- Audit Trail: Complete logging of hierarchy changes
Hierarchy-Specific Controls
// SOCI Act: Critical operations require enhanced verification
const SOCI_CRITICAL_OPERATIONS = [
'CASCADE_TERMINATION',
'PARENT_CHANGE',
'TEMPLATE_DELETION',
];
async verifySociCompliance(
operation: string,
context: OperationContext
): Promise<void> {
if (SOCI_CRITICAL_OPERATIONS.includes(operation)) {
// 1. Verify MFA
if (!context.mfaVerified) {
throw new SociComplianceException('MFA required for critical operation');
}
// 2. Verify authorised personnel
if (!this.isAuthorisedForCriticalOps(context.userId)) {
throw new SociComplianceException('User not authorised for critical operation');
}
// 3. Log for SOCI reporting
await this.sociAuditService.logCriticalOperation(operation, context);
}
}
Essential Eight Compliance
Application Control
Requirement: Only authorised applications can modify hierarchy.
Implementation:
- API authentication required
- Admin portal with MFA
- No direct database access
Restrict Administrative Privileges
Requirement: Limit hierarchy admin access.
Implementation:
- Separate
catalog.adminrole - Time-limited admin sessions
- Just-in-time access for hierarchy changes
Multi-Factor Authentication
Requirement: MFA for privileged access.
Implementation:
@UseGuards(JwtAuthGuard, MfaGuard)
@Roles('catalog.admin')
async deleteTemplate(code: string, context: OperationContext) {
// MFA already verified by guard
return this.service.deleteTemplate(code, context.sellerId);
}
Daily Backups
Requirement: Backup hierarchy data daily.
Implementation:
- Database backup includes all hierarchy tables
- Point-in-time recovery enabled
- Tested restore procedures
Security Checklist
Template Configuration Security
- All templates have sellerId assigned
- No orphaned templates (missing parent)
- No circular dependencies
- Charges properly configured
- Eligibility constraints validated
Instance Security
- All instances linked to valid customer
- Parent-child relationships validated
- Status transitions logged
- Cascade operations limited
Access Control Security
- RBAC implemented for all operations
- MFA required for admin operations
- HRT protection for cascades
- Session validation on each request
Audit Security
- All changes logged
- Audit logs tamper-proof
- Retention policy implemented
- Logs available for compliance