Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Docker setup, follow best-practices in containerization and make caldera easier to deploy #3114

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,23 @@ tests/
**/release.sh
**/requirements-dev.txt
**/tox.ini

# Artifacts from previous use/compilation
**/__pycache__/
**/node_modules/
**/plugins/magma/dist/
**/plugins/atomic/data/atomic-red-team
**/plugins/emu/data/adversary-emulation-plans
**/plugins/emu/payloads/*

# Artifacts from previous caldera use
data/*_store
data/abilities/*
data/adversaries
data/results/*
data/payloads/*
data/facts/*
data/sources/*
data/objectives/*
data/backup/*
data/planners/*
32 changes: 27 additions & 5 deletions .github/workflows/publish_docker_image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,38 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
- name: Extract metadata (tags, labels) for Docker (slim variant)
id: meta-slim
uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=auto
prefix=slim-,onlatest=true
suffix=

- name: Build and push Docker image (slim)
uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825
with:
context: .
push: true
tags: ${{ steps.meta-slim.outputs.tags }}
labels: ${{ steps.meta-slim.outputs.labels }}
build-args: |
VARIANT=slim

- name: Extract metadata (tags, labels) for Docker (full variant)
id: meta-full
uses: docker/metadata-action@818d4b7b91585d195f67373fd9cb0332e31a7175
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build and push Docker image
- name: Build and push Docker image (full)
uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
tags: ${{ steps.meta-full.outputs.tags }}
labels: ${{ steps.meta-full.outputs.labels }}
build-args: |
VARIANT=full
145 changes: 71 additions & 74 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,95 +1,92 @@
FROM ubuntu:23.04
SHELL ["/bin/bash", "-c"]

ARG TZ="UTC"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone
# This file uses a staged build, using a different stage to build the UI (magma)
# Build the UI
FROM node:23 as ui-build

WORKDIR /usr/src/app

# Make sure user cloned caldera recursively before installing anything.
ADD . .
RUN if [ -z "$(ls plugins/stockpile)" ]; then echo "stockpile plugin not downloaded - please ensure you recursively cloned the caldera git repository and try again."; exit 1; fi

RUN apt-get update && \
apt-get -y install python3 python3-pip python3-venv git curl golang-go


#WIN_BUILD is used to enable windows build in sandcat plugin
ARG WIN_BUILD=false
RUN if [ "$WIN_BUILD" = "true" ] ; then apt-get -y install mingw-w64; fi

# Set up python virtualenv
ENV VIRTUAL_ENV=/opt/venv/caldera
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

# Install pip requirements
RUN pip3 install --no-cache-dir -r requirements.txt

# Set up config file and disable atomic by default
RUN python3 -c "import app; import app.utility.config_generator; app.utility.config_generator.ensure_local_config();"; \
sed -i '/\- atomic/d' conf/local.yml;
# Build VueJS front-end
RUN (cd plugins/magma; npm install && npm run build)
# This stage contains all dependencies for

FROM debian:bookworm-slim as runtime
# There are two variants - slim and full
# The slim variant excludes some dependencies of *emu* and *slim* that can be downloaded on-demand if needed
# They are very large
ARG VARIANT=full
ENV VARIANT=${VARIANT}

# Display an error if variant is set incorrectly, otherwise just print information regarding which variant is in use
RUN if [ "$VARIANT" = "full" ]; then \
echo "Building \"full\" container suitable for offline use!"; \
elif [ "$VARIANT" = "slim" ]; then \
echo "Building slim container - some plugins (emu, atomic) may not be available without an internet connection!"; \
else \
echo "Invalid Docker build-arg!"; \
exit 1; \
fi

# Compile default sandcat agent binaries, which will download basic golang dependencies.
WORKDIR /usr/src/app

# Install Go dependencies
WORKDIR /usr/src/app/plugins/sandcat/gocat
RUN go mod tidy && go mod download
# Copy in source code and compiled UI
# IMPORTANT NOTE: the .dockerignore file is very important in preventing weird issues.
# Especially if caldera was ever compiled outside of Docker - we don't want those files to interfere with this build process,
# which should be repeatable.
ADD . .
COPY --from=ui-build /usr/src/app/plugins/magma/dist /usr/src/app/plugins/magma/dist

WORKDIR /usr/src/app/plugins/sandcat
# From https://docs.docker.com/build/building/best-practices/
# Install caldera dependencies
RUN apt-get update && \
apt-get --no-install-recommends -y install git curl unzip python3-dev python3-pip golang-go mingw-w64 zlib1g gcc && \
rm -rf /var/lib/apt/lists/*

# Fix line ending error that can be caused by cloning the project in a Windows environment
RUN if [ "$WIN_BUILD" = "true" ] ; then cp ./update-agents.sh ./update-agents-copy.sh; fi
RUN if [ "$WIN_BUILD" = "true" ] ; then tr -d '\15\32' < ./update-agents-copy.sh > ./update-agents.sh; fi
RUN if [ "$WIN_BUILD" = "true" ] ; then rm ./update-agents-copy.sh; fi

RUN ./update-agents.sh

# Check if we can compile the sandcat extensions, which will download golang dependencies for agent extensions
RUN mkdir /tmp/gocatextensionstest

RUN cp -R ./gocat /tmp/gocatextensionstest/gocat
RUN cp -R ./gocat-extensions/* /tmp/gocatextensionstest/gocat/
RUN cd /usr/src/app/plugins/sandcat; tr -d '\15\32' < ./update-agents.sh > ./update-agents.sh

RUN cp ./update-agents.sh /tmp/gocatextensionstest/update-agents.sh

WORKDIR /tmp/gocatextensionstest

RUN mkdir /tmp/gocatextensionstest/payloads
# Set timezone (default to UTC)
ARG TZ="UTC"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
echo $TZ > /etc/timezone

RUN ./update-agents.sh
# Install pip requirements
RUN pip3 install --break-system-packages --no-cache-dir -r requirements.txt

# Set up config file
RUN python3 -c "import app; import app.utility.config_generator; app.utility.config_generator.ensure_local_config();"

# For offline atomic (disable it by default in slim image)
# Disable atomic if this is not downloaded
RUN if [ ! -d "/usr/src/app/plugins/atomic/data/atomic-red-team" ] && [ "$VARIANT" = "full" ]; then \
git clone --depth 1 https://github.com/redcanaryco/atomic-red-team.git \
/usr/src/app/plugins/atomic/data/atomic-red-team; \
else \
sed -i '/\- atomic/d' conf/local.yml; \
fi

# Clone atomic red team repo for the atomic plugin
RUN if [ ! -d "/usr/src/app/plugins/atomic/data/atomic-red-team" ]; then \
git clone --depth 1 https://github.com/redcanaryco/atomic-red-team.git \
/usr/src/app/plugins/atomic/data/atomic-red-team; \
# For offline emu
# (Emu is disabled by default, no need to disable it if slim variant is being built)
RUN if [ ! -d "/usr/src/app/plugins/emu/data/adversary-emulation-plans" ] && [ "$VARIANT" = "full" ]; then \
git clone --depth 1 https://github.com/center-for-threat-informed-defense/adversary_emulation_library \
/usr/src/app/plugins/emu/data/adversary-emulation-plans; \
fi

WORKDIR /usr/src/app/plugins/emu
# Download emu payloads
# emu doesn't seem capable of running this itself - always download
RUN cd /usr/src/app/plugins/emu; ./download_payloads.sh

# If emu is enabled, complete necessary installation steps
RUN if [ $(grep -c "\- emu" ../../conf/local.yml) ]; then \
apt-get -y install zlib1g unzip; \
pip3 install -r requirements.txt; \
./download_payloads.sh; \
fi
# The commands above (git clone) will generate *huge* .git folders - remove them
RUN (find . -type d -name ".git") | xargs rm -rf

WORKDIR /usr/src/app
# Install Go dependencies
RUN cd /usr/src/app/plugins/sandcat/gocat; go mod tidy && go mod download

# Install Node.js, npm, and other build VueJS front-end
RUN apt-get update && \
apt-get install -y nodejs npm && \
# Directly use npm to install dependencies and build the application
(cd plugins/magma && npm install) && \
(cd plugins/magma && npm run build) && \
# Remove Node.js, npm, and other unnecessary packages
apt-get remove -y nodejs npm && \
apt-get autoremove -y && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Update sandcat agents
RUN cd /usr/src/app/plugins/sandcat; ./update-agents.sh

WORKDIR /usr/src/app
# Make sure emu can always be used in container (even if not enabled right now)
RUN cd /usr/src/app/plugins/emu; \
pip3 install --break-system-packages -r requirements.txt

STOPSIGNAL SIGINT

Expand Down
58 changes: 33 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,39 @@ These requirements are for the computer running the core framework:
* Recommended: GoLang 1.17+ to dynamically compile GoLang-based agents.
* NodeJS (v16+ recommended for v5 VueJS UI)

## Docker Installation (Recommended)
**Note 1: The image on DockerHub is outdated, please do not use it for the time being!**

**Note 2: The builder plugin will not work within Docker**

Local build:
```sh
git clone https://github.com/mitre/caldera.git --recursive
cd caldera
docker build --build-arg VARIANT=full -t caldera .
docker run -p 8888:8888 caldera
```

Adjust the port forwarding (`-p`) and build args (`--build-arg`) as desired to make ports accessible or change the caldera variant.

Pre-Built Image (from GitHub Container Registry):
```sh
docker run -p 8888:8888 ghcr.io/mitre/caldera:latest
```

To gracefully terminate your docker container, do the following:
```Bash
# Find the container ID for your docker container running Caldera
docker ps

# Stop the container
docker stop [container ID]
```

There are two variants available, *full* and *slim*.
The *slim* variant doesn't include files necessary for the *emu* and *atomic* plugins, which will be downloaded on-demand if the plugins are ever enabled. The *full* variant is suitable for operation in environments without an internet connection. Slim images on GHCR are prefixed with "slim".


## Installation

Concise installation steps:
Expand Down Expand Up @@ -110,31 +143,6 @@ If you'll be developing the UI, there are a few more additional installation ste

Your Caldera server is available at http://localhost:8888 as usual, but there will now be a hot-reloading development server for the VueJS front-end available at http://localhost:3000. Both logs from the server and the front-end will display in the terminal you launched the server from.

## Docker Deployment
To build a Caldera docker image, ensure you have docker installed and perform the following actions:
```Bash
# Recursively clone the Caldera repository if you have not done so
git clone https://github.com/mitre/caldera.git --recursive

# Build the docker image. Change image tagging as desired.
# WIN_BUILD is set to true to allow Caldera installation to compile windows-based agents.
# Alternatively, you can use the docker compose YML file via "docker-compose build"
cd caldera
docker build . --build-arg WIN_BUILD=true -t caldera:latest

# Run the image. Change port forwarding configuration as desired.
docker run -p 8888:8888 caldera:latest
```

To gracefully terminate your docker container, do the following:
```Bash
# Find the container ID for your docker container running Caldera
docker ps

# Stop the container
docker stop [container ID]
```

## Contributing

Refer to our [contributor documentation](CONTRIBUTING.md).
Expand Down
4 changes: 2 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ services:
context: .
dockerfile: Dockerfile
args:
TZ: "UTC" #TZ sets timezone for ubuntu setup
WIN_BUILD: "true" #WIN_BUILD is used to enable windows build in sandcat plugin
TZ: "UTC" # Timezone to use in container
VARIANT: "full"
image: caldera:latest
ports:
- "8888:8888"
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ asyncssh==2.14.1
aioftp~=0.20.0
packaging==23.2
croniter~=3.0.3
setuptools==75.6.0