Skip to content
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
32 changes: 29 additions & 3 deletions pantera-main/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,30 @@
FROM eclipse-temurin:21-jre-alpine
ARG JAR_FILE
ARG APM_VERSION=1.55.4
ARG TRIVY_VERSION=0.69.3
ARG GRYPE_VERSION=0.110.0

# Install curl for downloading Elastic APM agent + jattach for programmatic attachment (jmap + jstack + jcmd + jinfo)
RUN apk add --no-cache curl jattach
# Install curl, jattach, and vulnerability scanners (Trivy + Grype).
# Both are static Go binaries — no glibc required on Alpine.
# Trivy asset names use a hyphen (Linux-64bit), Grype uses underscore (linux_amd64).
RUN apk add --no-cache curl jattach && \
ARCH="$(uname -m)" && \
case "${ARCH}" in \
x86_64) TRIVY_ARCH="Linux-64bit" ; GRYPE_ARCH="amd64" ;; \
aarch64) TRIVY_ARCH="Linux-ARM64" ; GRYPE_ARCH="arm64" ;; \
armv7l) TRIVY_ARCH="Linux-ARM" ; GRYPE_ARCH="armv6" ;; \
*) echo "Unsupported arch: ${ARCH}" && exit 1 ;; \
esac && \
curl -fsSL "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_${TRIVY_ARCH}.tar.gz" \
-o /tmp/trivy.tar.gz && \
tar -xzf /tmp/trivy.tar.gz -C /usr/local/bin trivy && \
rm /tmp/trivy.tar.gz && \
chmod 755 /usr/local/bin/trivy && \
curl -fsSL "https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}/grype_${GRYPE_VERSION}_linux_${GRYPE_ARCH}.tar.gz" \
-o /tmp/grype.tar.gz && \
tar -xzf /tmp/grype.tar.gz -C /usr/local/bin grype && \
rm /tmp/grype.tar.gz && \
chmod 755 /usr/local/bin/grype

ENV JVM_ARGS="-XX:+UseG1GC -XX:MaxGCPauseMillis=300 \
-XX:G1HeapRegionSize=16m \
Expand All @@ -20,14 +41,19 @@ ENV JVM_ARGS="-XX:+UseG1GC -XX:MaxGCPauseMillis=300 \

RUN addgroup -g 2020 -S pantera && \
adduser -u 2021 -S -G pantera -s /sbin/nologin pantera && \
mkdir -p /etc/pantera /usr/lib/pantera /var/pantera/logs/dumps /var/pantera/cache/tmp /opt/apm && \
mkdir -p /etc/pantera /usr/lib/pantera /var/pantera/logs/dumps /var/pantera/cache/tmp /opt/apm \
/var/pantera/trivy/db /var/pantera/grype/db && \
chown -R pantera:pantera /etc/pantera /usr/lib/pantera /var/pantera && \
curl -L "https://repo1.maven.org/maven2/co/elastic/apm/elastic-apm-agent/${APM_VERSION}/elastic-apm-agent-${APM_VERSION}.jar" \
-o /opt/apm/elastic-apm-agent.jar && \
chown pantera:pantera /opt/apm/elastic-apm-agent.jar

ENV TMPDIR=/var/pantera/cache/tmp
ENV PANTERA_VERSION=2.0.7
# Vulnerability scanner DB directories — each scanner downloads its CVE database
# on first scan and caches it here. Mount named volumes for persistence.
ENV TRIVY_CACHE_DIR=/var/pantera/trivy/db
ENV GRYPE_DB_CACHE_DIR=/var/pantera/grype/db

USER 2021:2020

Expand Down
8 changes: 8 additions & 0 deletions pantera-main/docker-compose/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
# -----------------------------------------------------------------------------
PANTERA_VERSION=2.0.7
PANTERA_UI_VERSION=2.0.7

