Skip to content

Commit

Permalink
Merge branch 'json_presenter' of https://github.com/multiflexi/Tarani…
Browse files Browse the repository at this point in the history
…s-NG into json_presenter
  • Loading branch information
multiflexi committed Sep 29, 2023
2 parents 85da238 + da1046d commit 959e7c8
Show file tree
Hide file tree
Showing 22 changed files with 594 additions and 192 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ src/.env
*.key
*.log
*.crt
*.asc
local/

# settings of editors
Expand Down
18 changes: 15 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@

repos:
- repo: https://github.com/psf/black
rev: 22.6.0
rev: 23.3.0
hooks:
- id: black
language_version: python3
args: [--line-length=142]

- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
hooks:
- id: flake8
additional_dependencies: [flake8-docstrings]
args: [--max-line-length=142]
types: ['python']

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
9 changes: 7 additions & 2 deletions docker/Dockerfile.publishers
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.7-alpine3.14 AS build_shared
FROM python:3.9-alpine3.17 AS build_shared

WORKDIR /build_shared/

Expand All @@ -8,7 +8,7 @@ RUN python -m build



FROM python:3.7-alpine3.14 AS production
FROM python:3.9-alpine3.17 AS production

WORKDIR /app/

Expand All @@ -24,6 +24,10 @@ RUN pip install --no-cache-dir ./custom_packages/taranis_ng_shared-*.whl && rm -
# install dependencies

COPY ./src/publishers/requirements.txt /app/requirements.txt
RUN apk add --no-cache \
swig\
gnupg

RUN \
apk add --no-cache --virtual .build-deps build-base \
gcc \
Expand All @@ -32,6 +36,7 @@ RUN \
musl-dev \
python3-dev \
libffi-dev \
openssl-dev \
rust && \
pip install --no-cache-dir -r /app/requirements.txt && \
apk --purge del .build-deps
Expand Down
17 changes: 10 additions & 7 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ services:
max-file: "10"

core:
depends_on:
depends_on:
- "redis"
- "database"
restart: unless-stopped
Expand All @@ -46,7 +46,7 @@ services:
HTTPS_PROXY: "${HTTPS_PROXY}"
http_proxy: "${HTTP_PROXY}"
https_proxy: "${HTTPS_PROXY}"
environment:
environment:
REDIS_URL: "redis://redis"
DB_URL: "database"
DB_DATABASE: "taranis-ng"
Expand All @@ -55,6 +55,9 @@ services:
DB_POOL_SIZE: 100
DB_POOL_RECYCLE: 300
DB_POOL_TIMEOUT: 30
TARANIS_NG_AUTHENTICATOR: "${TARANIS_NG_AUTHENTICATOR}"
LDAP_SERVER: "${LDAP_SERVER}"
LDAP_BASE_DN: "${LDAP_BASE_DN}"

JWT_SECRET_KEY: "${JWT_SECRET_KEY}"
OPENID_LOGOUT_URL: ""
Expand Down Expand Up @@ -116,7 +119,7 @@ services:
options:
max-size: "200k"
max-file: "10"

collectors:
depends_on:
core:
Expand Down Expand Up @@ -144,7 +147,7 @@ services:
options:
max-size: "200k"
max-file: "10"

presenters:
depends_on:
core:
Expand Down Expand Up @@ -173,7 +176,7 @@ services:
options:
max-size: "200k"
max-file: "10"

