From dc82fa142133d38b6ec028a6e973eb2cf877b230 Mon Sep 17 00:00:00 2001 From: Plup Date: Thu, 2 Apr 2026 14:43:20 -0700 Subject: [PATCH 1/2] fix: enforced compliance of the id used in kubernetes annatations Made-with: Cursor --- .../thp/cortex/services/K8sJobRunnerSrv.scala | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/app/org/thp/cortex/services/K8sJobRunnerSrv.scala b/app/org/thp/cortex/services/K8sJobRunnerSrv.scala index 588d82331..3292ae032 100644 --- a/app/org/thp/cortex/services/K8sJobRunnerSrv.scala +++ b/app/org/thp/cortex/services/K8sJobRunnerSrv.scala @@ -11,6 +11,7 @@ import play.api.{Configuration, Logger} import java.nio.file._ import java.util import javax.inject.{Inject, Singleton} +import scala.annotation.tailrec import scala.concurrent.duration.{DurationInt, FiniteDuration} import scala.jdk.CollectionConverters._ import scala.util.{Failure, Success, Try} @@ -34,6 +35,27 @@ class K8sJobRunnerSrv( lazy val logger: Logger = Logger(getClass) + /** Kubernetes label values must be ≤63 chars and start/end with [A-Za-z0-9] (see label value validation). */ + private def isKubernetesLabelAlphanum(c: Char): Boolean = + (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') + + private def kubernetesLabelValue(raw: String): String = { + @tailrec + def trimEnds(s: String): String = { + if (s.isEmpty) s + else if (!isKubernetesLabelAlphanum(s.head)) trimEnds(s.tail) + else if (!isKubernetesLabelAlphanum(s.last)) trimEnds(s.init) + else s + } + val trimmed = trimEnds(raw) + if (trimmed.isEmpty) "0" + else if (trimmed.length <= 63) trimmed + else trimEnds(trimmed.take(63)) match { + case t if t.nonEmpty => t + case _ => "0" + } + } + lazy val isAvailable: Boolean = Try { val ver = client.getVersion @@ -55,10 +77,11 @@ class K8sJobRunnerSrv( // make the default longer than likely values, but still not infinite val timeout_or_default = timeout.getOrElse(8.hours) // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/ - // FIXME: this collapses case, jeopardizing the uniqueness of the identifier. - // LDH: lowercase, digits, hyphens. - val ldh_jobid = job.id.toLowerCase().replace('_', '-') + // RFC 1123 names: must start/end with alphanumeric; same trimming as label values. + val ldh_jobid = kubernetesLabelValue(job.id.toLowerCase().replace('_', '-')) val kjobName = "neuron-job-" + ldh_jobid + val jobLabel = kubernetesLabelValue(job.id) + val workerLabel = kubernetesLabelValue(job.workerId()) val pvcvs = new PersistentVolumeClaimVolumeSourceBuilder() .withClaimName(persistentVolumeClaimName.get) .withReadOnly(false) @@ -69,8 +92,8 @@ class K8sJobRunnerSrv( .withNewMetadata() .withName(kjobName) .withLabels(Map( - "cortex-job-id" -> job.id, - "cortex-worker-id" -> job.workerId(), + "cortex-job-id" -> jobLabel, + "cortex-worker-id" -> workerLabel, "cortex-neuron-job" -> "true").asJava) .endMetadata() .withNewSpec() @@ -124,7 +147,7 @@ class K8sJobRunnerSrv( s" image : $dockerImage\n" + s" mount : pvc $persistentVolumeClaimName subdir $relativeJobDirectory as /job" + created_env.map(ev => s"\n env : ${ev.getName} = ${ev.getValue}").mkString) - val ended_kjob = client.batch().v1().jobs().withLabel("cortex-job-id", job.id) + val ended_kjob = client.batch().v1().jobs().withLabel("cortex-job-id", jobLabel) .waitUntilCondition(x => Option(x).flatMap(j => Option(j.getStatus).flatMap(s => Some(s.getConditions.asScala.map(_.getType).exists(t => @@ -140,7 +163,7 @@ class K8sJobRunnerSrv( } // let's find the job by the attribute we know is fundamentally // unique, rather than one constructed from it - val deleted: util.List[StatusDetails] = client.batch().v1().jobs().withLabel("cortex-job-id", job.id).delete() + val deleted: util.List[StatusDetails] = client.batch().v1().jobs().withLabel("cortex-job-id", jobLabel).delete() if(!deleted.isEmpty) { logger.info(s"Deleted Kubernetes Job for job ${job.id}") } else { From 413b201d5c0339a3b42bfa8d3586b2b73a104b80 Mon Sep 17 00:00:00 2001 From: Plup Date: Thu, 2 Apr 2026 14:41:40 -0700 Subject: [PATCH 2/2] chore: added personal dockerfile Made-with: Cursor --- .dockerignore | 18 ++++++++++ Dockerfile | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..57e988834 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,18 @@ +# Git and IDE +.git +.gitignore +**/.idea +**/.vscode + +# Build outputs (rebuilt in the image) +**/target +**/node_modules +www/node_modules +www/dist + +# Docs and local tooling +*.md +.cursor +docker/cortex + +# Keep project/build.properties, build.sbt, sources — needed for sbt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..ccddecd0e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,95 @@ +# syntax=docker/dockerfile:1 +# +# Multi-stage image: compile Scala/Play + AngularJS frontend, then run on the official Cortex base layer. +# +# Build: +# docker build -t cortex:local . +# +# Run (example; point Elasticsearch at your cluster): +# docker run --rm -p 9001:9001 cortex:local +# +# Requires network access during build (Maven/Ivy/npm, Debian/Corretto/Docker apt repos). +# +# Runtime matches project/DockerSettings.scala / builds/docker/Dockerfile (Debian + Corretto 11 + Docker). +# To use the prebuilt base image instead (if you can pull it): replace the runtime FROM below with +# FROM ghcr.io/strangebee/cortex-baselayer:rolling +# and remove the duplicate RUN that installs Java/Docker/user (keep COPY and chmod). + +# ----------------------------------------------------------------------------- +# Builder: JDK 11, sbt, Node (webpack), and bower (needed by www npm postinstall scripts) +# ----------------------------------------------------------------------------- +FROM eclipse-temurin:11-jdk-jammy AS builder + +ENV LANG=C.UTF-8 \ + SBT_OPTS="-Xmx4096m -Xss2m" + +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl git gnupg ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# sbt (version aligned with project/build.properties) +RUN curl -fsSL "https://github.com/sbt/sbt/releases/download/v1.11.7/sbt-1.11.7.tgz" \ + | tar xz -C /usr/local + +ENV PATH="/usr/local/sbt/bin:${PATH}" + +# Node.js 20 (for www: npm install + webpack via sbt) +RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ + && apt-get update \ + && apt-get install -y --no-install-recommends nodejs \ + && rm -rf /var/lib/apt/lists/* + +# css-spaces and other legacy deps invoke `bower` in postinstall +RUN npm install -g bower + +WORKDIR /build + +COPY . . + +RUN sbt -batch stage + +# ----------------------------------------------------------------------------- +# Runtime: Debian + Amazon Corretto 11 + Docker CLI (same idea as builds/docker/Dockerfile) +# ----------------------------------------------------------------------------- +FROM debian:13-slim + +LABEL org.opencontainers.image.source="https://github.com/TheHive-Project/Cortex" +LABEL org.opencontainers.image.description="Cortex built from source" + +WORKDIR /opt/cortex + +ENV JAVA_HOME=/usr/lib/jvm/java-11-amazon-corretto + +RUN apt-get update && apt-get upgrade -y \ + && apt-get install -y --no-install-recommends ca-certificates curl gnupg \ + && curl -fL https://apt.corretto.aws/corretto.key | gpg --dearmor -o /usr/share/keyrings/corretto.gpg \ + && echo 'deb [signed-by=/usr/share/keyrings/corretto.gpg] https://apt.corretto.aws stable main' > /etc/apt/sources.list.d/corretto.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends java-11-amazon-corretto-jdk \ + && curl -fsSL https://download.docker.com/linux/debian/gpg -o /usr/share/keyrings/docker.asc \ + && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" > /etc/apt/sources.list.d/docker.list \ + && apt-get update \ + && apt-get install -y --no-install-recommends docker-ce docker-ce-cli containerd.io docker-ce-rootless-extras uidmap iproute2 fuse-overlayfs \ + && groupadd -g 1001 cortex \ + && useradd --system --uid 1001 --gid 1001 --groups docker cortex -d /opt/cortex \ + && mkdir -m 777 /var/log/cortex \ + && chmod 666 /etc/subuid /etc/subgid \ + && rm -rf /var/lib/apt/lists/* \ + && apt-get clean -y -q \ + && apt-get autoremove -y -q + +COPY --from=builder --chown=root:root /build/target/universal/stage/ /opt/cortex/ + +COPY --from=builder /build/package/docker/entrypoint /opt/cortex/entrypoint +COPY --from=builder /build/conf/application.sample /etc/cortex/application.conf +COPY --from=builder /build/package/logback.xml /etc/cortex/logback.xml + +RUN chmod +x /opt/cortex/bin/cortex /opt/cortex/entrypoint \ + && chown -R cortex:cortex /etc/cortex + +VOLUME /var/lib/docker + +EXPOSE 9001 + +ENTRYPOINT ["/opt/cortex/entrypoint"] +CMD []