# -----------------------------------------------------------------------------
# Vulnerability Scanners (versions pinned in the Dockerfile)
# Both binaries are installed in the pantera container and manage their own
# CVE databases. DBs are persisted in named volumes (trivy-db, grype-db).
# Trivy: https://github.com/aquasecurity/trivy/releases
# Grype: https://github.com/anchore/grype/releases
# -----------------------------------------------------------------------------
PANTERA_USER_NAME=PANTERA
PANTERA_USER_PASS=changeme
PANTERA_CONFIG=/etc/PANTERA/PANTERA.yml
Expand Down
10 changes: 9 additions & 1 deletion pantera-main/docker-compose/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ services:
- KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
# Vulnerability scanner DB directories — persisted across restarts
- TRIVY_CACHE_DIR=/var/pantera/trivy/db
- GRYPE_DB_CACHE_DIR=/var/pantera/grype/db
volumes:
- ./pantera/pantera.yml:/etc/pantera/pantera.yml
- ./log4j2.xml:/etc/pantera/log4j2.xml
Expand All @@ -70,6 +73,9 @@ services:
- ./pantera/cache:/var/pantera/cache
- ./pantera/cache/log:/var/pantera/logs/
- ~/.aws:/home/.aws
# Vulnerability scanner databases — downloaded on first scan
- trivy-db:/var/pantera/trivy/db
- grype-db:/var/pantera/grype/db
networks:
- pantera-net
# - es
Expand Down Expand Up @@ -236,4 +242,6 @@ networks:
volumes:
valkey-data:
prometheus-data:
grafana-data:
grafana-data:
trivy-db:
grype-db:
27 changes: 26 additions & 1 deletion pantera-main/docker-compose/pantera/pantera.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,32 @@ meta:
repo_types:
npm-proxy:
enabled: true


vulnerability:
# Set to true to enable on-demand vulnerability scanning.
# The scanner binary must be present in the container.
enabled: true
# Scanner backend type. Supported values: "trivy", "grype"
scanner_type: trivy
# Path to the scanner binary. Must be on PATH (both are installed in the container).
scanner_path: trivy
# Hours before a cached scan result is considered stale.
# Users will see a "stale" indicator and can click "Scan Now" to refresh.
cache_ttl_hours: 24
# Maximum seconds to allow a single scanner subprocess to run before killing it.
scan_timeout_seconds: 300
# Maximum number of artifacts scanned in parallel during a "Scan Repository" operation.
# Caps resource usage regardless of repository size. Rule of thumb: CPU cores / 2.
scan_concurrency: 4
# Maximum concurrent scans across ALL repositories at one time.
# Prevents scan-all floods from overwhelming the system. Default: 8.
max_global_concurrency: 8
# Optional cron expression for automatic full scans (Quartz format).
# If omitted, only on-demand scans are performed.
# Example: scan all repos every day at 2 AM
# cron: "0 0 2 * * ?"
cron: "0 0/2 * * * ?"

artifacts_database:
postgres_host: "pantera-db"
postgres_port: 5432
Expand Down
61 changes: 61 additions & 0 deletions pantera-main/src/main/java/com/auto1/pantera/VertxMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,31 @@
import com.auto1.pantera.jetty.http3.Http3Server;
import com.auto1.pantera.jetty.http3.SslFactoryFromYaml;
import com.auto1.pantera.misc.PanteraProperties;
import com.auto1.pantera.scheduling.JobDataRegistry;
import com.auto1.pantera.scheduling.QuartzService;
import com.auto1.pantera.scheduling.ScriptScheduler;
import com.auto1.pantera.vuln.DefaultVulnerabilityScanner;
import com.auto1.pantera.vuln.VulnerabilityDao;
import com.auto1.pantera.vuln.VulnerabilityScanJob;
import com.auto1.pantera.vuln.VulnerabilitySettings;
import com.auto1.pantera.vuln.backend.ScannerBackendFactory;
import com.auto1.pantera.vuln.preparer.ComposerPreparer;
import com.auto1.pantera.vuln.preparer.GoModulePreparer;
import com.auto1.pantera.vuln.preparer.MavenPomArtifactPreparer;
import com.auto1.pantera.vuln.preparer.NpmArtifactPreparer;
import com.auto1.pantera.vuln.preparer.PypiSdistArtifactPreparer;
import com.auto1.pantera.settings.ConfigFile;
import com.auto1.pantera.settings.MetricsContext;
import com.auto1.pantera.settings.Settings;
import com.auto1.pantera.settings.SettingsFromPath;
import com.auto1.pantera.settings.repo.DbRepositories;
import com.auto1.pantera.settings.repo.MapRepositories;
import com.auto1.pantera.settings.repo.RepoConfig;
import com.auto1.pantera.api.ManageRepoSettings;
import com.auto1.pantera.asto.blocking.BlockingStorage;
import com.auto1.pantera.db.dao.RepositoryDao;
import com.auto1.pantera.settings.RepoData;
import com.auto1.pantera.settings.repo.CrudRepoSettings;
import com.auto1.pantera.http.log.EcsLogger;
import com.auto1.pantera.settings.repo.Repositories;
import com.auto1.pantera.db.DbManager;
Expand Down Expand Up @@ -398,6 +414,51 @@ settings, repos, new JwtTokens(jwt, jwtSettings, userTokenDao)

