security: Fix critical RBAC vulnerability in user management

Critical security fixes:
- Add admin-only checks for user CRUD operations
- Add admin-only checks for role CRUD operations
- Add admin-only checks for role assignment operations
- Add permission check for agent script generation
- Fix auth_middleware to include is_admin flag in user context
- Hide user/role management buttons from non-admin users in UI
- Add 'View Only' labels for viewer users

Security improvements:
- Prevent viewer users from creating/editing/deleting users
- Prevent viewer users from creating/editing/deleting roles
- Prevent viewer users from assigning roles to users
- Backend API endpoints now properly check admin status
- Frontend UI now hides admin-only actions from viewers

Public release changes:
- Remove company-specific registry URLs from build-images.sh
- Update registry to generic example: your-registry.example.com

Affected endpoints:
- POST /api/users (create user) - admin only
- PUT /api/users/{id} (update user) - admin only
- DELETE /api/users/{id} (delete user) - admin only
- POST /api/roles (create role) - admin only
- PUT /api/roles/{id} (update role) - admin only
- DELETE /api/roles/{id} (delete role) - admin only
- POST /api/users/{id}/roles (assign roles) - admin only
- POST /api/agents/generate-install-script - permission check
This commit is contained in:
taylanbakircioglu
2025-11-04 13:44:49 +03:00
parent eeee768510
commit 1133fbe229
5 changed files with 155 additions and 74 deletions
+6 -4
View File
@@ -53,7 +53,7 @@ async def get_current_user_from_token(authorization: Optional[str] = None) -> Op
# Get user from database
conn = await get_database_connection()
user = await conn.fetchrow("""
SELECT id, username, email, full_name, role, is_active
SELECT id, username, email, full_name, role, is_active, is_admin
FROM users
WHERE id = $1 AND is_active = TRUE
""", int(user_id))
@@ -70,7 +70,8 @@ async def get_current_user_from_token(authorization: Optional[str] = None) -> Op
"username": user["username"],
"email": user["email"],
"full_name": user["full_name"],
"role": user["role"]
"role": user["role"],
"is_admin": user.get("is_admin", False)
}
except jwt.ExpiredSignatureError:
@@ -123,7 +124,7 @@ async def get_current_user_from_token_no_exception(authorization: Optional[str]
# Get user from database
conn = await get_database_connection()
user = await conn.fetchrow("""
SELECT id, username, email, full_name, is_active
SELECT id, username, email, full_name, is_active, is_admin
FROM users
WHERE id = $1 AND is_active = TRUE
""", int(user_id))
@@ -136,7 +137,8 @@ async def get_current_user_from_token_no_exception(authorization: Optional[str]
"id": user["id"],
"username": user["username"],
"email": user["email"],
"full_name": user["full_name"]
"full_name": user["full_name"],
"is_admin": user.get("is_admin", False)
}
except Exception as e:
+9 -1
View File
@@ -433,8 +433,16 @@ async def generate_install_script(req_data: AgentScriptRequest, request: Request
# Support both user authentication and agent API key
current_user = None
if authorization:
from auth_middleware import get_current_user_from_token
from auth_middleware import get_current_user_from_token, check_user_permission
current_user = await get_current_user_from_token(authorization)
# SECURITY: Check permission for agent script generation
has_permission = await check_user_permission(current_user["id"], "agents", "create")
if not has_permission:
raise HTTPException(
status_code=403,
detail="Insufficient permissions: agents.create required"
)
elif x_api_key:
# Agent API key authentication for upgrade process
agent_auth = await validate_agent_api_key(x_api_key)
+49
View File
@@ -157,6 +157,13 @@ async def create_user(user_data: UserCreate, authorization: str = Header(None)):
# Verify authentication
current_user = await get_current_user_from_token(authorization)
# SECURITY: Only admin users can create users
if not current_user.get("is_admin", False):
raise HTTPException(
status_code=403,
detail="Only admin users can create new users"
)
conn = await get_database_connection()
# Check if username already exists (only active users)
@@ -242,6 +249,13 @@ async def update_user(user_id: int, user_data: dict, authorization: str = Header
try:
current_user = await get_current_user_from_token(authorization)
# SECURITY: Only admin users can update other users
if not current_user.get("is_admin", False):
raise HTTPException(
status_code=403,
detail="Only admin users can update user information"
)
conn = await get_database_connection()
# Check if user exists
@@ -642,6 +656,13 @@ async def delete_user(user_id: int, authorization: str = Header(None)):
try:
current_user = await get_current_user_from_token(authorization)
# SECURITY: Only admin users can delete users
if not current_user.get("is_admin", False):
raise HTTPException(
status_code=403,
detail="Only admin users can delete users"
)
conn = await get_database_connection()
# Check if user exists and get details
@@ -940,6 +961,13 @@ async def create_role(role_data: dict, authorization: str = Header(None)):
# Verify authentication
current_user = await get_current_user_from_token(authorization)
# SECURITY: Only admin users can create roles
if not current_user.get("is_admin", False):
raise HTTPException(
status_code=403,
detail="Only admin users can create roles"
)
conn = await get_database_connection()
# Check if role name already exists
@@ -996,6 +1024,13 @@ async def update_role(role_id: int, role_data: dict, authorization: str = Header
# Verify authentication
current_user = await get_current_user_from_token(authorization)
# SECURITY: Only admin users can update roles
if not current_user.get("is_admin", False):
raise HTTPException(
status_code=403,
detail="Only admin users can update roles"
)
conn = await get_database_connection()
# Check if role exists
@@ -1061,6 +1096,13 @@ async def delete_role(role_id: int, authorization: str = Header(None)):
# Verify authentication
current_user = await get_current_user_from_token(authorization)
# SECURITY: Only admin users can delete roles
if not current_user.get("is_admin", False):
raise HTTPException(
status_code=403,
detail="Only admin users can delete roles"
)
conn = await get_database_connection()
# Check if role exists and is not system role
@@ -1112,6 +1154,13 @@ async def assign_user_roles(user_id: int, role_data: dict, authorization: str =
# Verify authentication
current_user = await get_current_user_from_token(authorization)
# SECURITY: Only admin users can assign roles to users
if not current_user.get("is_admin", False):
raise HTTPException(
status_code=403,
detail="Only admin users can assign roles to users"
)
conn = await get_database_connection()
# Check if user exists
+5 -3
View File
@@ -32,7 +32,9 @@ print_error() {
}
# Configuration
REGISTRY="${REGISTRY:-intprod-harbor.burgan.com.tr/devops}"
# Set REGISTRY environment variable or use default
# Example: export REGISTRY="your-registry.example.com/project"
REGISTRY="${REGISTRY:-your-registry.example.com/haproxy-openmanager}"
BACKEND_IMAGE="${REGISTRY}/haproxy-openmanager-backend"
FRONTEND_IMAGE="${REGISTRY}/haproxy-openmanager-frontend"
VERSION="${VERSION:-latest}"
@@ -99,10 +101,10 @@ fi
print_status "Updating Kubernetes manifests with new image versions..."
# Update backend deployment
sed -i.bak "s|image: intprod-harbor.burgan.com.tr/devops/haproxy-openmanager:<image-version>|image: $BACKEND_IMAGE:$VERSION|g" k8s/manifests/08-backend.yaml
sed -i.bak "s|image: your-registry.example.com/haproxy-openmanager/haproxy-openmanager-backend:<image-version>|image: $BACKEND_IMAGE:$VERSION|g" k8s/manifests/08-backend.yaml
# Update frontend deployment
sed -i.bak "s|image: intprod-harbor.burgan.com.tr/devops/haproxy-openmanager:<image-version>|image: $FRONTEND_IMAGE:$VERSION|g" k8s/manifests/09-frontend.yaml
sed -i.bak "s|image: your-registry.example.com/haproxy-openmanager/haproxy-openmanager-frontend:<image-version>|image: $FRONTEND_IMAGE:$VERSION|g" k8s/manifests/09-frontend.yaml
print_success "Kubernetes manifests updated"
+86 -66
View File
@@ -34,6 +34,7 @@ import {
DownloadOutlined
} from '@ant-design/icons';
import axios from 'axios';
import { useAuth } from '../contexts/AuthContext';
const { TabPane } = Tabs;
const { Option } = Select;
@@ -222,6 +223,7 @@ const PERMISSION_TREE = [
];
const UserManagement = () => {
const { isAdmin } = useAuth(); // Get admin status from auth context
const [activeTab, setActiveTab] = useState('users');
// Users state
@@ -718,41 +720,48 @@ const UserManagement = () => {
key: 'actions',
render: (_, record) => (
<Space>
<Tooltip title="Edit User">
<Button
icon={<EditOutlined />}
size="small"
onClick={() => handleEditUser(record)}
/>
</Tooltip>
<Tooltip title="Assign Roles">
<Button
icon={<TeamOutlined />}
size="small"
onClick={() => handleAssignRoles(record)}
/>
</Tooltip>
<Tooltip title="Change Password">
<Button
icon={<KeyOutlined />}
size="small"
onClick={() => handleChangePassword(record)}
/>
</Tooltip>
<Popconfirm
title="Are you sure you want to delete this user?"
onConfirm={() => handleDeleteUser(record)}
okText="Yes"
cancelText="No"
>
<Tooltip title="Delete User">
<Button
icon={<DeleteOutlined />}
danger
size="small"
/>
</Tooltip>
</Popconfirm>
{isAdmin() && (
<>
<Tooltip title="Edit User">
<Button
icon={<EditOutlined />}
size="small"
onClick={() => handleEditUser(record)}
/>
</Tooltip>
<Tooltip title="Assign Roles">
<Button
icon={<TeamOutlined />}
size="small"
onClick={() => handleAssignRoles(record)}
/>
</Tooltip>
<Tooltip title="Change Password">
<Button
icon={<KeyOutlined />}
size="small"
onClick={() => handleChangePassword(record)}
/>
</Tooltip>
<Popconfirm
title="Are you sure you want to delete this user?"
onConfirm={() => handleDeleteUser(record)}
okText="Yes"
cancelText="No"
>
<Tooltip title="Delete User">
<Button
icon={<DeleteOutlined />}
danger
size="small"
/>
</Tooltip>
</Popconfirm>
</>
)}
{!isAdmin() && (
<Text type="secondary">View Only</Text>
)}
</Space>
)
}
@@ -821,29 +830,36 @@ const UserManagement = () => {
key: 'actions',
render: (_, record) => (
<Space>
<Tooltip title="Edit Role">
<Button
icon={<EditOutlined />}
size="small"
onClick={() => handleEditRole(record)}
disabled={record.is_system}
/>
</Tooltip>
<Popconfirm
title="Are you sure you want to delete this role?"
onConfirm={() => handleDeleteRole(record)}
okText="Yes"
cancelText="No"
>
<Tooltip title="Delete Role">
<Button
icon={<DeleteOutlined />}
{isAdmin() && (
<>
<Tooltip title="Edit Role">
<Button
icon={<EditOutlined />}
size="small"
onClick={() => handleEditRole(record)}
disabled={record.is_system}
/>
</Tooltip>
<Popconfirm
title="Are you sure you want to delete this role?"
onConfirm={() => handleDeleteRole(record)}
okText="Yes"
cancelText="No"
>
<Tooltip title="Delete Role">
<Button
icon={<DeleteOutlined />}
danger
size="small"
disabled={record.is_system || record.user_count > 0}
/>
</Tooltip>
</Popconfirm>
</>
)}
{!isAdmin() && (
<Text type="secondary">View Only</Text>
)}
</Space>
)
}
@@ -1033,13 +1049,15 @@ const UserManagement = () => {
}}
/>
</div>
<Button
type="primary"
icon={<UserAddOutlined />}
onClick={handleCreateUser}
>
Add User
</Button>
{isAdmin() && (
<Button
type="primary"
icon={<UserAddOutlined />}
onClick={handleCreateUser}
>
Add User
</Button>
)}
</Space>
}
>
@@ -1110,13 +1128,15 @@ const UserManagement = () => {
}}
/>
</div>
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleCreateRole}
>
Add Role
</Button>
{isAdmin() && (
<Button
type="primary"
icon={<PlusOutlined />}
onClick={handleCreateRole}
>
Add Role
</Button>
)}
</Space>
}
>