Skip to content

Commit

Permalink
Merge branch 'main' into feat-graylog-provider
Browse files Browse the repository at this point in the history
  • Loading branch information
shahargl authored Oct 29, 2024
2 parents 1db891e + d101552 commit fc78ba5
Show file tree
Hide file tree
Showing 11 changed files with 141 additions and 53 deletions.
5 changes: 3 additions & 2 deletions docs/deployment/authentication/keycloak-auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ To start Keep with Keycloak authentication, set the following environment variab
| AUTH_TYPE | Set to 'KEYCLOAK' for Keycloak authentication | Yes | - |
| KEYCLOAK_ID | Your Keycloak client ID (e.g. keep) | Yes | - |
| KEYCLOAK_ISSUER | Full URL to Your Keycloak issuer URL e.g. http://localhost:8181/auth/realms/keep | Yes | - |
| KEYCLOAK_SECRET | Your Keycloak client secret | Yes | keep-keycloak-secret |

#### Backend Environment Variables

Expand All @@ -50,5 +51,5 @@ To start Keep with Keycloak authentication, set the following environment variab
### Example configuration

To get a better understanding on how to use Keep together with Keycloak, you can:
- See [Keycloak](https://github.com/keephq/keep/tree/main/tests) directory for configuration, realm.json, etc
- See Keep + Keycloak [docker-compose example](https://github.com/keephq/keep/blob/main/keycloak/docker-compose.yml)
- See [Keycloak](https://github.com/keephq/keep/tree/main/keycloak) directory for configuration, realm.json, etc
- See Keep + Keycloak [docker-compose example](https://github.com/keephq/keep/blob/main/keycloak/docker-compose.yaml)
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import logging

from fastapi import Depends, HTTPException

Expand All @@ -8,6 +9,8 @@
from keycloak.keycloak_uma import KeycloakUMA
from keycloak.uma_permissions import UMAPermission

logger = logging.getLogger(__name__)


class KeycloakAuthVerifier(AuthVerifierBase):
"""Handles authentication and authorization for Keycloak"""
Expand Down Expand Up @@ -55,6 +58,10 @@ def _verify_bearer_token(
email = payload.get("preferred_username")
org_id = payload.get("active_organization", {}).get("id")
org_realm = payload.get("active_organization", {}).get("name")
if org_id is None or org_realm is None:
logger.warning(
"Invalid Keycloak configuration - no org information for user. Check organization mapper: https://github.com/keephq/keep/blob/main/keycloak/keep-realm.json#L93"
)
role = (
payload.get("resource_access", {})
.get(self.keycloak_client_id, {})
Expand Down
51 changes: 30 additions & 21 deletions keep-ui/components/navbar/DashboardLink.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useSortable } from '@dnd-kit/sortable';
import { useSortable } from "@dnd-kit/sortable";
import { Subtitle } from "@tremor/react";
import { FiLayout } from "react-icons/fi";
import { LinkWithIcon } from "components/LinkWithIcon"; // Ensure you import this correctly
import classNames from 'classnames';
import { clsx } from "clsx";

interface Dashboard {
id: string;
Expand All @@ -14,31 +14,40 @@ type DashboardLinkProps = {
dashboard: Dashboard;
pathname: string | null;
deleteDashboard: (id: string) => void;
titleClassName?: string;
};

export const DashboardLink = ({ dashboard, pathname, deleteDashboard }: DashboardLinkProps) => {
export const DashboardLink = ({
dashboard,
pathname,
deleteDashboard,
titleClassName,
}: DashboardLinkProps) => {
const href = `/dashboard/${dashboard.dashboard_name}`;
const isActive = decodeURIComponent(pathname|| "") === href;

const { isDragging } =
useSortable({
id: dashboard.id,
});
const isActive = decodeURIComponent(pathname || "") === href;

const { isDragging } = useSortable({
id: dashboard.id,
});

return (
<LinkWithIcon
href={href}
icon={FiLayout}
isDeletable={true}
onDelete={() => deleteDashboard(dashboard.id)}
>
<Subtitle className={classNames({
"text-orange-400": isActive,
"pointer-events-none cursor-auto": isDragging,
})}>
{dashboard.dashboard_name}
</Subtitle>
</LinkWithIcon>
href={href}
icon={FiLayout}
isDeletable={true}
onDelete={() => deleteDashboard(dashboard.id)}
>
<Subtitle
className={clsx(
{
"text-orange-400": isActive,
"pointer-events-none cursor-auto": isDragging,
},
titleClassName
)}
>
{dashboard.dashboard_name}
</Subtitle>
</LinkWithIcon>
);
};
5 changes: 3 additions & 2 deletions keep-ui/components/navbar/DashboardLinks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DashboardLink } from "./DashboardLink";
import { Subtitle, Button, Badge, Text } from "@tremor/react";
import { Disclosure } from "@headlessui/react";
import { IoChevronUp } from "react-icons/io5";
import classNames from "classnames";
import clsx from "clsx";
import { useDashboards } from "utils/hooks/useDashboards";
import { useApiUrl } from "utils/hooks/useConfig";

Expand Down Expand Up @@ -105,7 +105,7 @@ export const DashboardLinks = ({ session }: DashboardProps) => {
Beta
</Badge>
<IoChevronUp
className={classNames(
className={clsx(
{ "rotate-180": open },
"mr-2 text-slate-400"
)}
Expand All @@ -132,6 +132,7 @@ export const DashboardLinks = ({ session }: DashboardProps) => {
dashboard={dashboard}
pathname={pathname}
deleteDashboard={deleteDashboard}
titleClassName="max-w-[150px] overflow-hidden overflow-ellipsis"
/>
))
) : (
Expand Down
30 changes: 23 additions & 7 deletions keep/api/core/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -2950,7 +2950,9 @@ def get_all_same_alert_ids(


def get_alerts_data_for_incident(
alert_ids: List[str | UUID], session: Optional[Session] = None
alert_ids: List[str | UUID],
existed_fingerprints: Optional[List[str]] = None,
session: Optional[Session] = None
) -> dict:
"""
Function to prepare aggregated data for incidents from the given list of alert_ids
Expand All @@ -2962,12 +2964,14 @@ def get_alerts_data_for_incident(
Returns: dict {sources: list[str], services: list[str], count: int}
"""
existed_fingerprints = existed_fingerprints or []

with existed_or_new_session(session) as session:

fields = (
get_json_extract_field(session, Alert.event, "service"),
Alert.provider_type,
Alert.fingerprint,
get_json_extract_field(session, Alert.event, "severity"),
)

Expand All @@ -2980,8 +2984,9 @@ def get_alerts_data_for_incident(
sources = []
services = []
severities = []
fingerprints = set()

for service, source, severity in alerts_data:
for service, source, fingerprint, severity in alerts_data:
if source:
sources.append(source)
if service:
Expand All @@ -2991,12 +2996,14 @@ def get_alerts_data_for_incident(
severities.append(IncidentSeverity.from_number(severity))
else:
severities.append(IncidentSeverity(severity))
if fingerprint and fingerprint not in existed_fingerprints:
fingerprints.add(fingerprint)

return {
"sources": set(sources),
"services": set(services),
"max_severity": max(severities),
"count": len(alerts_data),
"count": len(fingerprints),
}


Expand Down Expand Up @@ -3047,6 +3054,17 @@ def add_alerts_to_incident(
)
).all()
)
existing_fingerprints = set(
session.exec(
select(Alert.fingerprint)
.join(AlertToIncident, AlertToIncident.alert_id == Alert.id)
.where(
AlertToIncident.deleted_at == NULL_FOR_DELETED_AT,
AlertToIncident.tenant_id == tenant_id,
AlertToIncident.incident_id == incident.id,
)
).all()
)

new_alert_ids = [
alert_id for alert_id in all_alert_ids if alert_id not in existing_alert_ids
Expand All @@ -3055,9 +3073,7 @@ def add_alerts_to_incident(
if not new_alert_ids:
return incident

alerts_data_for_incident = get_alerts_data_for_incident(
new_alert_ids, session
)
alerts_data_for_incident = get_alerts_data_for_incident(new_alert_ids, existing_fingerprints, session)

incident.sources = list(
set(incident.sources if incident.sources else []) | set(alerts_data_for_incident["sources"])
Expand Down Expand Up @@ -3177,7 +3193,7 @@ def remove_alerts_to_incident_by_incident_id(
session.commit()

# Getting aggregated data for incidents for alerts which just was removed
alerts_data_for_incident = get_alerts_data_for_incident(all_alert_ids, session)
alerts_data_for_incident = get_alerts_data_for_incident(all_alert_ids, session=session)

service_field = get_json_extract_field(session, Alert.event, "service")

Expand Down
5 changes: 4 additions & 1 deletion keep/api/tasks/process_event_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,10 @@ def process_event(
and isinstance(event, dict)
or isinstance(event, FormData)
):
provider_class = ProvidersFactory.get_provider_class(provider_type)
try:
provider_class = ProvidersFactory.get_provider_class(provider_type)
except Exception:
provider_class = ProvidersFactory.get_provider_class("keep")
event = provider_class.format_alert(
tenant_id=tenant_id,
event=event,
Expand Down
4 changes: 2 additions & 2 deletions keycloak/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: '3.9'
version: "3.9"

services:
mysql:
Expand All @@ -20,7 +20,7 @@ services:
- keycloak-and-mysql-network

keycloak:
image: quay.io/phasetwo/phasetwo-keycloak:latest
image: us-central1-docker.pkg.dev/keephq/keep/keep-keycloak
ports:
- 8181:8080
restart: unless-stopped
Expand Down
4 changes: 2 additions & 2 deletions keycloak/keycloak_entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ fi

# Start Keycloak in the background
echo "Starting Keycloak"
/opt/keycloak/bin/kc.sh start-dev --features=preview --import-realm -Dkeycloak.profile.feature.scripts=enabled -Dkeycloak.migration.strategy=OVERWRITE_EXISTIN &
/opt/keycloak/bin/kc.sh start-dev --log-level=DEBUG --features=preview --import-realm -Dkeycloak.profile.feature.scripts=enabled -Dkeycloak.migration.strategy=OVERWRITE_EXISTIN &
echo "Keycloak started"
# Try to connect to Keycloak - wait until Keycloak is ready or timeout
echo "Waiting for Keycloak to be ready"
Expand Down Expand Up @@ -62,7 +62,7 @@ echo "Event listener 'last_login' configured"
# Configure Content-Security-Policy and X-Frame-Options
# So that the SSO connect works with the Keep UI
echo "Configuring Content-Security-Policy and X-Frame-Options"
/opt/keycloak/bin/kcadm.sh update realms/${KEEP_REALM} -s 'browserSecurityHeaders.contentSecurityPolicy="frame-src '\''self'\'' '"$KEEP_URL"'; frame-ancestors '\''self'\'' '"$KEEP_URL"'; object-src '\''none'\'';"'
/opt/keycloak/bin/kcadm.sh update realms/${KEEP_REALM} -s 'browserSecurityHeaders.contentSecurityPolicy="frame-src '\''self'\'' '${KEEP_URL}'; frame-ancestors '\''self'\'' '${KEEP_URL}'; object-src '\''none'\'';"'
/opt/keycloak/bin/kcadm.sh update realms/${KEEP_REALM} -s 'browserSecurityHeaders.xFrameOptions="ALLOW"'
echo "Content-Security-Policy and X-Frame-Options configured"

Expand Down
24 changes: 20 additions & 4 deletions keycloak/readme.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@

# Docker-compose example:
```
docker-compose -f keycloak/docker-compose.yaml up
```
Keycloak: http://localhost:8181/auth/ (keep_kc:keep_kc)

Keep login page: http://localhost:3000/

## For Azure:
Instructions:
1. https://rahulroyz.medium.com/using-keycloak-as-idp-for-azure-ad-sso-authentication-role-authorization-0b309c15eadc
2. https://rahulroyz.medium.com/using-keycloak-as-idp-for-azure-ad-role-authorization-part-2-map-ad-groups-to-keycloak-roles-9850d4acd536

Set email, first name & last name for keep_admin user: http://localhost:8181/auth/admin/master/console/#/keep/users
Also please assign admin role for keep_admin.

# Development

```
docker run --name phasetwo_test --rm -p 8181:8080 \
-e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/phasetwo/phasetwo-keycloak:latest \
start-dev


```
```
http://localhost:8181/realms/keep/portal/
http://localhost:8181/realms/keep/portal/

https://euc1.auth.ac/auth/realms/keep/portal

```

# delete realm to refresh
1. delete the realm from the UI
Expand Down
Binary file modified keycloak/themes/keep.jar
Binary file not shown.
Loading

0 comments on commit fc78ba5

Please sign in to comment.