mirror of
https://github.com/taylanbakircioglu/haproxy-openmanager.git
synced 2026-09-23 02:53:26 +00:00
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:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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"
|
||||
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user