diff --git a/.github/workflows/mdbook.yml b/.github/workflows/mdbook.yml index 66bb91b6..60b4857a 100644 --- a/.github/workflows/mdbook.yml +++ b/.github/workflows/mdbook.yml @@ -1,60 +1,123 @@ -name: Deploy mdBook site to Pages +# docs in `main` get built into `/latest`, docs from tags get built into a subdirectory +# matching the tag name (e.g. `/0.1`). Automatically runs when updating the docs, editing +# this workflow, or pushing a new tag. +# +# Does not handle directly publishing to GitHub Pages; just updates the `gh-pages` branch to +# let GitHub build and deploy from that branch. + +name: Update docs with mdbook on: - # Runs on pushes targeting the default branch push: - branches: ["main"] + branches: ['main'] + paths: + - 'docs/**' + - '.github/workflows/mdbook.yml' + tags: + - '[0-9]*' - # Allows you to run this workflow manually from the Actions tab + # Allows you to run this workflow manually from the Actions tab. + # To backfill an old version, pass the tag as the `ref` input so the new + # workflow file runs from main but checks out docs from the specified tag. workflow_dispatch: + inputs: + ref: + description: 'Tag to build and deploy docs from (e.g. 0.1); leave empty for latest main' + required: false + type: string -# Default to read-only; the deploy job grants itself the extra -# permissions it needs to publish to GitHub Pages. +# Write access needed to push to the gh-pages branch permissions: - contents: read + contents: write # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: - group: "pages" + group: 'mdbook' cancel-in-progress: false jobs: - # Build job build: runs-on: ubuntu-latest + if: ${{ github.repository == 'roostorg/osprey' }} env: MDBOOK_VERSION: 0.5.2 steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + # inputs.ref is only set for manual backfills; push-to-main and tag-push events + # default to the triggering ref automatically + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: persist-credentials: false + ref: ${{ inputs.ref }} + + - name: Determine deployment path + id: deploy-path + env: + INPUT_REF: ${{ inputs.ref }} + run: | + # For manual backfill, use the input ref; otherwise derive from the triggering ref + DEPLOY_REF="${INPUT_REF}" + if [[ -z "$DEPLOY_REF" ]]; then + if [[ "${{ github.event_name }}" == "workflow_dispatch" && "$GITHUB_REF_NAME" != "main" ]]; then + echo "Dispatching from a non-main branch requires a ref input (e.g. a tag name)" >&2 + exit 1 + fi + DEPLOY_REF="$GITHUB_REF_NAME" + fi + if [[ "$GITHUB_REF_TYPE" == "tag" || "$DEPLOY_REF" =~ ^[0-9] ]]; then + if [[ "$DEPLOY_REF" =~ [^A-Za-z0-9._-] ]]; then + echo "Refusing unsafe deploy ref: $DEPLOY_REF" >&2 + exit 1 + fi + echo "path=$DEPLOY_REF" >> "$GITHUB_OUTPUT" + else + echo "path=latest" >> "$GITHUB_OUTPUT" + fi + - name: Install mdBook run: | - curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf -y | sh - rustup update - cargo install --version ${MDBOOK_VERSION} mdbook - - name: Setup Pages - id: pages - uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0 + ARCH="x86_64-unknown-linux-gnu" + ARCHIVE="mdbook-v${MDBOOK_VERSION}-${ARCH}.tar.gz" + URL="https://github.com/rust-lang/mdBook/releases/download/v${MDBOOK_VERSION}/${ARCHIVE}" + mkdir -p mdbook-bin + curl -sSL -o mdbook.tar.gz "$URL" + tar -xzf mdbook.tar.gz -C mdbook-bin + rm mdbook.tar.gz + - name: Build with mdBook - run: mdbook build - - name: Upload artifact - uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0 - with: - path: ./book - - # Deployment job - deploy: - permissions: - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: + run: ${{ github.workspace }}/mdbook-bin/mdbook build docs + - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0 + env: + GITHUB_TOKEN: ${{ github.token }} + DEPLOY_PATH: ${{ steps.deploy-path.outputs.path }} + run: | + # Clone the gh-pages branch, or initialize it if this is the first deployment + if git ls-remote --exit-code --heads origin gh-pages > /dev/null 2>&1; then + git clone --depth=1 --single-branch --branch gh-pages \ + "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \ + _gh-pages + else + mkdir _gh-pages + git -C _gh-pages init + git -C _gh-pages checkout --orphan gh-pages + git -C _gh-pages remote add origin \ + "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + fi + + touch _gh-pages/.nojekyll + + # Deploy the versioned docs + rm -rf "_gh-pages/${DEPLOY_PATH}" + cp -r docs/book "_gh-pages/${DEPLOY_PATH}" + + # Regenerate the root version index + (cd _gh-pages && tree -d -L 1 -H . -T "Osprey Documentation" -o index.html --noreport) + + # Commit and push + git -C _gh-pages config user.name "github-actions[bot]" + git -C _gh-pages config user.email "github-actions[bot]@users.noreply.github.com" + git -C _gh-pages add --all + git -C _gh-pages diff --staged --quiet || \ + git -C _gh-pages commit --message "Deploy docs: ${DEPLOY_PATH}" + git -C _gh-pages push origin gh-pages \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 80b2dcb2..dd0ae1e5 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,31 +1,37 @@ -# Osprey Documentation +# Welcome -Welcome to the Osprey documentation! This folder contains important information about contributing to and working with this project. +Welcome to the Osprey documentation! Visit [Osprey on GitHub](https://github.com/roostorg/osprey#readme) for source code and project information. These docs are split into a few guides, depending on who you are and what you're looking for: -## Getting Started +- [User Guide](user/): learn about Osprey, its functionality, and the user interface +- [Development Guide](development/): get Osprey running and learn how to navigate the code -New to the project? We recommend reading the documentation in this order: +See additional topics for reference: -1. **[Code of Conduct](https://github.com/roostorg/.github/blob/main/CODE_OF_CONDUCT.md)** - Understand our community standards -2. **[Contributing Guidelines](https://github.com/roostorg/.github/blob/main/CONTRIBUTING.md)** - Learn how to contribute -3. **[Development Guide](DEVELOPMENT.md)** - Set up your development environment and get started using Osprey -4. **[Osprey UI](UI.md)** - Understand how to use and navigate the UI +- [Writing Rules](rules.md) +- [User Research & Personas](research-personas.md) -## Reporting a Bug or Issue +## Contributing -Found a bug or have a feature request? We'd love to hear from you! When opening an issue, please use our templates: +Osprey is an open source project from [ROOST](https://roost.tools) and the community. We welcome contributions and benefit from diverse perspectives and expertise in building safer online spaces. -* [Bug Report](https://github.com/roostorg/osprey/issues/new?template=bug_report.md) -* [Feature Request](https://github.com/roostorg/osprey/issues/new?template=feature_request.md) -* [Submit an Egg (new tool idea) to ROOST!](https://github.com/roostorg/osprey/issues/new?template=documentation.md) +If you're new to the project, we recommend you: -## Need Help? +- Read the [Code of Conduct](https://github.com/roostorg/.github/blob/main/CODE_OF_CONDUCT.md) to understand our community standards +- Review the [Contributing Guidelines](https://github.com/roostorg/.github/blob/main/CONTRIBUTING.md) for all ROOST projects +- Check out the [project board](https://github.com/orgs/roostorg/projects/13) for open issues, milestones, and prioritization -If you can't find what you're looking for in these documents, please: -- Check the project's main README.md -- Open an issue with your question -- Reach out to the maintainers +Writing code is not the only way to help the project. Reviewing pull requests, answering questions, providing feedback, organizing and teaching tutorials, and improving the documentation are all priceless contributions. ---- +### Report an issue -Thank you for your interest in contributing to our project! +Found a bug or have a feature request? We'd love to hear from you! + +- Search [existing issues](https://github.com/roostorg/osprey/issues) to see if it's already been filed +- Create a [new issue](https://github.com/roostorg/osprey/issues/new) if it's unique + +## Get help + +If you need help with Osprey or want to get in touch, you are always welcome to: + +- [Open a discussion](https://github.com/roostorg/osprey/discussions) with the community +- [Join our Discord server](https://discord.gg/2brrzbqgJF) to chat with contributors, adopters, and other community members diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 5768e342..4e2edea1 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,16 +1,31 @@ # Summary -[Getting Started](README.md) +[Welcome](README.md) -- [Development](DEVELOPMENT.md) +--- + +- [User Guide](user/README.md) + - [Investigate](user/investigate/README.md) + - [Labels](user/investigate/labels.md) + - [Query Syntax](user/investigate/query-syntax.md) + - [Manage](user/manage.md) + - [Operate](user/operate.md) + +--- + +- [Development Guide](development/README.md) - [Workflow](development/workflow.md) - [Tools Overview](development/tools.md) - [Troubleshooting](development/troubleshooting.md) - [IDE Setup](development/ide.md) - [Release Process](development/releases.md) -- [User Interface](UI.md) + +--- + +# Concepts & More + - [Writing Rules](rules.md) -- [User Research & Personas](user_personas.md) +- [User Research & Personas](research-personas.md) --- diff --git a/docs/UI.md b/docs/UI.md deleted file mode 100644 index d3ac41cc..00000000 --- a/docs/UI.md +++ /dev/null @@ -1,281 +0,0 @@ -# Osprey User Interface Guide - -## Getting Started - -Enable Corepack once per machine so `pnpm` is on your PATH (resolves the version pinned in `package.json`): - -```bash -corepack enable -``` - -Then, from `osprey_ui`: - -```bash -cd osprey_ui -pnpm install --frozen-lockfile -pnpm start -``` - -The Osprey UI has several pages accessible by a left-hand menu: - -![Left Side Menu](images/left-side-menu.png) - -Home will bring you to the default page of Osprey, with three main columns. - -![Osprey Home](images/osprey-home.png) - -### Left Column: Query - -#### Query Box - -The Osprey Query UI uses the same SML syntax as rules, but for searching and filtering near-real-time and historical data rather than creating new rules. Using the test data generator, you can try writing a query to look for an action called “create\_post” specifically from a given User ID. - -![Query Box](images/query-box.png) - -You can also use a UDF in your query. If you ever forget what a UDF does, you can hover on the information symbol for a tip: - -![Query UDF Hover](images/query-udf-hover.png) - -A query can be run against a time window ranging from the last second to the last 3 months (and also a custom range): - -![Query Time Range](images/query-time-range.png) - -The Osprey UI is designed to be dynamic and update in real-time. If any other component in the other two columns is interacted with, the query will automatically update and vice versa. The query also automatically populates the URL. This can be handy for sharing a specific query with someone on a team, but may present privacy risks. - -![Query and Charts](images/query-and-charts.png) - -#### History - -Every query is logged in the Query History view, and there is a dropdown filter to only show queries that you have run. - -When you hover over the query, it will also show the Top N Charts used during the query session (more on that below). - -![Query History](images/query-history.png) - -The Query History can also be accessed and seen in a different format via the left-side menu. From here you can filter by the user who ran the query, view the original query, and run it using the same time range the original query used. - -![Query History Page](images/query-history-page.png) - -#### Saved Queries - -If there are specific queries that are used often, Osprey provides the ability to save a query: - -![Save Query History](images/query-history-save.png) - -The user who initiated the query and when the query was first run is logged as part of the Saved Query. Saved Queries can also be accessed via the left-side menu. The user who saved the query and what time it was saved is logged and visible. There is a drop-down menu at the top to filter saved queries by users. - -![Saved Queries Page](images/saved-queries-page.png) - -### Middle Column: Charts - -The middle column in Osprey shows two types of charts: **Time Series** and **Top N Results**. Both sections provide the ability to add extra charts to see different slices of time or types of top results. - -![Charts](images/charts.png) - -#### Time Series Chart - -The Time Series chart shows a visualization of the results in the query over a period of time. The time ranges include: - -* Minute -* Fifteen minutes -* Half hour -* Hour -* Day -* Week -* Month - -Hovering over a bar in the time series chart shows how many events took place during that time. - -![Time Series](images/hover-time-series.png) - -There is also a time and date picker above the time series chart where you can set a custom range: - -![Date Picker](images/time-date-picker.png) - -An extra table can be added for another view of a different unit of time. To get rid of the table, you can “[yeet](https://www.urbandictionary.com/define.php?term=Yeet) it”. - -![Multiple Time Series](images/multiple-time-series.png) - -#### Top N Results - -Adding a Top N Results table populates a table with the top results for the results of the query. You can view and assign labels to a specific entity by hovering over it and clicking “Edit Labels” - -![Top N Charts](images/top-n-charts.png) - -![Add Labels](images/add-labels.png) - -You can also select PoP (Period over Period) to compare the query results with results from a window of time in the past to see the delta. - -![Period Over Period](images/pop.png) - -### Right Column: Event Stream - -The Event Stream is essentially Osprey's "live feed" and investigation dashboard where security teams can: - -* Monitor real-time activity -* Search historical events using SML queries -* Investigate suspicious patterns -* Track rule execution results -* Drill down into specific users/entities - -It provides a more detailed view of each event that matches the query. The Event Stream can show metadata related to accounts that can link to other internal tools that provide detailed information about an account and/or further enforcement actions. -![Event Stream](images/event-stream.png) - -The event stream is also viewable in a card format vs a list format (list format shown in the screenshot). - -Osprey users may have personal preferences on how to do investigations and what information is most helpful for them. Osprey makes it easy to customize the types of information shown in the Event Stream by clicking “Summary Features” -![Summary Features](images/summary-features.png) - -### Labeling - -Any unique entity can be labeled in the Osprey UI. This manual labeling tool is used by Safety teams to tag individual entities (users, IPs, emails, etc.) with labels. Labels are essentially the manual annotation tool that feeds into Osprey's automated rule system, allowing human judgment to enhance machine detection. Labels can be positive, negative, or neutral. Examples: - -**Negative Labels: Harmful/problematic behavior** -* Examples: "spammer", "bot", "banned", "suspicious" - -**Positive Labels: Good/trusted behavior** -* Examples: "verified", "trusted", "premium\_user" - -**Neutral Labels: Informational tags** -* Examples: "new\_user", "from\_mobile", "beta\_tester" - -![Empty Label](images/empty-label.png) -![Complete Label](images/complete-label.png) - -### UDF Documentation - -The UDF Documentation page can be accessed via the left-side menu. It dynamically updates based on the code, so any new UDFs added will show up on this page. This page essentially serves as the "API reference" for the SML language, making it easy for users to discover and properly use all available functions when writing rules and queries. -![UDF Documentation](images/udf-documentation.png) - -This page can be used as a manual for writing SML rules or queries, guide for understanding parameter types and requirements, and act as a plugin discovery portal to explore what custom UDFs are loaded. - -### Bulk Labeling - -There are two ways to bulk label items in Osprey: the left-side menu and via the chart column. In this example, you can bulk label all the users that have posted a message that is not empty: - -![Bulk Label](images/bulk-label.png) - -**Bulk labels can be dangerous if there’s a false positive\!** Osprey provides a counter of how many unique entities are about to be bulk labeled at the top. Labels can be positive, negative, or neutral. A reason must be provided when labeling anything. Each bulk job will create a unique task ID and log the user who initiated the bulk job, the status of the bulk labeling, and a link to the query that the bulk job originated from. - -To view all bulk labeling jobs that have been done, click into “Bulk Job History” from the left-side menu. You’ll need the unique task ID to look up a bulk job. - -![Bulk Job History](images/bulk-job-history.png) - -### Rule Visualizer - -The Rule Visualizer shows how upstream labels, rules, and downstream labels interact with one another. To use it, select an Action or a Label. A graph view will appear showing the relations between rules and labels. - -* **Red circle:** label upstream of a rule -* **Blue square:** rule -* **Green circle:** label downstream of a rule - -![Rule Visualizer](images/rules-visualizer.png) - -### Query Syntax - -#### Actions - -Actions are events that are sent to Osprey. An event is simply something that happens. When a user does something like create a post, send a message, change their username, etc an event happens to represent that. There are probably a lot of events emitted in your org, and Osprey doesn’t need to consume all of them. - -##### Features - -A feature is any variable in the global namespace in Osprey. All features must be uniquely named. However, prefixing a \`\_\` at the start of a variable name prevents it from being exported as a feature and keeps the variable within the local file’s namespace. - -Features are outputs of Osprey executions. Downstream, they are sent to and indexed by Druid, so users can query for events based on feature names later, i.e. \`UserEmail \== '`` despicable@example.com` ``. - -```py -UserId: Entity[int] = EntityJson(type='User', path='$.user.id', coerce_type=True) - -UserEmail: str = JsonData(type='Email', path='$.user.email', required=False) -``` - -In the example above, both `UserId` and `UserEmail` are features. - -##### Entities - -Entities are a special type of Feature. All entities are features, but not all features are entities. An entity can have effects applied to it, such as labels, classifications, or signals). Every entity has a type that determines which effects can be applied to it based on static validations. - -Entities get special treatment within the Osprey UI. Clicking on an entity in the tool will take you to an Entity View, providing a deep dive into its history. - -This could be: - -* User ID -* IP Address -* Post Text -* Internet Service Provider - -#### Effects - -Effects can be triggered when one or many rules are evaluated to be true. These are validated and handled in aggregate at the end of an execution output. For example, an effect might apply a label to an entity marking it as a “Spammer”. - -### Basic Query Structure - -Let’s say you have a data field called “EventType” and one of the events is to “create\_post”. You are looking for posts by a user whose ID is 12345 and you don’t want to see empty posts. Your query would look like: - -```py -# Simple field comparisons -EventType == "create_post" -UserId == 12345 -MessageText != Null -``` - -#### Combining Conditions - -Let’s say you’re looking for any matches where a user tried to login more than 3 times. You can create a query to check for two types of data fields: “EventType” and “LoginAttempts”. - -If you’re looking for multiple types of events in “EventType” like for posts AND messages, you can use brackets to list the types of events. - -```py - # Multiple conditions (AND) - EventType == "user_login" and LoginAttempts >= 3 - - # OR conditions - EventType in ["create_post", "send_message"] - (UserId == 123) or (UserId == 456) -``` - -#### Using UDFs in Queries - -UDFs (read more [here](rules.md#user-defined-functions-udfs)) are a powerful part of queries. Once you define a UDF with the specific desired logic, you can reference it in a query. - -> [!NOTE] -> If you try to query a UDF that doesn’t exist, Osprey will silently fail with a 500 error. - -```py - # Text search - TextContains(text=PostContent, phrase="spam") - RegexMatch(target=MessageText, pattern="(buy|sell|deal)") - - # List operations - ListLength(list=UserConnections) > 10 -``` - -#### Label Queries - -Since the UI searches across actions/events: - -* **Don't use:** HasLabel() - won't work in Query UI -* **Use instead**: DidAddLabel() - shows when an action added a label - -```py - # Find actions that added specific labels - DidAddLabel(entity_type="UserId", label_name="likely_spammer") - DidAddLabel(entity_type="IpAddress", label_name="suspicious") -``` - -### Example Queries - -```py -# Find suspicious login attempts: - EventType == "user_login" and LoginAttempts >= 5 - -# Find posts containing specific words: - EventType == "create_post" and TextContains(text=PostContent, phrase="urgent") - -# Find users who were flagged: - DidAddLabel(entity_type="UserId", label_name="flagged") - -# Complex search: - EventType == "send_message" and - RegexMatch(target=MessageText, pattern="(click|link|urgent)") and - not DidAddLabel(entity_type="UserId", label_name="verified") diff --git a/book.toml b/docs/book.toml similarity index 79% rename from book.toml rename to docs/book.toml index c1b7d25f..c663872b 100644 --- a/book.toml +++ b/docs/book.toml @@ -2,13 +2,14 @@ title = "Osprey Documentation" authors = ["ROOST"] language = "en" -src = "docs" +src = "." [build] -build-dir = "book" +build-dir = "./book" [output.html] -edit-url-template = "https://github.com/roostorg/osprey/edit/main/{path}" +site-url = "/osprey/" +edit-url-template = "https://github.com/roostorg/osprey/edit/main/docs/{path}" git-repository-url = "https://github.com/roostorg/osprey" [output.html.print] diff --git a/docs/DEVELOPMENT.md b/docs/development/README.md similarity index 71% rename from docs/DEVELOPMENT.md rename to docs/development/README.md index e56b06c2..80f2dce4 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/development/README.md @@ -1,6 +1,6 @@ -# Setup Guide +# Development Guide -This guide provides comprehensive instructions for setting up a development environment for Osprey. +Set up a development environment for Osprey. ## Prerequisites @@ -8,7 +8,7 @@ This guide provides comprehensive instructions for setting up a development envi - **[Python](https://www.python.org/) 3.11 or higher** (check with `python --version`) - **[Git](https://git-scm.com/)** for version control - **[uv](https://docs.astral.sh/uv/)** for Python package management -- **[Node.js](https://nodejs.org/en/download/) 22+** for the UI (Corepack ships with Node and auto-resolves pnpm from `osprey_ui/package.json`'s `packageManager` field — no separate pnpm install needed) +- **[Node.js](https://nodejs.org/en/download/) 22+** for the UI (Corepack ships with Node and auto-resolves pnpm from `osprey_ui/package.json`'s `packageManager` field; no separate pnpm install needed) ## Project Setup @@ -133,7 +133,7 @@ Rules are written in SML, some examples are provided in `example_rules/` with YA OSPREY_RULES=./example_rules uv run python3.11 osprey_worker/src/osprey/worker/cli/sinks.py run-rules-sink ``` -[More about rules →](rules.md) +For more about rules, see [Writing Rules](../rules.md). ## Test Data @@ -143,48 +143,3 @@ docker compose --profile test_data up osprey-kafka-test-data-producer -d ``` Produces user login events with timestamps, user IDs, and IP addresses to `osprey.actions_input` topic. - -## Troubleshooting - -### Druid schema not updating - -If Druid is not ingesting data or is stuck on a stale schema after changing rule output structure, reset the Kafka supervisor and resubmit the ingestion spec: - -```bash -curl -X POST http://localhost:8888/druid/indexer/v1/supervisor/osprey.execution_results/terminate -docker compose restart druid-spec-submitter -``` - -To wipe all Druid and MinIO state and start fresh (Postgres data is preserved): - -```bash -docker compose down -docker volume rm osprey_middle_var osprey_historical_var osprey_broker_var osprey_coordinator_var osprey_router_var osprey_druid_shared osprey_minio_data -docker compose up -d -``` - -To wipe everything including Postgres: - -```bash -docker compose down -v && docker compose up -d -``` - -### Test data not appearing in the UI - -The UI defaults to querying the last 24 hours. If the selected time range is too large (weeks or months), Druid scans across many segments and results can be slow or appear empty. - -- Narrow the time range to 1–4 hours centered on when you generated test data -- Click the edit icon next to the displayed time range to switch to a custom date/time picker -- Note that Druid's Kafka consumer uses `auto.offset.reset: latest` — it only picks up events produced after `docker compose up` first ran, so events from before that point will not appear regardless of the time range - -### Kafka topic disk growth - -Topics are created with a 48-hour / 8 GB-per-partition retention limit. If you deployed before this was set, apply the config to your existing topics: - -```bash -for topic in osprey.actions_input osprey.execution_results; do - kafka-configs --bootstrap-server localhost:9092 \ - --entity-type topics --entity-name $topic --alter \ - --add-config retention.ms=172800000,retention.bytes=8589934592,segment.bytes=1073741824 -done -``` diff --git a/docs/development/ide.md b/docs/development/ide.md index 4f1c4e52..554da48e 100644 --- a/docs/development/ide.md +++ b/docs/development/ide.md @@ -1,8 +1,10 @@ -# IDE Setup Recommendations +# IDE Setup + +Use the IDE of your choice to work on Osprey. ## VS Code -Install these extensions for the best development experience: +If using VS Code, we recommend you install these extensions for the best development experience: - **Python** (Microsoft) - **Ruff** (Astral Software) diff --git a/docs/development/tools.md b/docs/development/tools.md index e1f6dd70..037eb5bd 100644 --- a/docs/development/tools.md +++ b/docs/development/tools.md @@ -1,21 +1,21 @@ -# Development Tools Overview +# Development Tools -## Ruff - Linting and Formatting +Familiarize yourself with these development tools to better understand how to work on and debug Osprey. -**Purpose**: Replaces Black, isort, Flake8, and other tools +## Ruff -**Configuration**: Located in `pyproject.toml` under `[tool.ruff]` +Linting and formatting. Replaces Black, isort, Flake8, and other tools. Configuration in `pyproject.toml` under `[tool.ruff]`. -**Key Rules Enabled**: +Key rules enabled: -- `E` - pycodestyle errors -- `F` - pyflakes -- `I` - isort (import sorting) -- `B006` - flake8-bugbear (mutable default arguments) +- `E`: pycodestyle errors +- `F`: pyflakes +- `I`: isort (import sorting) +- `B006`: flake8-bugbear (mutable default arguments) -**Commands**: +Commands: -```bash +```sh # Check for issues uv run ruff check @@ -29,22 +29,20 @@ uv run ruff format uv run ruff check path/to/file.py ``` -## MyPy - Type Checking +## MyPy -**Purpose**: Static type checking for Python +Static type checking for Python. Configuration in `pyproject.toml` under `[tool.mypy]`. -**Configuration**: Located in `pyproject.toml` under `[tool.mypy]` - -**Key Features**: +Key features: - Pydantic plugin support - SQLAlchemy plugin support - Relaxed strict mode (matching legacy codebase) - Ignores protobuf generated files -**Commands**: +Commands: -```bash +```sh # Type check entire project uv run mypy . @@ -58,15 +56,13 @@ uv run mypy osprey_worker/ uv run mypy --show-traceback path/to/file.py ``` -## Pre-commit - Git Hooks - -**Purpose**: Automated quality checks before commits +## Pre-commit -**Configuration**: Located in `.pre-commit-config.yaml` +Git hooks for automated quality checks before committing code. Configuration in `.pre-commit-config.yaml`. -**Commands**: +Commands: -```bash +```sh # Run all hooks on staged files uv run pre-commit run @@ -83,13 +79,11 @@ uv run pre-commit autoupgrade git commit --no-verify ``` -## UV - Package Management - -**Purpose**: Fast Python package manager and environment management +## UV -**Key Commands**: +Python package and environment management. Key commands: -```bash +```sh # Install dependencies uv sync diff --git a/docs/development/troubleshooting.md b/docs/development/troubleshooting.md index 9dccbf9a..e23c2b57 100644 --- a/docs/development/troubleshooting.md +++ b/docs/development/troubleshooting.md @@ -1,31 +1,74 @@ # Troubleshooting -## Common Issues +Solve common issues. -### "uv: command not found" +## Druid schema not updating -**Solution**: Install uv using the installation script or pip: +If Druid is not ingesting data or is stuck on a stale schema after changing rule output structure, reset the Kafka supervisor and resubmit the ingestion spec: + +```bash +curl -X POST http://localhost:8888/druid/indexer/v1/supervisor/osprey.execution_results/terminate +docker compose restart druid-spec-submitter +``` + +To wipe all Druid and MinIO state and start fresh (Postgres data is preserved): + +```bash +docker compose down +docker volume rm osprey_middle_var osprey_historical_var osprey_broker_var osprey_coordinator_var osprey_router_var osprey_druid_shared osprey_minio_data +docker compose up -d +``` + +To wipe everything including Postgres: + +```bash +docker compose down -v && docker compose up -d +``` + +## Test data not appearing in the UI + +The UI defaults to querying the last 24 hours. If the selected time range is too large (weeks or months), Druid scans across many segments and results can be slow or appear empty. + +- Narrow the time range to 1–4 hours centered on when you generated test data +- Click the edit icon next to the displayed time range to switch to a custom date/time picker +- Note that Druid's Kafka consumer uses `auto.offset.reset: latest`; it only picks up events produced after `docker compose up` first ran, so events from before that point will not appear regardless of the time range + +## Kafka topic disk growth + +Topics are created with a 48-hour/8 GB-per-partition retention limit. If you deployed before this was set, apply the config to your existing topics: + +```bash +for topic in osprey.actions_input osprey.execution_results; do + kafka-configs --bootstrap-server localhost:9092 \ + --entity-type topics --entity-name $topic --alter \ + --add-config retention.ms=172800000,retention.bytes=8589934592,segment.bytes=1073741824 +done +``` + +## "uv: command not found" + +Install uv using the installation script or pip: ```bash curl -LsSf https://astral.sh/uv/install.sh | sh # Then restart your terminal ``` -### Pre-commit hooks failing +## Pre-commit hooks failing -**Solution**: Run hooks manually to see detailed errors: +Run hooks manually to see detailed errors: ```bash uv run pre-commit run --all-files ``` -### MyPy errors on protobuf files +## MyPy errors on protobuf files **Solution**: Protobuf generated files are excluded in configuration. If you see errors, check that files match the exclusion patterns in `pyproject.toml`. -### Import errors during type checking +## Import errors during type checking -**Solution**: Ensure all dependencies are installed: +Ensure all dependencies are installed: ```bash uv sync diff --git a/docs/docs.md b/docs/docs.md index c34ad150..c9210557 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -17,7 +17,7 @@ To build the site locally, clone this repository and install `mdbook` (follow th Once installed, use the `mdbook` command-line tool from the root of this repo. For example, to automatically start watching, building, and serving the site: ```shell -mdbook serve +mdbook serve docs ``` Then make your changes, preview them in your web browser (at [http://localhost:3000](http://localhost:3000) by default), commit, push, and open a pull request like any other git project. diff --git a/docs/user_personas.md b/docs/research-personas.md similarity index 100% rename from docs/user_personas.md rename to docs/research-personas.md diff --git a/docs/rules.md b/docs/rules.md index 5a748f39..53e1494d 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -4,37 +4,37 @@ ## Creating Rules -Osprey rules are written in SML (Some Madeup Language) which is a subset of Python with additional restrictions to simplify rule writing. You may write rules that are specific to +Osprey rules are written in SML (“Some Madeup Language”) which is a subset of Python with additional restrictions to simplify rule writing. You may write rules that are specific to single event types on a network, or ones that are applied to multiple event types. -By themselves, rules only create variables, and without a corresponding `WhenRules()` function call, the rule will have no effects outside of evaluation and query functionality. +By themselves, rules only create variables; without a corresponding `WhenRules()` function call, the rule will have no effects outside of evaluation and query functionality. -Rules currently support the following concepts through the `Rule(...)` function of the same name. +Rules currently support the following concepts through the `Rule(...)` function of the same name: -- Name +- **Name** - `Rule_Name = Rule(...)` + `Rule_Name = Rule(...)` - The name of the rule also functions as a conventional "RuleId" and the name of the bool that can be used to query individual rule hits in the Osprey Query UI. As a result, changing the name of a rule after activation may affect historical query results in the UI if not logged externally. + The name of the rule also functions as a conventional "RuleId" and the name of the bool that can be used to query individual rule hits in the Osprey Query UI. As a result, changing the name of a rule after activation may affect historical query results in the UI if not logged externally. -- Logic +- **Logic** - `when_all=[]` + `when_all=[]` - The actual logic that will be used to evaluate Osprey rules is all encompassed as single comma-delimited list of signals within the `when_all` parameter of the `Rule(...)` function and supports the use of Labels, Plugins, UDFs and other values to help enrich heuristics. + The actual logic that will be used to evaluate Osprey rules is all encompassed as single comma-delimited list of signals within the `when_all` parameter of the `Rule(...)` function and supports the use of Labels, Plugins, UDFs and other values to help enrich heuristics. At present, when evaluating UDFs or abstracted variables, any `NULL` evaluations in the series will cause the entire rule function to evaluate as `NULL`, which may be undesirable. -- Description +- **Description** - `description=f''` + `description=f''` - There is an additional string description field that is able to be emitted alongside the rule itself to external systems such as logging and ticketing systems to help enrich work-streams that may benefit from plain-language context on what the rule criteria is and what the rule may intend to do. + There is an additional string description field that is able to be emitted alongside the rule itself to external systems such as logging and ticketing systems to help enrich work-streams that may benefit from plain-language context on what the rule criteria is and what the rule may intend to do. It may be helpful to include dynamic variables as well to help enrich operational workflows that may need to identify specific values related to the trigger criteria. -An example is below of a simple rule using various signal evaluations and out-of-the-box UDFs. +Here's an example of a simple rule using various signal evaluations and out-of-the-box UDFs: ```python My_Rule_Name_v2 = Rule( @@ -303,7 +303,7 @@ UDF outputs can also implement the `CustomExtractedFeature` interface - which ge ## Labels -Labels are a standard plugin that enable stateful rules, and touch many parts of Osprey. They are effectively tags on various entities, which may be arbitrarily defined. +Labels are a standard plugin that enable stateful rules, and touch many parts of Osprey. They are effectively tags on various entities, which may be arbitrarily defined. For more about labels, see [User Guide → Investigate → Labels](user/investigate/labels.md). ### Creating Entities diff --git a/docs/theme/fonts/fonts.css b/docs/theme/fonts/fonts.css new file mode 100644 index 00000000..5180f001 --- /dev/null +++ b/docs/theme/fonts/fonts.css @@ -0,0 +1,19 @@ +@import url('https://fonts.googleapis.com/css2?family=Funnel+Display:wght@300..800&family=Funnel+Sans:ital,wght@0,300..800;1,300..800&display=swap'); + +:root { + font-family: "Funnel Sans", sans-serif; + font-optical-sizing: auto; + font-weight: 400; + font-style: normal; +} + +h1, h2, h3, h4, h5, h6 { + font-family: "Funnel Display", sans-serif; + font-optical-sizing: auto; + font-weight: 700; + font-style: normal; +} + +.content a { + text-decoration: underline; +} diff --git a/docs/user/README.md b/docs/user/README.md new file mode 100644 index 00000000..18f95e10 --- /dev/null +++ b/docs/user/README.md @@ -0,0 +1,25 @@ +# User Guide + +Osprey is a web-based investigation and management console for safety teams. Query event data in real time, visualize trends, label entities, manage rules and features, and run bulk operations. + +Osprey's tools are categorized into three primary functions: + +**Investigate**: query events and examine entity behavior + +- [Query](investigate/#query): the main investigation page +- [Query History](investigate/#query-history): browse and re-run past queries +- [Saved Queries](investigate/#saved-queries): manage frequently used queries + +**Manage**: understand and navigate your rule and feature configuration + +- [Rules Visualizer](manage.md#rules-visualizer): graph view of label/rule relationships +- [UDF Registry](manage.md#udf-registry): API reference for all available functions +- [Features](manage.md#features): inventory of all features in the system +- [Rules](manage.md#rules): inventory of all rules in the system + +**Operate**: run and review bulk operations + +- [Bulk Actions](operate.md#bulk-actions): start and monitor bulk labeling jobs +- [Bulk Job History](operate.md#bulk-job-history): review past bulk jobs and their results + +The sidebar can be collapsed to a narrow icon-only strip using the toggle at the bottom. Its state persists between sessions. diff --git a/docs/user/investigate/README.md b/docs/user/investigate/README.md new file mode 100644 index 00000000..9b25f036 --- /dev/null +++ b/docs/user/investigate/README.md @@ -0,0 +1,96 @@ +# Investigate + +The Investigate section is where most day-to-day work happens. It contains the main query interface, your query history, and saved queries. + +## Query + +The Query page is the home page of Osprey. It's a live investigation workspace with three panels. + +![Osprey Home](../../images/osprey-home.png) + +### Query input + +The left panel is where you write and run queries. Osprey uses SML syntax—the same language used to write rules—to filter and search event data. See [Query Syntax](query-syntax.md) for a full reference. + +![Query Box](../../images/query-box.png) + +As you type, the input offers autocomplete suggestions for feature names, action names, and UDFs. Hovering over any UDF name shows a tooltip with its description. + +![Query UDF Hover](../../images/query-udf-hover.png) + +The active query is reflected in the page URL, making it easy to share a specific investigation with a teammate, though be aware this may expose sensitive query parameters. + +### Time range + +Every query runs against a time window. You can choose a preset interval—from the last second up to the last three months—or set a custom date range using the date picker. + +![Query Time Range](../../images/query-time-range.png) + +The entire page updates dynamically when the query or time range changes, and interacting with any other panel (charts, event stream) can also update the query in turn. + +### Charts + +The center panels show two types of visualizations: + +![Charts](../../images/charts.png) + +**Timeseries** displays how many matching events occurred over time. You can set the granularity—minute, fifteen minutes, half hour, hour, day, week, or month—and hover over individual bars to see the count for that period. + +![Time Series Hover](../../images/hover-time-series.png) + +You can add additional timeseries charts to compare different time granularities side-by-side. Charts you no longer need can be removed ("yeeted"). + +![Multiple Time Series](../../images/multiple-time-series.png) + +**Top N** shows a table of the top results for the current query, grouped by a dimension you choose. You can: +- Add or remove dimension columns +- Adjust the number of results shown (precision) +- Enable **Period over Period (PoP)** to compare current results against a past time window and see the delta +- Export the table as a CSV + +![Top N Charts](../../images/top-n-charts.png) + +![Period over Period](../../images/pop.png) + +### Event stream + +The right panel is Osprey's live feed. It shows individual events matching the current query in near-real time, and can also be used to search historical events. + +![Event Stream](../../images/event-stream.png) + +The stream can be displayed in card format or list format. You can customize which fields appear per event by selecting **Summary Features**, helpful when different team members care about different metadata. + +![Summary Features](../../images/summary-features.png) + +Selecting an entity in the event stream (such as a user ID or IP address) opens the [Entity Details](labels.md#entity-details) view. Hold Ctrl (⌘ Cmd on macOS) while clicking to select multiple events for bulk labeling. + +Selecting an event opens a detail view at `/events/:eventId` showing all extracted feature values for that specific event. + +## Query History + +Every query you run is automatically saved to your history. + +![Query History](../../images/query-history.png) + +Hovering over a query in the sidebar shows the Top N dimensions that were active during that session. + +The full Query History page (accessible from the sidebar) shows a searchable list of all queries run across your team. You can filter by user email, view the original query text, and re-run any past query using the same time range it was originally run with. + +![Query History Page](../../images/query-history-page.png) + +## Saved Queries + +For queries you return to frequently, Osprey lets you save them by name. + +![Save Query](../../images/query-history-save.png) + +The Saved Queries page (accessible from the sidebar) shows a grid of all saved queries with the query text, the user who saved it, and when it was saved. You can filter by user email. + +![Saved Queries Page](../../images/saved-queries-page.png) + +From the grid, you can: +- **Run** a saved query to load it into the Query page +- **Rename** it via an edit modal +- **Delete** it (with a confirmation step) + +Saved queries also have a direct URL: `/saved-query/:savedQueryId/latest` automatically loads and executes the query. diff --git a/docs/user/investigate/labels.md b/docs/user/investigate/labels.md new file mode 100644 index 00000000..574e2764 --- /dev/null +++ b/docs/user/investigate/labels.md @@ -0,0 +1,37 @@ +# Labels + +Labels are annotations you apply to entities: users, IP addresses, emails, and other tracked objects. They're the bridge between human judgment and Osprey's automated rule system; a label you apply manually can feed into rules that act on future events automatically. + +Labels have three polarities: + +- **Negative**: harmful or problematic (e.g. `spammer`, `bot`, `banned`, `suspicious`) +- **Positive**: trusted or verified (e.g. `verified`, `trusted`, `premium_user`) +- **Neutral**: informational (e.g. `new_user`, `from_mobile`, `beta_tester`) + +A reason is required whenever you apply a label. + +## Entity Details + +Selecting an entity anywhere in the UI navigates to an entity view showing every label that has ever been applied to that entity, grouped by label name. + +![User Entity](../../images/osprey-user-entity.png) + +Each label entry shows: +- The label value and type +- Whether it was applied by a rule (automated), manually, or via a bulk action +- The reason provided +- Who applied it and when + +## Add manually + +From a Top N table, hover over an entity row and select **Edit Labels**: + +![Add Labels](../../images/add-labels.png) + +From the event stream, select any entity to open its label drawer. + +![Empty Label](../../images/empty-label.png) + +![Complete Label](../../images/complete-label.png) + +For more about how labels are used, see [Writing Rules → Labels](../../rules.md#labels). diff --git a/docs/user/investigate/query-syntax.md b/docs/user/investigate/query-syntax.md new file mode 100644 index 00000000..9aca6cd2 --- /dev/null +++ b/docs/user/investigate/query-syntax.md @@ -0,0 +1,85 @@ +# Query Syntax + +Osprey uses SML (“Some Madeup Language,” a subset of Python with additional restrictions) for queries. Queries filter events by matching against features, actions, and labels. + +## Core concepts + +**Actions** are events that happen in your system, like a user creating a post or sending a message. Your query filters which action types to look at. + +**Features** are named variables extracted from events. All features are in a global namespace; any feature exported by Osprey rules is queryable. Prefixing a variable with `_` keeps it local to a rule file and excludes it from querying. + +```py +UserId: Entity[int] = EntityJson(type='User', path='$.user.id', coerce_type=True) +UserEmail: str = JsonData(type='Email', path='$.user.email', required=False) +``` + +Both `UserId` and `UserEmail` above are features. + +**Entities** are a special kind of feature. They can have effects applied to them: labels, classifications, signals. Clicking an entity in the UI navigates to its [Entity Details](labels.md#entity-details) view. + +**Effects** are outcomes triggered when one or more rules evaluate to true; for example, applying a `spammer` label to a `UserId` entity. + +## Basic comparisons + +```py +EventType == "create_post" +UserId == 12345 +MessageText != Null +``` + +## Combining conditions + +```py +# AND: all conditions must match +EventType == "user_login" and LoginAttempts >= 3 + +# OR: any condition may match +(UserId == 123) or (UserId == 456) + +# in: match any value in a list +EventType in ["create_post", "send_message"] +``` + +## Using UDFs + +UDFs (see [UDF Registry](../manage.md#udf-registry)) extend what you can express in a query: + +```py +# Text search +TextContains(text=PostContent, phrase="spam") +RegexMatch(target=MessageText, pattern="(buy|sell|deal)") + +# List operations +ListLength(list=UserConnections) > 10 +``` + +> [!NOTE] +> If you query a UDF that doesn't exist, Osprey will silently fail with a 500 error. Use the UDF Registry to confirm a function name before using it. + +## Label queries + +The query UI searches across actions (events), not entity state, so `HasLabel()` won't work here. Use `DidAddLabel()` instead, which matches events where a label was added: + +```py +# Find events that added a specific label +DidAddLabel(entity_type="UserId", label_name="likely_spammer") +DidAddLabel(entity_type="IpAddress", label_name="suspicious") +``` + +## Example queries + +```py +# Suspicious login attempts +EventType == "user_login" and LoginAttempts >= 5 + +# Posts containing specific words +EventType == "create_post" and TextContains(text=PostContent, phrase="urgent") + +# Users who were flagged +DidAddLabel(entity_type="UserId", label_name="flagged") + +# Complex: messages matching a pattern, from users without a verified label +EventType == "send_message" and +RegexMatch(target=MessageText, pattern="(click|link|urgent)") and +not DidAddLabel(entity_type="UserId", label_name="verified") +``` diff --git a/docs/user/manage.md b/docs/user/manage.md new file mode 100644 index 00000000..36ed4225 --- /dev/null +++ b/docs/user/manage.md @@ -0,0 +1,58 @@ +# Manage + +The Manage section provides visibility into your Osprey configuration: the rules and features that power your detection, the functions available to query and rule authors, and tools to understand how everything connects. + +## Rules Visualizer + +The Rules Visualizer shows how labels, rules, and other labels relate to one another in a dependency graph. It's useful for understanding what will fire when a particular label is applied, or what conditions must be true for a label to be produced. + +![Rules Visualizer](../images/rules-visualizer.png) + +To use it, select a feature or label from the search interface. A graph appears showing the upstream and downstream relationships for your selection. You can toggle upstream and downstream visibility independently. + +Node types in the graph: +- **Red ellipse**: a label that is upstream of a rule (an input condition) +- **Blue rectangle**: a rule +- **Green ellipse**: a label that is downstream of a rule (an output) + +Hovering over a node shows its source file path. The graph supports zoom and pan to navigate large dependency trees. + +## UDF Registry + +The UDF Registry is an auto-generated API reference for every user-defined function (UDF) available in Osprey. It updates dynamically as UDFs are added or modified in code, so it always reflects what's actually available. + +![UDF Documentation](../images/udf-documentation.png) + +UDFs are organized by category and are searchable. Each entry shows: +- Function signature with syntax highlighting +- Description of what the function does +- Parameter names, types, and descriptions +- Return type + +Use this page as your reference when writing queries or rules: especially to confirm a function's exact name and parameter order before using it. (Querying a UDF that doesn't exist causes a silent 500 error.) + +## Features + +The Features page lists every feature defined in your Osprey deployment. Features are named variables extracted from events; they're what you query against and what rules operate on. + +The list is paginated (50 per page) and can be filtered and sorted: + +- **Search**: filter by name, category, or description +- **Category filter**: narrow to a specific feature category +- **Extraction function filter**: narrow to features using a specific extraction function +- **Unused only**: show only features not referenced by any rule +- **Sort**: by name, most referenced, or least referenced + +Each row shows the feature's name, category, extraction function(s), reference count (how many rules use it), description, owner, and last modified date. + +## Rules + +The Rules page lists every rule loaded in your Osprey deployment. + +The list is paginated (50 per page) and can be filtered and sorted: + +- **Search**: filter by name, source file, or description +- **Unused only**: hide rules that are referenced by other (when-)rules, showing only "leaf" rules +- **Sort**: by name, most referenced, or least referenced + +Each row shows the rule's name, source file, description, reference count, and line number within the source file. diff --git a/docs/user/operate.md b/docs/user/operate.md new file mode 100644 index 00000000..73884b32 --- /dev/null +++ b/docs/user/operate.md @@ -0,0 +1,44 @@ +# Operate + +The Operate section contains tools for running bulk labeling jobs and reviewing their results. These operations can affect a large number of entities at once, so Osprey includes safeguards to help prevent unintended impact. + +## Bulk Actions + +Bulk Actions let you apply a label to every entity matching the current query; useful when you've identified a clear pattern and want to act on many entities at once. + +> [!WARNING] +> A false positive in a bulk action can label a large number of entities incorrectly. **Always review the entity count before confirming a job.** + +### Starting a bulk action + +There are two ways to start a bulk action: + +1. **Bulk Actions** → **Create New Job** to open the job creation modal +2. **From the Query page**: use the bulk label drawer in the chart column, which pre-fills the query from your current session + +![Bulk Label](../images/bulk-label.png) + +Before submitting, Osprey shows a count of how many unique entities will be labeled. Review this number carefully to understand the impact of your bulk action. + +Each job requires: +- The entity type and label to apply +- A reason (required for all labeling operations) +- The label polarity (negative, positive, or neutral) + +### Monitoring jobs + +The Bulk Actions page shows a table of all jobs with their ID, status, progress percentage, the user who created them, and when they were created. Jobs that are still running show a cancel button. + +The page polls for updates automatically; you don't need to refresh to see progress. + +## Bulk Job History + +The Bulk Job History page provides a detailed view of past bulk jobs. + +![Bulk Job History](../images/bulk-job-history.png) + +The page has two columns: +- Recent bulk jobs with status, progress, and summary statistics; filterable by task ID +- Detailed view of a single job, including its full results and logs + +To look up a specific job, enter its task ID in the search field. Each bulk job has a unique ID that's logged when the job is created.