Add some CI for security testing
This commit is contained in:
parent
b2ed4cc4e5
commit
0a3abf439f
18
.gitea/workflows/datadog-sca.yml
Normal file
18
.gitea/workflows/datadog-sca.yml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
on: [push]
|
||||||
|
|
||||||
|
name: Datadog Software Composition Analysis
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
software-composition-analysis:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Datadog SBOM Generation and Upload
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Check imported libraries are secure and compliant
|
||||||
|
id: datadog-software-composition-analysis
|
||||||
|
uses: DataDog/datadog-sca-github-action@main
|
||||||
|
with:
|
||||||
|
dd_api_key: ${{ secrets.DD_API_KEY }}
|
||||||
|
dd_app_key: ${{ secrets.DD_APP_KEY }}
|
||||||
|
dd_site: datadoghq.com
|
19
.gitea/workflows/datadog-static-analysis.yml
Normal file
19
.gitea/workflows/datadog-static-analysis.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
on: [push]
|
||||||
|
|
||||||
|
name: Datadog Static Analysis
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
static-analysis:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Datadog Static Analyzer
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Check code meets quality and security standards
|
||||||
|
id: datadog-static-analysis
|
||||||
|
uses: DataDog/datadog-static-analyzer-github-action@v1
|
||||||
|
with:
|
||||||
|
dd_api_key: ${{ secrets.DD_API_KEY }}
|
||||||
|
dd_app_key: ${{ secrets.DD_APP_KEY }}
|
||||||
|
dd_site: datadoghq.com
|
||||||
|
cpu_count: 2
|
@ -1,32 +1,39 @@
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from src.config.config import Configuration
|
from src.config.config import Configuration
|
||||||
from src.rendering import GENERIC_FILE_MAPPING
|
from src.rendering import GENERIC_FILE_MAPPING
|
||||||
|
from src.rendering.markdown import render_markdown
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from thumbhash import image_to_thumbhash
|
from thumbhash import image_to_thumbhash
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from datetime import datetime
|
||||||
|
import frontmatter
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ImageMetadata:
|
class ImageMetadata:
|
||||||
path: str
|
|
||||||
width: int
|
width: int
|
||||||
height: int
|
height: int
|
||||||
alt: str
|
alt: str
|
||||||
thumbhash: str
|
|
||||||
# exif attributes
|
|
||||||
exif: dict
|
exif: dict
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MarkdownMetadata:
|
||||||
|
fontmatter: dict
|
||||||
|
content: str
|
||||||
|
preview: str
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FileMetadata:
|
class FileMetadata:
|
||||||
path: str
|
typeMeta: MarkdownMetadata | None
|
||||||
first_hundred_chars: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class TemplateFile:
|
class TemplateFile:
|
||||||
name: str
|
name: str
|
||||||
|
path: str
|
||||||
|
proper_name: str
|
||||||
extension: str
|
extension: str
|
||||||
categories: list[str]
|
categories: list[str]
|
||||||
date_modified: str
|
date_modified: str
|
||||||
@ -37,10 +44,47 @@ class TemplateFile:
|
|||||||
is_dir: bool
|
is_dir: bool
|
||||||
|
|
||||||
|
|
||||||
|
def format_date(timestamp):
|
||||||
|
return datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d")
|
||||||
|
|
||||||
|
|
||||||
class TemplateHelpers:
|
class TemplateHelpers:
|
||||||
def __init__(self, config: Configuration):
|
def __init__(self, config: Configuration):
|
||||||
self.config: Configuration = config
|
self.config: Configuration = config
|
||||||
|
|
||||||
|
|
||||||
|
def build_metadata_for_file(self, path: str, categories: list[str] = []):
|
||||||
|
"""Builds metadata for a file"""
|
||||||
|
file_path = self.config.content_dir / path
|
||||||
|
for k in categories:
|
||||||
|
if k == "image":
|
||||||
|
img = Image.open(file_path)
|
||||||
|
exif = img._getexif()
|
||||||
|
orientation = exif.get(274, 1) if exif else 1
|
||||||
|
width, height = img.width, img.height
|
||||||
|
if orientation in [5, 6, 7, 8]:
|
||||||
|
width, height = height, width
|
||||||
|
return ImageMetadata(
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
alt=file_path.name,
|
||||||
|
exif=img.info,
|
||||||
|
)
|
||||||
|
elif k == "document":
|
||||||
|
ret = None
|
||||||
|
with open(file_path, "r") as fdoc:
|
||||||
|
ret = FileMetadata(None)
|
||||||
|
if file_path.suffix[1:].lower() == "md":
|
||||||
|
ret.typeMeta = MarkdownMetadata({}, "", "")
|
||||||
|
ret.typeMeta.fontmatter = frontmatter.load(file_path)
|
||||||
|
ret.typeMeta.content = render_markdown(file_path)
|
||||||
|
ret.typeMeta.preview = ret.typeMeta.content[:100]
|
||||||
|
if "#" in ret.typeMeta.preview:
|
||||||
|
ret.typeMeta.preview = ret.typeMeta.preview.split("#")[0]
|
||||||
|
return ret
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_folder_contents(self, path: str = ""):
|
def get_folder_contents(self, path: str = ""):
|
||||||
"""Returns the contents of a folder as a list of TemplateFile objects
|
"""Returns the contents of a folder as a list of TemplateFile objects
|
||||||
|
|
||||||
@ -52,45 +96,25 @@ class TemplateHelpers:
|
|||||||
for f in files:
|
for f in files:
|
||||||
t = TemplateFile(
|
t = TemplateFile(
|
||||||
name=f.name,
|
name=f.name,
|
||||||
|
path=str(f.relative_to(self.config.content_dir)),
|
||||||
|
proper_name=f.stem,
|
||||||
extension=f.suffix.lower(),
|
extension=f.suffix.lower(),
|
||||||
categories=[],
|
categories=[],
|
||||||
date_modified=f.stat().st_mtime,
|
date_modified=format_date(f.stat().st_mtime),
|
||||||
date_created=f.stat().st_ctime,
|
date_created=format_date(f.stat().st_ctime),
|
||||||
size_kb=f.stat().st_size / 1024,
|
size_kb=f.stat().st_size / 1024,
|
||||||
metadata=None,
|
metadata=None,
|
||||||
dir_item_count=len(list(f.glob("*"))) if f.is_dir() else 0,
|
dir_item_count=len(list(f.glob("*"))) if f.is_dir() else 0,
|
||||||
is_dir=f.is_dir(),
|
is_dir=f.is_dir(),
|
||||||
)
|
)
|
||||||
if f.is_file():
|
if f.is_file():
|
||||||
# Build metadata depending on the mapping in GENERIC_FILE_MAPPING
|
|
||||||
for k, v in GENERIC_FILE_MAPPING.items():
|
for k, v in GENERIC_FILE_MAPPING.items():
|
||||||
if f.suffix[1:].lower() in v:
|
if f.suffix[1:].lower() in v:
|
||||||
t.categories.append(k)
|
t.categories.append(k)
|
||||||
if k == "image":
|
t.metadata = self.build_metadata_for_file(f, t.categories)
|
||||||
img = Image.open(f)
|
|
||||||
exif = img._getexif()
|
|
||||||
orientation = exif.get(274, 1) if exif else 1
|
|
||||||
width, height = img.width, img.height
|
|
||||||
if orientation in [5, 6, 7, 8]:
|
|
||||||
width, height = height, width
|
|
||||||
t.metadata = ImageMetadata(
|
|
||||||
path=str(f.relative_to(self.config.content_dir)),
|
|
||||||
width=width,
|
|
||||||
height=height,
|
|
||||||
alt=f.name,
|
|
||||||
thumbhash=image_to_thumbhash(img),
|
|
||||||
exif=img.info,
|
|
||||||
)
|
|
||||||
elif k == "document":
|
|
||||||
with open(f, "r") as fdoc:
|
|
||||||
t.metadata = FileMetadata(
|
|
||||||
path=str(f.relative_to(self.config.content_dir)),
|
|
||||||
first_hundred_chars=fdoc.read(100),
|
|
||||||
)
|
|
||||||
ret.append(t)
|
ret.append(t)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def get_sibling_content_files(self, path: str = ""):
|
def get_sibling_content_files(self, path: str = ""):
|
||||||
search_contnet_path = self.config.content_dir / path
|
search_contnet_path = self.config.content_dir / path
|
||||||
files = search_contnet_path.glob("*")
|
files = search_contnet_path.glob("*")
|
||||||
|
@ -43,4 +43,4 @@ def generate_thumbnail(image_path, resize_percent, min_width):
|
|||||||
img.save(thumbnail_io, format=img_format)
|
img.save(thumbnail_io, format=img_format)
|
||||||
thumbnail_io.seek(0)
|
thumbnail_io.seek(0)
|
||||||
|
|
||||||
return (thumbnail_io, img_format)
|
return (thumbnail_io.getvalue(), img_format)
|
@ -1,7 +1,7 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from src.config.config import Configuration
|
from src.config.config import Configuration
|
||||||
from src.rendering.renderer import render_page, render_error_page
|
from src.rendering.renderer import render_page, render_error_page
|
||||||
from flask import send_file
|
from flask import send_file
|
||||||
from src.rendering.image import generate_thumbnail
|
from src.rendering.image import generate_thumbnail
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
|
||||||
@ -10,7 +10,39 @@ class RouteManager:
|
|||||||
def __init__(self, config: Configuration):
|
def __init__(self, config: Configuration):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
|
def _ensure_route(self, path: str):
|
||||||
|
"""
|
||||||
|
Escapes the path for anything like
|
||||||
|
a path execution or injection attack
|
||||||
|
evaluates the path and ensures that it it does not
|
||||||
|
go above the self.content.content_dir
|
||||||
|
If any part of the path contains __, __{foldername}, or __{filename},
|
||||||
|
that is a hidden file or folder and should raise an exception
|
||||||
|
Any illegal path should raise an exception
|
||||||
|
"""
|
||||||
|
file_path: Path = self.config.content_dir / (path if path else "index.md")
|
||||||
|
print(file_path)
|
||||||
|
if file_path < self.config.content_dir:
|
||||||
|
raise Exception("Illegal path")
|
||||||
|
|
||||||
|
for part in file_path.parts:
|
||||||
|
if part.startswith("__"):
|
||||||
|
raise Exception("Illegal path")
|
||||||
|
|
||||||
def default_route(self, path: str):
|
def default_route(self, path: str):
|
||||||
|
try:
|
||||||
|
self._ensure_route(path)
|
||||||
|
print("all good")
|
||||||
|
print(path)
|
||||||
|
print("=============")
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return render_error_page(
|
||||||
|
403,
|
||||||
|
"Forbidden",
|
||||||
|
"You do not have permission to access this resource.",
|
||||||
|
self.config.templates_dir,
|
||||||
|
)
|
||||||
file_path: Path = self.config.content_dir / (path if path else "index.md")
|
file_path: Path = self.config.content_dir / (path if path else "index.md")
|
||||||
return render_page(
|
return render_page(
|
||||||
file_path,
|
file_path,
|
||||||
@ -18,26 +50,38 @@ class RouteManager:
|
|||||||
template_path=self.config.templates_dir,
|
template_path=self.config.templates_dir,
|
||||||
style_path=self.config.styles_dir,
|
style_path=self.config.styles_dir,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_style(self, path: str):
|
def get_style(self, path: str):
|
||||||
file_path: Path = self.config.styles_dir / path
|
file_path: Path = self.config.styles_dir / path
|
||||||
if file_path.exists():
|
if file_path.exists():
|
||||||
return send_file(file_path)
|
return send_file(file_path)
|
||||||
else:
|
else:
|
||||||
return render_error_page(404, "Not Found", "The requested resource was not found on this server.", self.config.templates_dir)
|
return render_error_page(
|
||||||
|
404,
|
||||||
|
"Not Found",
|
||||||
|
"The requested resource was not found on this server.",
|
||||||
|
self.config.templates_dir,
|
||||||
|
)
|
||||||
|
|
||||||
@lru_cache(maxsize=128)
|
@lru_cache(maxsize=None)
|
||||||
def get_static(self, path: str):
|
def get_static(self, path: str):
|
||||||
file_path: Path = self.config.content_dir / path
|
file_path: Path = self.config.content_dir / path
|
||||||
if file_path.exists():
|
if file_path.exists():
|
||||||
# Check to see if the file is an image, if it is, render a thumbnail
|
# Check to see if the file is an image, if it is, render a thumbnail
|
||||||
if file_path.suffix.lower() in [".jpg", ".jpeg", ".png", ".gif"]:
|
if file_path.suffix.lower() in [".jpg", ".jpeg", ".png", ".gif"]:
|
||||||
thumbnail_bytes, img_format = generate_thumbnail(str(file_path), 10, 300)
|
thumbnail_bytes, img_format = generate_thumbnail(
|
||||||
|
str(file_path), 10, 2048
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
thumbnail_bytes.getvalue(),
|
thumbnail_bytes,
|
||||||
200,
|
200,
|
||||||
{"Content-Type": f"image/{img_format.lower()}"},
|
{"Content-Type": f"image/{img_format.lower()}"},
|
||||||
)
|
)
|
||||||
return send_file(file_path)
|
return send_file(file_path)
|
||||||
else:
|
else:
|
||||||
return render_error_page(404, "Not Found", "The requested resource was not found on this server.", self.config.templates_dir)
|
return render_error_page(
|
||||||
|
404,
|
||||||
|
"Not Found",
|
||||||
|
"The requested resource was not found on this server.",
|
||||||
|
self.config.templates_dir,
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user