quartz.start();
new ScriptScheduler(quartz).loadCrontab(settings, repos);
final VulnerabilitySettings vsettings = settings.vulnerabilitySettings();
if (vsettings.enabled() && vsettings.cronExpression() != null) {
final CrudRepoSettings vulnCrs = sharedDs.isPresent()
? new RepositoryDao(sharedDs.get())
: new ManageRepoSettings(
new BlockingStorage(settings.configStorage())
);
final VulnerabilityDao vulnDao = sharedDs
.map(VulnerabilityDao::new).orElse(null);
JobDataRegistry.register(VulnerabilityScanJob.KEY_SCANNER,
new DefaultVulnerabilityScanner(
ScannerBackendFactory.create(vsettings),
java.util.List.of(
new NpmArtifactPreparer(),
new MavenPomArtifactPreparer(),
new PypiSdistArtifactPreparer(),
new GoModulePreparer(),
new ComposerPreparer()
),
vsettings
)
);
if (vulnDao != null) {
JobDataRegistry.register(VulnerabilityScanJob.KEY_DAO, vulnDao);
}
JobDataRegistry.register(VulnerabilityScanJob.KEY_CRS, vulnCrs);
JobDataRegistry.register(VulnerabilityScanJob.KEY_REPO_DATA,
new RepoData(settings.configStorage(), settings.caches().storagesCache())
);
JobDataRegistry.register(VulnerabilityScanJob.KEY_SETTINGS, vsettings);
try {
quartz.schedulePeriodicJob(
vsettings.cronExpression(), VulnerabilityScanJob.class,
new org.quartz.JobDataMap()
);
EcsLogger.info("com.auto1.pantera")
.message("Scheduled vulnerability scan job with cron: " + vsettings.cronExpression())
.eventCategory("security")
.eventAction("vulnerability_schedule")
.eventOutcome("success")
.log();
} catch (final org.quartz.SchedulerException ex) {
throw new PanteraException(ex);
}
}

// JIT warmup: fire lightweight requests through group code paths so the
// first real client request doesn't pay ~140ms JIT compilation penalty.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,15 @@
import com.auto1.pantera.cooldown.CooldownService;
import com.auto1.pantera.cooldown.CooldownSupport;
import com.auto1.pantera.cooldown.metadata.CooldownMetadataService;
import com.auto1.pantera.vuln.DefaultVulnerabilityScanner;
import com.auto1.pantera.vuln.VulnerabilityScanner;
import com.auto1.pantera.vuln.VulnerabilitySettings;
import com.auto1.pantera.vuln.backend.ScannerBackendFactory;
import com.auto1.pantera.vuln.preparer.ComposerPreparer;
import com.auto1.pantera.vuln.preparer.GoModulePreparer;
import com.auto1.pantera.vuln.preparer.MavenPomArtifactPreparer;
import com.auto1.pantera.vuln.preparer.NpmArtifactPreparer;
import com.auto1.pantera.vuln.preparer.PypiSdistArtifactPreparer;
import com.auto1.pantera.db.dao.AuthProviderDao;
import com.auto1.pantera.db.dao.RoleDao;
import com.auto1.pantera.db.dao.RepositoryDao;
Expand Down Expand Up @@ -312,6 +321,29 @@ crs, new RepoData(this.configsStorage, this.caches.storagesCache()),
this.security.policy()
).register(router);
new SearchHandler(this.artifactIndex, this.security.policy()).register(router);
// Vulnerability scanning handler
final VulnerabilitySettings vsettings = this.settings.vulnerabilitySettings();
final VulnerabilityScanner vulnScanner = vsettings.enabled()
? new DefaultVulnerabilityScanner(
ScannerBackendFactory.create(vsettings),
java.util.List.of(
new NpmArtifactPreparer(),
new MavenPomArtifactPreparer(),
new PypiSdistArtifactPreparer(),
new GoModulePreparer(),
new ComposerPreparer()
),
vsettings
)
: VulnerabilityScanner.NOP;
new VulnerabilityHandler(
vulnScanner,
this.dataSource,
vsettings,
crs,
new RepoData(this.configsStorage, this.caches.storagesCache()),
this.security.policy()
).register(router);
// Start server
final HttpServer server;
final String schema;
Expand Down
Loading