publishers:
depends_on:
core:
Expand Down Expand Up @@ -212,7 +215,7 @@ services:
HTTPS_PROXY: "${HTTPS_PROXY}"
http_proxy: "${HTTP_PROXY}"
https_proxy: "${HTTPS_PROXY}"
# ports:
# ports:
# - "8080:80"
environment:
NGINX_WORKERS: "4"
Expand Down Expand Up @@ -253,7 +256,7 @@ services:
image: "traefik:latest"
environment:
TZ: "${TZ}"
ports:
ports:
- "${TARANIS_NG_HTTP_PORT}:80"
- "${TARANIS_NG_HTTPS_PORT}:443"
- "${TRAEFIK_MANAGEMENT_PORT}:9090"
Expand Down
12 changes: 11 additions & 1 deletion src/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ Keycloak is not needed to run test version of TaranisNG at the moment. You can u
11. In **taranis-ng-core** add environment variable TARANIS_NG_AUTHENTICATOR=openid (just for sign in) or TARANIS_NG_AUTHENTICATOR=keycloak (for identy management)
12. In **taranis-ng-core** add environment variable OPENID_LOGOUT_URL and set it according to your Keycloak installation e.g. http://127.0.0.1:8081/auth/realms/taranisng/protocol/openid-connect/logout?redirect_uri=<GOTO_URL>
13. In **taranis-ng-gui** add these environment variables to activate external login:
VUE_APP_TARANIS_NG_LOGIN_URL=http://127.0.0.1:5000/api/auth/login;VUE_APP_TARANIS_NG_LOGOUT_URL=http://127.0.0.1:5000/api/auth/logout
```
VUE_APP_TARANIS_NG_LOGIN_URL=http://127.0.0.1:5000/api/auth/login
VUE_APP_TARANIS_NG_LOGOUT_URL=http://127.0.0.1:5000/api/auth/logout
```

## Keycloak example of docker-compose.yml for taranis-ng-core:
```
Expand All @@ -50,4 +53,11 @@ TARANIS_NG_KEYCLOAK_CLIENT_SECRET: "XXXXXX"
TARANIS_NG_AUTHENTICATOR: "keycloak"
KEYCLOAK_REALM_NAME: "taranis-ng"
KEYCLOAK_USER_MANAGEMENT: "false"
```
# **LDAP authentication**
If you prefer to authenticate users with LDAP, you need to set environment variables similarly to this:
```
TARANIS_NG_AUTHENTICATOR: "ldap"
LDAP_SERVER: "ldaps://ldap.example.com"
LDAP_BASE_DN: "ou=people,dc=example,dc=com"
```
1 change: 1 addition & 0 deletions src/core/auth/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Place certificate for LDAP authentication in this folder and name it ldap_ca.pem
56 changes: 56 additions & 0 deletions src/core/auth/ldap_authenticator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from managers import log_manager
from auth.base_authenticator import BaseAuthenticator
from flask import request
from ldap3 import Server, Connection, ALL, Tls
import ssl
import time
import random
import os


class LDAPAuthenticator(BaseAuthenticator):
"""Authenticates users against an LDAP server.
Args:
BaseAuthenticator (_type_): _description_
Returns:
_type_: _description_
"""

LDAP_SERVER = os.getenv('LDAP_SERVER')
LDAP_BASE_DN = os.getenv('LDAP_BASE_DN')
LDAP_CA_CERT_PATH = 'auth/ldap_ca.pem'
if not os.path.isfile(LDAP_CA_CERT_PATH):
LDAP_CA_CERT_PATH = None
log_manager.store_auth_error_activity("No LDAP CA certificate found. LDAP authentication might not work.")

def get_required_credentials(self):
"""Gets the username and the password.
Returns:
_type_: _description_
"""
return ["username", "password"]

def authenticate(self, credentials):
"""Tries to authenticate the user against the LDAP server.
Args:
credentials (_type_): _description_
Returns:
_type_: _description_
"""
tls = Tls(ca_certs_file=self.LDAP_CA_CERT_PATH, validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLSv1_2)
server = Server(self.LDAP_SERVER, use_ssl=True, tls=tls, get_info=ALL)
conn = Connection(server, user=f'uid={credentials["username"]},{self.LDAP_BASE_DN}', password=credentials["password"], read_only=True)

