Enforce backend route authorization for self-hosted model operations for non-Duo Enterprise users
Summary
While #579420 addresses hiding UI action buttons for self-hosted models from non-Duo Enterprise users, we need to ensure that the backend routes are fully protected against direct access attempts (API calls, URL manipulation, etc.).
Problem
Currently, even with UI buttons hidden, non-Enterprise users could potentially:
- Make direct API calls to create/update/delete self-hosted models
- Bypass frontend restrictions using browser dev tools
- Access routes directly via URL manipulation
This creates a security gap where authorization is enforced only at the UI layer, not at the controller/route level.
Proposal
Implement comprehensive backend authorization checks to ensure non-Duo Enterprise users cannot perform create, update, or delete operations on self-hosted models, regardless of how they attempt to access these endpoints.
Requirements
Backend Authorization
Controller-Level Protection:
- Add
before_actionauthorization check inAdmin::GitlabDuo::SelfHostedModelsController - Block
createaction for non-Enterprise users (return 403 Forbidden) - Block
updateaction for non-Enterprise users (return 403 Forbidden) - Block
deleteaction for non-Enterprise users (return 403 Forbidden) - Ensure
indexandshowactions remain accessible (read-only viewing) - Return clear, actionable error messages (e.g., "Self-hosted model management requires Duo Enterprise")
Policy/Ability Updates:
- Update or create
Admin::GitlabDuo::SelfHostedModelPolicy(or equivalent) - Define tier-based permissions:
- Duo Enterprise: Full CRUD access
- Duo Pro/Core: Read-only access
- Verify policy is applied consistently across all controller actions
Route Protection:
- Verify routes in
config/routes.rbare protected - Ensure unauthorized requests return 403 (not 404 or 500)
- Test direct API endpoint access for each tier
Test Coverage
Controller Specs
`# spec/controllers/admin/gitlab_duo/self_hosted_models_controller_spec.rb
context 'with Duo Pro license' do it 'returns 403 for create action' do post :create, params: { ... } expect(response).to have_gitlab_http_status(:forbidden) end
it 'returns 403 for update action' do put :update, params: { id: model.id, ... } expect(response).to have_gitlab_http_status(:forbidden) end
it 'returns 403 for delete action' do delete :destroy, params: { id: model.id } expect(response).to have_gitlab_http_status(:forbidden) end
it 'allows index action (read-only)' do get :index expect(response).to have_gitlab_http_status(:ok) end end `
Request Specs
- Test API endpoints for each tier (Core/Pro/Enterprise)
- Verify 403 responses include appropriate error messages
- Test edge cases (expired licenses, tier downgrades)
Policy Specs
- Test
can?(:create, SelfHostedModel)for each tier - Test
can?(:update, SelfHostedModel)for each tier - Test
can?(:destroy, SelfHostedModel)for each tier - Test
can?(:read, SelfHostedModel)returns true for all tiers
Integration Tests
- Feature spec: Non-Enterprise admin attempts direct route access
- API spec: Verify curl/API client requests are blocked
- Verify error handling doesn't leak sensitive information
Acceptance Criteria
-
Authorization Enforcement:
- Non-Enterprise admins receive 403 Forbidden when attempting POST/PUT/DELETE on self-hosted model routes
- Enterprise admins can successfully perform all CRUD operations
- All tiers can view (GET) self-hosted models
-
Error Handling:
- 403 responses include clear error message: "Self-hosted model management requires Duo Enterprise"
- No 500 errors or stack traces exposed
- Consistent error format across all endpoints
-
Test Coverage:
- Controller specs cover all actions for all tiers
- Policy specs verify tier-based permissions
- Request specs test direct API access
- All tests pass in CI/CD pipeline
-
Documentation:
- Update relevant API documentation with tier requirements
- Add inline code comments explaining authorization logic
- Update admin documentation if needed
Technical Implementation Notes
Suggested Approach
Option 1: Policy-based (Recommended)
`# app/policies/admin/gitlab_duo/self_hosted_model_policy.rb class Admin::GitlabDuo::SelfHostedModelPolicy < BasePolicy condition(:duo_enterprise_enabled) do License.feature_available?(:duo_enterprise) end
rule { duo_enterprise_enabled }.enable :create rule { duo_enterprise_enabled }.enable :update rule { duo_enterprise_enabled }.enable :destroy rule { admin }.enable :read end
app/controllers/admin/gitlab_duo/self_hosted_models_controller.rb
before_action :authorize_self_hosted_model_management!, only: [:create, :update, :destroy]
def authorize_self_hosted_model_management! render_403 unless can?(current_user, :create, Admin::GitlabDuo::SelfHostedModel) end `
Option 2: Ability-based
# app/models/ability.rb def admin_gitlab_duo_abilities if License.feature_available?(:duo_enterprise) can :manage, Admin::GitlabDuo::SelfHostedModel else can :read, Admin::GitlabDuo::SelfHostedModel end end
Files to Modify
app/controllers/admin/gitlab_duo/self_hosted_models_controller.rb-
app/policies/admin/gitlab_duo/self_hosted_model_policy.rb(create if doesn't exist) spec/controllers/admin/gitlab_duo/self_hosted_models_controller_spec.rbspec/policies/admin/gitlab_duo/self_hosted_model_policy_spec.rbspec/requests/api/admin/gitlab_duo/self_hosted_models_spec.rb
Related Issues & MRs
- Parent issue: #579053 (Instance-Level Model Selection Configuration incorrectly restricted)
- Related issue: #579420 (Hide self-hosted model action buttons for non-Duo Enterprise users)
- Backend implementation: !210969
Should be addressed in the same milestone as #579420 to ensure complete protection.