Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
4425ed7
Bookworm migration: nftables, IFB QoS, Python 3.11, IPS and build fixes
singhrohit23 Mar 16, 2026
cfca26a
CI: add bookworm build support via ngfw-bookworm branch
singhrohit23 Mar 16, 2026
9557960
IPsec VPN: fix VTI tunnel teardown and L2TP mark rule errors
singhrohit23 Mar 18, 2026
35af110
NGFW-15735: ATS Test failures on bookworm bandwidth-control, captive-…
singhrohit23 Apr 24, 2026
0e56f56
NGFW-15735: Fixed MTU Probe, websearch test case fix
singhrohit23 Apr 28, 2026
e4e6fbc
Ngfw 15692 remove virus blocker lite from installables (#1165)
DhirajM09 Apr 6, 2026
cc1b5b8
NGFW-15749: trixie ngfw_src patches — IPS ruamel 0.18 + Phase G porting
singhrohit23 May 13, 2026
d1b5980
NGFW-15749: drop -extensions from openssl req in cert-gen scripts (Op…
singhrohit23 May 14, 2026
635531c
NGFW-15749: move UDP QoS connmark save into ut-uvm-update-rules.sh
singhrohit23 May 15, 2026
9002e0c
NGFW-15749: Travis REPOSITORY=trixie on ngfw-trixie branch
singhrohit23 May 18, 2026
2c6c2dd
NGFW-15749: trixie Dockerfile add safe.directory '*' for git 2.47
singhrohit23 May 19, 2026
faa5249
NGFW-15749: trixie build image use fakeroot-tcp backend
singhrohit23 May 19, 2026
0dcfcc8
NGFW-15749: NO_CLEAN=1 to skip fakeroot cleanup hang on Travis trixie
singhrohit23 May 19, 2026
13622e3
NGFW-15749: Dockerfile.trixie-base export keyring as .asc for sqv
singhrohit23 May 20, 2026
4a8252f
NGFW-15749: WebrootQuery raise BctidClientReadTimeout 5000 -> 30000
singhrohit23 May 21, 2026
dafef12
NGFW-15749: SSL Inspector disable TLSv1 and TLSv1.1 for JDK21 compat
singhrohit23 May 21, 2026
cda2ed1
NGFW-15749: IPS guard synchronizeSettingsWithVariables against empty …
singhrohit23 May 21, 2026
14373ab
NGFW-15749: ut-upgrade.py add Trixie target helpers + kept-back toler…
singhrohit23 May 22, 2026
d9d11fb
NGFW-15749: ut-upgrade.py gate REFRESH COLLATION behind PG15+
singhrohit23 May 25, 2026
2f28835
NGFW-15792: ut-upgrade.py trixie helper parity with bookworm + auto-r…
singhrohit23 May 26, 2026
b889149
NGFW-15749: ATS test hardening for trixie post-upgrade flakes
singhrohit23 May 29, 2026
2c561f9
NGFW-15749: fix localhost→127.0.0.1 for UVM JSON-RPC auth on trixie
singhrohit23 Jun 3, 2026
2c4790c
NGFW-15802: fix uvm_login.py IPv6 socket lookup for localhost auth
singhrohit23 Jun 3, 2026
04f04d5
NGFW-15749: fix test_310_system_logs unzip prompt on trixie
singhrohit23 Jun 5, 2026
988a1be
NGFW-15749: fix test_administration cert upload crash on trixie
singhrohit23 Jun 9, 2026
0fbc116
NGFW-15802: ATS Test fixes on trixie
singhrohit23 Jun 10, 2026
743abc9
NGFW-15802: fix cert upload test failures on trixie ATS
singhrohit23 Jun 12, 2026
f71d999
NGFW-15826: remove admin.js flush workaround from cert upload tests
singhrohit23 Jun 16, 2026
0213cd4
NGFW-15749: fix unclosed comment in AppManagerImpl breaking javac
singhrohit23 Jul 6, 2026
d201472
NGFW-15675: fix broken index.txt/serial.txt symlinks after root CA ro…
singhrohit23 Jul 31, 2026
82d54f5
Revert "NGFW-15675: fix broken index.txt/serial.txt symlinks after ro…
singhrohit23 Jul 31, 2026
f602378
NGFW-15675: fix broken index.txt/serial.txt symlinks after root CA ro…
singhrohit23 Jul 31, 2026
418c08c
NGFW-15675: move self-heal to constructor so it runs on every startup
singhrohit23 Jul 31, 2026
2797e70
NGFW-15918: fix restart_uvm() NFQUEUE collision on trixie (JDK21)
singhrohit23 Aug 11, 2026
48aa25a
Allow Pulse restart from DEAD state
singhrohit23 Aug 14, 2026
fdaa158
ATS: clean up apps when initial_extra_setup fails
singhrohit23 Aug 18, 2026
60294ac
fix ATS regressions from rebase: QoS status + OpenVPN cipher config
singhrohit23 Aug 27, 2026
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
5 changes: 2 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,9 @@ env:
PKGTOOLS_COMMIT: origin/${TRAVIS_BRANCH}
UPLOAD: scp
jobs:
- REPOSITORY: bullseye
- REPOSITORY: trixie
ARCHITECTURE: amd64
- REPOSITORY: bullseye
ARCHITECTURE: arm64
NO_CLEAN: 1

before_install:
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
Expand Down
28 changes: 28 additions & 0 deletions Dockerfile.trixie-base
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
FROM debian:trixie
LABEL maintainer="Rohit Singh <rohit@arista.com>"

ENV REPOSITORY=trixie
ENV STABLE_VERSION=19.0.0

USER root
ENV DEBIAN_FRONTEND=noninteractive

RUN echo 'APT::Install-Recommends "false";' > /etc/apt/apt.conf.d/no-recommends && \
echo 'APT::Install-Suggests "false";' >> /etc/apt/apt.conf.d/no-recommends

RUN apt update -q
RUN apt dist-upgrade -y
RUN apt install -y gnupg dirmngr

# cleanup
RUN apt clean
RUN rm -rf /var/lib/apt/lists/*

# NGFW-15749: trixie's sqv (replacement for apt-key) rejects keybox-format
# .gpg files. Export ASCII-armored .asc instead (sqv accepts .asc and .pgp,
# not .gpg keybox). Same fix shipped in ngfw_pkgs untangle-archive-keyring
# commit aa164ae57.
RUN mkdir -p /root/.gnupg && chmod 700 /root/.gnupg && \
gpg --keyserver keyserver.ubuntu.com --recv-keys 735A9E18E8F62EDF413592460B9D6AE3627BF103 && \
gpg --export --armor 735A9E18E8F62EDF413592460B9D6AE3627BF103 \
> /etc/apt/trusted.gpg.d/untangle.asc
85 changes: 85 additions & 0 deletions Dockerfile.trixie-build
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
FROM untangleinc/ngfw:trixie-base-multiarch
LABEL maintainer="Rohit Singh <rohit@arista.com>"

# do not gzip apt lists files (for apt-show-versions)
RUN rm -f /etc/apt/apt.conf.d/docker-gzip-indexes

RUN apt update -q

# add foreign architectures and their corresponding crossbuild package
RUN dpkg --add-architecture arm64
RUN apt install --yes crossbuild-essential-arm64

# install required packages
RUN apt install --yes build-essential
RUN apt install --yes debhelper
RUN apt install --yes devscripts
RUN apt install --yes git
# NGFW-15749: git 2.47 (trixie) enforces safe.directory stricter than bookworm's 2.39.
# Bind-mounted /opt/untangle/build at runtime triggers "dubious ownership" → empty git log
# → empty timestamp → malformed debian/changelog → libapache2-mod-python setup.py fails.
RUN git config --system --add safe.directory '*'
RUN apt install --yes apt-show-versions
RUN apt install --yes openssh-client
RUN apt install --yes dput
RUN apt install --yes curl
RUN apt install --yes procps
RUN apt install --yes gawk
RUN apt install --yes apt-utils

# kernel build dependencies
RUN apt install --yes kernel-wedge
RUN apt install --yes quilt
RUN apt install --yes bc
RUN apt install --yes flex
RUN apt install --yes bison
RUN apt install --yes libelf-dev
RUN apt install --yes libssl-dev
RUN apt install --yes rsync
RUN apt install --yes kmod
RUN apt install --yes cpio
RUN apt install --yes python3
RUN apt install --yes python3-jinja2
RUN apt install --yes dwarves
RUN apt install --yes dh-exec

# ISO build dependencies
RUN apt install --yes simple-cdd
RUN apt install --yes reprepro
RUN apt install --yes dose-distcheck
RUN apt install --yes mtools
RUN apt install --yes dosfstools
RUN apt install --yes xorriso
RUN apt install --yes debian-archive-keyring
RUN apt install --yes fakeroot
# NGFW-15749: Travis CI Docker container's IPC namespace restricts SysV semaphores,
# causing fakeroot (default sysv backend) to hang indefinitely on `fakeroot debian/rules
# clean` after dput. Switch to the TCP backend which uses sockets instead and works in
# any IPC environment. Local builds also unaffected (libfakeroot-tcp.so is upstream-shipped).
RUN update-alternatives --set fakeroot /usr/bin/fakeroot-tcp
RUN apt install --yes dpkg-dev

# cleanup
RUN apt clean
RUN rm -rf /var/lib/apt/lists/* /var/cache/apt-show-versions/*

# do not use official Debian mirrors during build
RUN rm -f /etc/apt/sources.list

# base dir
ENV UNTANGLE=/opt/untangle
RUN mkdir -p ${UNTANGLE}

# pkgtools
ENV PKGTOOLS=${UNTANGLE}/ngfw_pkgtools
VOLUME ${PKGTOOLS}

# source to build
ENV SRC=/opt/untangle/build
RUN mkdir -p ${SRC}
VOLUME ${SRC}

WORKDIR ${SRC}


CMD [ "bash", "-c", "${PKGTOOLS}/build.sh" ]
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""branding_manager tests"""
import json
import re
import time
import unittest
import urllib.error
import urllib.request
Expand All @@ -24,6 +25,50 @@
default_banner_message = ""

default_policy_id = 1


def _fetch_uvm_root_with_retry(retries=10, retry_sleep=2, all_parameters=True):
"""
GET the UVM root URL via remote wget and retry until the response body
looks like the UVM login page (contains a populated <title> with company
name in it), not Apache's mod_python "Permission denied" fallback.

Right after untangle-vm restart Apache mod_python's DbmSession isn't fully
initialized for some seconds and any unauthenticated GET to / returns the
"<title>Server</title> ... Permission denied" page instead of the actual
login HTML. Observed as 2 branding-manager failures (test_020/021) at T+9m
in the 2026-05-28 ATS bookworm->trixie post-upgrade run; both pages render
correctly on re-run minutes later.

Returns the (string) response body. Raises AssertionError if no retry
succeeds.
"""
title_re = re.compile(r'<title>(.*?)</title>', re.IGNORECASE | re.DOTALL)
last = ""
for attempt in range(retries):
result = remote_control.run_command(
global_functions.build_wget_command(
output_file="-",
ignore_certificate=True,
all_parameters=all_parameters,
uri=global_functions.get_http_url(),
),
stdout=True,
) or ""
m = title_re.search(result)
title = m.group(1).strip() if m else ""
# Apache fallback page renders as "<title>Server</title>"; UVM login
# renders with the company name in the title (e.g. "Arista Administrator
# Login"). Any non-"Server" title with content means the UVM stack served.
if title and title.lower() != "server":
return result
last = result
time.sleep(retry_sleep)
raise AssertionError(
f"UVM root never returned the login page after {retries} attempts; "
f"last body title was empty or 'Server' (mod_python fallback). "
f"Apache mod_python likely not warm after untangle-vm restart."
)

@pytest.mark.branding_manager
class BrandingManagerTests(NGFWTestCase):
Expand Down Expand Up @@ -162,8 +207,8 @@ def test_019_valid_contact_renders_on_blockpage(self):
@pytest.mark.failure_behind_ngfw
def test_020_check_login_page_branding(self):
# Check login page for branding
result = remote_control.run_command(global_functions.build_wget_command(output_file="-", ignore_certificate=True, all_parameters=True, uri=global_functions.get_http_url()),stdout=True)
result = _fetch_uvm_root_with_retry()

# Verify Title of blockpage as company name
myRegex = re.compile('<title>(.*?)</title>', re.IGNORECASE|re.DOTALL)
matchText = myRegex.search(result).group(1)
Expand All @@ -175,17 +220,17 @@ def test_020_check_login_page_branding(self):

def test_021_changeBranding_bannerMessage(self):
global app, appWeb, appData

# TODO Just like the changes above, I think this may be unnecessary. Not sure though. Do we need to test multi-line?
appData['bannerMessage'] = "A regulation banner requirement containing a mix of text including <b>html</b> and\nmultiple\nlines"
app.setSettings(appData)
result = remote_control.run_command(global_functions.build_wget_command(output_file="-", all_parameters=True, uri=global_functions.get_http_url()),stdout=True)
result = _fetch_uvm_root_with_retry()
myRegex = re.compile('.*A regulation banner requirement containing a mix of text including <b>html</b> and<br/>multiple<br/>lines.*', re.DOTALL|re.MULTILINE)
assert(re.match(myRegex, result))

appData['bannerMessage'] = default_banner_message
app.setSettings(appData)
result = remote_control.run_command(global_functions.build_wget_command(output_file="-", ignore_certificate=True, all_parameters=True, uri=global_functions.get_http_url()),stdout=True)
result = _fetch_uvm_root_with_retry()
myRegex = re.compile('.*A regulation banner requirement containing a mix of text including <b>html</b> and<br/>multiple<br/>lines.*', re.DOTALL|re.MULTILINE)
assert(not re.match(myRegex, result))

Expand Down
2 changes: 1 addition & 1 deletion build-order.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
. bullseye
. trixie
9 changes: 8 additions & 1 deletion buildtools/buildtools.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
openjdk8 = "java-8-openjdk-#{arch}"
openjdk11 = "java-11-openjdk-#{arch}"
openjdk17 = "java-17-openjdk-#{arch}"
openjdk21 = "java-21-openjdk-#{arch}"

jvm = case arch
when "armel"
Expand All @@ -13,7 +14,13 @@
when "armhf"
File.exist?("/usr/lib/jvm/#{openjdk8}") ? openjdk8 : "jdk-7-oracle-arm-vfp-hflt"
else
File.exist?("/usr/lib/jvm/#{openjdk17}") ? openjdk17 : openjdk11
if File.exist?("/usr/lib/jvm/#{openjdk21}")
openjdk21
elsif File.exist?("/usr/lib/jvm/#{openjdk17}")
openjdk17
else
openjdk11
end
end
warn "JVM = #{jvm}"
ENV['JAVA_HOME'] = "/usr/lib/jvm/#{jvm}"
Expand Down
2 changes: 1 addition & 1 deletion buildtools/jars.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def Jars.findJars
const_set(:GetText, [ Jars.downloadTarget('gettext-commons-0.9.1/gettext-commons-0.9.1.jar') ])
const_set(:JakartaActivation, [ Jars.downloadTarget('jakarta.activation-1.2.1/jakarta.activation-1.2.1.jar') ])
const_set(:JavaTransaction, [ Jars.downloadTarget('jta-1.1/jta-1.1.jar') ])
const_set(:Slf4j, [ Jars.downloadTarget( 'slf4j-2.0.9/slf4j-reload4j-2.0.9.jar'),
const_set(:Slf4j, [ Jars.downloadTarget( 'slf4j-2.0.9/slf4j-nop-2.0.9.jar'),
Jars.downloadTarget( 'slf4j-2.0.9/slf4j-api-2.0.9.jar' ) ])

const_set(:TomcatCommon, [ 'tomcat-embed-jasper.jar',
Expand Down
2 changes: 1 addition & 1 deletion buildtools/target.rb
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def initialize(package, moveSpecs, taskName, filterset = nil, destBase = nil)

if File.symlink?(src)
## Handling symbolic links that don't resolve until in place.
file dest => src if File.exists?( src )
file dest => src if File.exist?( src )

file dest do
ensureDirectory(File.dirname(dest)) if !File.exist?(dest)
Expand Down
52 changes: 46 additions & 6 deletions captive-portal/hier/usr/share/untangle/web/capture/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from uvm import Uvm
import urllib.request, urllib.parse, urllib.error
import pprint
import imp
import importlib.util
import time
import os
import uvm.i18n_helper
Expand All @@ -19,7 +19,8 @@

from uvm import settings_reader

_ = uvm.i18n_helper.get_translation('untangle').lgettext
_trans = uvm.i18n_helper.get_translation('untangle')
_ = getattr(_trans, 'lgettext', _trans.gettext)

# Dictionary of Oauth providers by name, each with the following fields:
# platform Identifier to pass to auth-relay
Expand Down Expand Up @@ -244,7 +245,17 @@ def index(req):
# Arguments include username and password along with several hidden fields
# that store the details of the page originally requested.

def authpost(req,username,password,method,nonce,appid,host,uri):
def authpost(req,username=None,password=None,method=None,nonce=None,appid=None,host=None,uri=None):
# On Python 3.11, mod_python FieldStorage returns bytes keys
if username is None or method is None:
fields = _get_form_fields(req)
if username is None: username = fields.get('username', '')
if password is None: password = fields.get('password', '')
if method is None: method = fields.get('method', '')
if nonce is None: nonce = fields.get('nonce', '')
if appid is None: appid = fields.get('appid', '')
if host is None: host = fields.get('host', '')
if uri is None: uri = fields.get('uri', '')
if type(username) == bytes:
username = username.decode('utf-8')
if type(password) == bytes:
Expand Down Expand Up @@ -322,7 +333,17 @@ def authpost(req,username,password,method,nonce,appid,host,uri):
# in the POST data. To handle this scenario, we use a function parameter
# default of 'empty' which will cause app.userActivate to return false.

def infopost(req,method,nonce,appid,host,uri,agree=b'empty'):
def infopost(req,method=None,nonce=None,appid=None,host=None,uri=None,agree=b'empty'):
# On Python 3.11, mod_python FieldStorage returns bytes keys which
# don't match str parameter names in apply_fs_data, so args are empty
if method is None or nonce is None or appid is None or host is None or uri is None:
fields = _get_form_fields(req)
if method is None: method = fields.get('method', '')
if nonce is None: nonce = fields.get('nonce', '')
if appid is None: appid = fields.get('appid', '')
if host is None: host = fields.get('host', '')
if uri is None: uri = fields.get('uri', '')
if agree == b'empty': agree = fields.get('agree', 'empty')
if type(method) == bytes:
method = method.decode('utf-8')
if type(nonce) == bytes:
Expand Down Expand Up @@ -622,6 +643,23 @@ def extjs_reply(status,message,filename=""):

return(result)

#-----------------------------------------------------------------------------
# Extract form fields from req.form, handling bytes keys from Python 3.11

def _get_form_fields(req):
fields = {}
form = getattr(req, 'form', None)
if form is not None and hasattr(form, 'list') and form.list:
for field in form.list:
name = field.name
value = field.value
if type(name) == bytes:
name = name.decode('utf-8')
if type(value) == bytes:
value = value.decode('utf-8')
fields[name] = value
return fields

#-----------------------------------------------------------------------------
# forces stuff loaded from settings files to be UTF-8 when plugged
# into the page template files
Expand Down Expand Up @@ -688,5 +726,7 @@ def custom_handler(req):
def _import_file(filename):
(path, name) = os.path.split(filename)
(name, ext) = os.path.splitext(name)
(file, filename, data) = imp.find_module(name, [path])
return imp.load_module(name, file, filename, data)
spec = importlib.util.spec_from_file_location(name, os.path.join(path, name + ext))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

from uvm import settings_reader

_ = uvm.i18n_helper.get_translation('untangle').lgettext
_trans = uvm.i18n_helper.get_translation('untangle')
_ = getattr(_trans, 'lgettext', _trans.gettext)

#-----------------------------------------------------------------------------
# This is the default function that gets called for a client logout request
Expand Down
18 changes: 18 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
untangle-vm (18.0.0.20260429T064446Z.c59ed45571+localdiff20260511T113424-1trixie) current; urgency=medium

* auto build

-- Untangle Buildbot <buildbot@untangle.com> Wed, 29 Apr 2026 06:44:46 +0000

untangle-vm (18.0.0.20260429T064446Z.c59ed45571+localdiff20260511T112649-1trixie) current; urgency=medium

* auto build

-- Untangle Buildbot <buildbot@untangle.com> Wed, 29 Apr 2026 06:44:46 +0000

untangle-vm (18.0.0.20260429T064446Z.c59ed45571+localdiff20260511T111712-1trixie) current; urgency=medium

* auto build

-- Untangle Buildbot <buildbot@untangle.com> Wed, 29 Apr 2026 06:44:46 +0000

untangle-vm (10.0.0~svn20130320r34349trunk-1squeeze) dmorris; urgency=low

* 10.0
Expand Down
Loading