if not conn.bind():
data = request.get_json()
data["password"] = log_manager.sensitive_value(data["password"])
log_manager.store_auth_error_activity("Authentication failed for user: " + credentials["username"], request_data=data)
time.sleep(random.uniform(1, 3))
return BaseAuthenticator.generate_error()

return BaseAuthenticator.generate_jwt(credentials["username"])
19 changes: 13 additions & 6 deletions src/core/managers/auth_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from auth.keycloak_authenticator import KeycloakAuthenticator
from auth.openid_authenticator import OpenIDAuthenticator
from auth.password_authenticator import PasswordAuthenticator
from auth.ldap_authenticator import LDAPAuthenticator
from model.collectors_node import CollectorsNode
from model.news_item import NewsItem
from model.osint_source import OSINTSourceGroup
Expand All @@ -37,13 +38,15 @@ def initialize(app):

JWTManager(app)

which = os.getenv('TARANIS_NG_AUTHENTICATOR')
which = os.getenv('TARANIS_NG_AUTHENTICATOR').casefold()
if which == 'openid':
current_authenticator = OpenIDAuthenticator()
elif which == 'keycloak':
current_authenticator = KeycloakAuthenticator()
elif which == 'password':
current_authenticator = PasswordAuthenticator()
elif which == 'ldap':
current_authenticator = LDAPAuthenticator()
else:
current_authenticator = PasswordAuthenticator()

Expand Down Expand Up @@ -139,8 +142,8 @@ def get_user_from_api_key():
user: User object or None
"""
try:
if not request.headers.has_key('Authorization') or not request.headers['Authorization'].__contains__('Bearer '):
return None
if 'Authorization' not in request.headers or not request.headers['Authorization'].__contains__('Bearer '):
return None
key_string = request.headers['Authorization'].replace('Bearer ', '')
api_key = ApiKey.find_by_key(key_string)
if not api_key:
Expand All @@ -151,6 +154,7 @@ def get_user_from_api_key():
log_manager.store_auth_error_activity("Apikey check presence error: " + str(ex))
return None


def get_perm_from_user(user):
"""
Get user permmisions
Expand All @@ -171,6 +175,7 @@ def get_perm_from_user(user):
log_manager.store_auth_error_activity("Get permmision from user error: " + str(ex))
return None


def get_user_from_jwt_token():
"""
Try to authenticate the user by API key
Expand All @@ -197,6 +202,7 @@ def get_user_from_jwt_token():
return None
return user


def get_perm_from_jwt_token(user):
"""
Get user permmisions
Expand All @@ -218,6 +224,7 @@ def get_perm_from_jwt_token(user):
log_manager.store_auth_error_activity("Get permmision from JWT error: " + str(ex))
return None


def auth_required(required_permissions, *acl_args):
def auth_required_wrap(fn):
@wraps(fn)
Expand Down Expand Up @@ -264,7 +271,7 @@ def wrapper(*args, **kwargs):
error = ({'error': 'not authorized'}, 401)

# do we have the authorization header?
if not request.headers.has_key('Authorization'):
if 'Authorization' not in request.headers.has_key:
log_manager.store_auth_error_activity("Missing Authorization header for external access")
return error

Expand Down Expand Up @@ -293,7 +300,7 @@ def wrapper(*args, **kwargs):
error = ({'error': 'not authorized'}, 401)

# do we have the authorization header?
if not request.headers.has_key('Authorization'):
if 'Authorization' not in request.headers:
log_manager.store_auth_error_activity("Missing Authorization header for remote access")
return error

Expand Down Expand Up @@ -358,7 +365,7 @@ def decode_user_from_jwt(jwt_token):
decoded = None
try:
decoded = jwt.decode(jwt_token, os.getenv('JWT_SECRET_KEY'))
except Exception as ex: # e.g. "Signature has expired"
except Exception as ex: # e.g. "Signature has expired"
log_manager.store_auth_error_activity("Invalid JWT: " + str(ex))
if decoded is None:
return None
Expand Down
Loading

0 comments on commit 959e7c8

Please sign in to comment.