Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
9f08e61
Update __init__.py
grumo35 Jan 31, 2022
bc964ca
Merge branch 'master' into patch-1
furlongm Feb 15, 2022
57b33e1
Merge branch 'master' into patch-1
furlongm Feb 16, 2022
15e9be0
Add management commands
skatsaounis Apr 10, 2020
726fa74
Add set_site command
skatsaounis May 12, 2020
6d0b4b0
Fix option
skatsaounis May 21, 2020
58d36e3
Update apps.py
furlongm Jun 21, 2024
8072e34
Merge branch 'master' into patch-1
furlongm Mar 20, 2025
fecca1c
Merge branch 'main' into patch-1
furlongm Mar 22, 2025
684516b
Merge branch 'main' into add-management-commands
furlongm Mar 22, 2025
ae7d58e
Merge branch 'main' into patch-1
furlongm Mar 22, 2025
cd577bf
Update __init__.py
furlongm Mar 22, 2025
80a2d60
Update __init__.py
furlongm Mar 22, 2025
12b2d79
Merge branch 'main' into patch-1
furlongm Mar 22, 2025
e1dde1b
Merge branch 'main' into add-management-commands
furlongm Apr 18, 2025
d44a2ce
handle duplicate CVSSes better
Apr 23, 2025
08faacd
Merge pull request #681 from furlongm/cvss-fix
furlongm Apr 23, 2025
bf626c9
reduce max charfield length for mysql
furlongm Apr 8, 2025
382cd29
further reduce charfield size for mysql
furlongm Apr 18, 2025
20a42ed
reduce URLField max_length to 765
furlongm Apr 29, 2025
33d15b2
Merge pull request #673 from furlongm/bug/mysql-max-col-length
furlongm Apr 30, 2025
5685ef3
Merge branch 'main' into patch-1
furlongm Apr 30, 2025
bf5478c
Bump django from 4.2.20 to 4.2.21
dependabot[bot] May 8, 2025
d9c6df7
Merge pull request #682 from furlongm/dependabot/pip/django-4.2.21
furlongm May 8, 2025
57e5c0d
Bump django from 4.2.21 to 4.2.22
dependabot[bot] Jun 6, 2025
6a45e90
Bump requests from 2.32.3 to 2.32.4
dependabot[bot] Jun 10, 2025
64b1920
Merge pull request #684 from furlongm/dependabot/pip/django-4.2.22
furlongm Jun 10, 2025
334af8f
Merge pull request #685 from furlongm/dependabot/pip/requests-2.32.4
furlongm Jun 10, 2025
5674853
Remove unused dependency 'chardet' from requirements.txt
vtalos Jul 17, 2025
cf8c77f
Merge pull request #689 from vtalos/remove-unused-chardet
furlongm Jul 17, 2025
94fcb04
get_or_create_module only returns module
furlongm Aug 6, 2025
1480468
Bump django from 4.2.22 to 4.2.24
dependabot[bot] Sep 10, 2025
36cefb1
Merge pull request #700 from furlongm/dependabot/pip/django-4.2.24
furlongm Sep 10, 2025
6aee812
Merge pull request #693 from furlongm/module-creation
furlongm Sep 10, 2025
b616296
Package types are in the Package class
willfurnell Sep 12, 2025
3f8756c
Merge pull request #701 from willfurnell/package-fix
furlongm Sep 15, 2025
3676e78
Bump django from 4.2.24 to 4.2.25
dependabot[bot] Oct 1, 2025
1328e52
Merge pull request #704 from furlongm/dependabot/pip/django-4.2.25
furlongm Oct 3, 2025
1c26001
bump redis
furlongm Oct 3, 2025
0f54454
Update license in common.py
furlongm Oct 3, 2025
ce9f4f0
fix licenses
furlongm Oct 3, 2025
c651c3f
use GPL-3.0-only for debian copyright
furlongm Oct 3, 2025
807e5de
Merge pull request #377 from grumo35/patch-1
furlongm Oct 21, 2025
1c7ef23
Update patchman/management/commands/createsuperuser_with_password.py
furlongm Oct 21, 2025
f5b34d7
Update patchman/management/commands/createsuperuser_with_password.py
furlongm Oct 21, 2025
c3468c8
Merge branch 'main' into add-management-commands
furlongm Oct 21, 2025
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
2 changes: 1 addition & 1 deletion debian/copyright
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Source: https://github.com/furlongm/patchman
Files: *
Copyright: 2011-2012 VPAC http://www.vpac.org
2013-2021 Marcus Furlong <furlongm@gmail.com>
License: GPL-3.0
License: GPL-3.0-only
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 3 only.
Expand Down
2 changes: 1 addition & 1 deletion errata/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('er_type', models.CharField(max_length=255)),
('url', models.URLField(max_length=2000)),
('url', models.URLField(max_length=765)),
],
),
migrations.CreateModel(
Expand Down
7 changes: 3 additions & 4 deletions hosts/templatetags/report_alert.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
# Copyright 2016-2021 Marcus Furlong <furlongm@gmail.com>
# Copyright 2016-2025 Marcus Furlong <furlongm@gmail.com>
#
# This file is part of Patchman.
#
# Patchman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# the Free Software Foundation, version 3 only.
#
# Patchman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Patchman If not, see <http://www.gnu.org/licenses/>.
# along with Patchman. If not, see <http://www.gnu.org/licenses/>

from datetime import timedelta

Expand Down
4 changes: 2 additions & 2 deletions modules/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

def get_or_create_module(name, stream, version, context, arch, repo):
""" Get or create a module object
Returns the module and a boolean for created
Returns the module
"""
created = False
m_arch, c = PackageArchitecture.objects.get_or_create(name=arch)
Expand All @@ -46,7 +46,7 @@ def get_or_create_module(name, stream, version, context, arch, repo):
arch=m_arch,
repo=repo,
)
return module, created
return module


def get_matching_modules(name, stream, version, context, arch):
Expand Down
6 changes: 3 additions & 3 deletions packages/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,11 @@ def __str__(self):
rel = f'-{self.release}'
else:
rel = ''
if self.packagetype == self.GENTOO:
if self.packagetype == Package.GENTOO:
return f'{self.category}/{self.name}-{epo}{self.version}{rel}-{self.arch}.{self.get_packagetype_display()}'
elif self.packagetype in [self.DEB, self.ARCH]:
elif self.packagetype in [Package.DEB, Package.ARCH]:
return f'{self.name}_{epo}{self.version}{rel}_{self.arch}.{self.get_packagetype_display()}'
elif self.packagetype == self.RPM:
elif self.packagetype == Package.RPM:
return f'{self.name}-{epo}{self.version}{rel}-{self.arch}.{self.get_packagetype_display()}'
else:
return f'{self.name}-{epo}{self.version}{rel}-{self.arch}.{self.get_packagetype_display()}'
Expand Down
5 changes: 5 additions & 0 deletions patchman/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from django.apps import AppConfig


class PatchmanConfig(AppConfig):
name = 'patchman'
Empty file added patchman/management/__init__.py
Empty file.
Empty file.
33 changes: 33 additions & 0 deletions patchman/management/commands/createsuperuser_with_password.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from django.contrib.auth.management.commands import createsuperuser
from django.core.management import CommandError


class Command(createsuperuser.Command):
help = 'Crate a superuser, and allow password to be provided'

def add_arguments(self, parser):
super().add_arguments(parser)
parser.add_argument(
'--password', dest='password', default=None,
help='Specifies the password for the superuser.',
)

def handle(self, *args, **options):
password = options.get('password')
username = options.get('username')
database = options.get('database')

if options['interactive']:
raise CommandError(
'Command is required to run with --no-input option')
if password and not username:
raise CommandError(
'--username is required if specifying --password')

super().handle(*args, **options)

if password:
user = self.UserModel._default_manager.db_manager(
database).get(username=username)
user.set_password(password)
user.save()
17 changes: 17 additions & 0 deletions patchman/management/commands/set_rdns_check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.core.management.base import BaseCommand, CommandError
from hosts.models import Host


class Command(BaseCommand):
help = 'Enable/Disable rDNS check for hosts'

def add_arguments(self, parser):
parser.add_argument(
'--disable', action='store_false', default=True, dest='rdns_check',
help='If set, disables rDNS check')

def handle(self, *args, **options):
try:
Host.objects.all().update(check_dns=options['rdns_check'])
except Exception as e:
raise CommandError('Failed to update rDNS check', str(e))
52 changes: 52 additions & 0 deletions patchman/management/commands/set_secret_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import os
import re
import sys
import codecs
from random import choice
from tempfile import NamedTemporaryFile
from shutil import copy

from django.core.management.base import BaseCommand


class Command(BaseCommand):
help = 'Set SECRET_KEY of Patchman Application.'

def add_arguments(self, parser):
parser.add_argument(
'--key', help=(
'The SECRET_KEY to be used by Patchman. If not set, a random '
'key of length 50 will be created.'))

@staticmethod
def get_random_key():
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
return ''.join([choice(chars) for i in range(50)])

def handle(self, *args, **options):
secret_key = options.get('key', self.get_random_key())

if sys.prefix == '/usr':
conf_path = '/etc/patchman'
else:
conf_path = os.path.join(sys.prefix, 'etc/patchman')
# if conf_path doesn't exist, try ./etc/patchman
if not os.path.isdir(conf_path):
conf_path = './etc/patchman'
local_settings = os.path.join(conf_path, 'local_settings.py')

settings_contents = codecs.open(
local_settings, 'r', encoding='utf-8').read()
settings_contents = re.sub(
r"(?<=SECRET_KEY = ')'", secret_key + "'", settings_contents)

f = NamedTemporaryFile(delete=False)
temp = f.name
f.close()

fh = codecs.open(temp, 'w+b', encoding='utf-8')
fh.write(settings_contents)
fh.close()

copy(temp, local_settings)
os.remove(temp)
23 changes: 23 additions & 0 deletions patchman/management/commands/set_site.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from django.core.management.base import BaseCommand, CommandError
from django.contrib.sites.models import Site
from django.conf import settings


class Command(BaseCommand):
help = 'Set Patchman Site Name'

def add_arguments(self, parser):
parser.add_argument(
'-n', '--name', dest='site_name', help='Site name')
parser.add_argument(
'--clear-cache', action='store_true', default=False,
dest='clear_cache', help='Clear Site cache')

def handle(self, *args, **options):
try:
Site.objects.filter(pk=settings.SITE_ID).update(
name=options['site_name'], domain=options['site_name'])
if options['clear_cache']:
Site.objects.clear_cache()
except Exception as e:
raise CommandError('Failed to update Site name', str(e))
1 change: 1 addition & 0 deletions patchman/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
'security.apps.SecurityConfig',
'reports.apps.ReportsConfig',
'util.apps.UtilConfig',
'patchman.apps.PatchmanConfig',
]

REST_FRAMEWORK = {
Expand Down
1 change: 0 additions & 1 deletion patchman/wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
# along with Patchman. If not, see <http://www.gnu.org/licenses/>

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'patchman.settings') # noqa
Expand Down
2 changes: 1 addition & 1 deletion repos/repo_types/yum.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def extract_module_metadata(data, url, repo):
packages.add(package)

from modules.utils import get_or_create_module
module, created = get_or_create_module(m_name, m_stream, m_version, m_context, arch, repo)
module = get_or_create_module(m_name, m_stream, m_version, m_context, arch, repo)

package_ids = []
for package in packages:
Expand Down
7 changes: 3 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
Django==4.2.20
Django==4.2.25
django-taggit==4.0.0
django-extensions==3.2.3
django-bootstrap3==23.1
python-debian==1.0.1
defusedxml==0.7.1
PyYAML==6.0.2
chardet==5.2.0
requests==2.32.3
requests==2.32.4
colorama==0.4.6
djangorestframework==3.15.2
django-filter==25.1
Expand All @@ -16,7 +15,7 @@ python-magic==0.4.27
gitpython==3.1.44
tenacity==8.2.3
celery==5.4.0
redis==5.2.1
redis==6.4.0
django-celery-beat==2.7.0
tqdm==4.67.1
cvss==3.4
4 changes: 2 additions & 2 deletions security/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cwe_id', models.CharField(max_length=255, unique=True)),
('name', models.CharField(blank=True, max_length=255, null=True)),
('description', models.CharField(blank=True, max_length=65535, null=True)),
('description', models.CharField(blank=True, max_length=255, null=True)),
],
),
migrations.CreateModel(
Expand All @@ -36,7 +36,7 @@ class Migration(migrations.Migration):
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cve_id', models.CharField(max_length=255, unique=True)),
('title', models.CharField(blank=True, max_length=255, null=True)),
('description', models.CharField(max_length=65535)),
('description', models.CharField(max_length=255)),
('reserved_date', models.DateTimeField(blank=True, null=True)),
('published_date', models.DateTimeField(blank=True, null=True)),
('rejected_date', models.DateTimeField(blank=True, null=True)),
Expand Down
2 changes: 1 addition & 1 deletion security/migrations/0005_reference_cve_references.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Migration(migrations.Migration):
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('ref_type', models.CharField(max_length=255)),
('url', models.URLField(max_length=2000)),
('url', models.URLField(max_length=765)),
],
options={
'unique_together': {('ref_type', 'url')},
Expand Down
17 changes: 9 additions & 8 deletions security/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
class Reference(models.Model):

ref_type = models.CharField(max_length=255)
url = models.URLField(max_length=2000)
url = models.URLField(max_length=765)

class Meta:
unique_together = ['ref_type', 'url']
Expand Down Expand Up @@ -125,19 +125,20 @@ def add_cvss_score(self, vector_string, score=None, severity=None, version=None)
score = cvss_score.base_score
if not severity:
severity = cvss_score.severities()[0]
existing = self.cvss_scores.filter(version=version, vector_string=vector_string)
if existing:
cvss = existing.first()
else:
try:
cvss, created = CVSS.objects.get_or_create(
version=version,
vector_string=vector_string,
score=score,
severity=severity,
)
cvss.score = score
cvss.severity = severity
cvss.save()
except CVSS.MultipleObjectsReturned:
matching_cvsses = CVSS.objects.filter(
version=version,
vector_string=vector_string,
)
cvss = matching_cvsses.first()
matching_cvsses.exclude(id=cvss.id).delete()
self.cvss_scores.add(cvss)

def fetch_cve_data(self, fetch_nist_data=False, sleep_secs=6):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
# Copyright 2013-2021 Marcus Furlong <furlongm@gmail.com>
# Copyright 2013-2025 Marcus Furlong <furlongm@gmail.com>
#
# This file is part of Patchman.
#
Expand Down
13 changes: 10 additions & 3 deletions util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import magic
import zlib
import lzma
import os
from datetime import datetime, timezone
from enum import Enum
from hashlib import md5, sha1, sha256, sha512
Expand All @@ -28,17 +29,23 @@
from time import time
from tqdm import tqdm

from patchman.signals import error_message, info_message, debug_message

from django.utils.timezone import make_aware
from django.utils.dateparse import parse_datetime
from django.conf import settings

from patchman.signals import error_message, info_message, debug_message

pbar = None
verbose = None
Checksum = Enum('Checksum', 'md5 sha sha1 sha256 sha512')

http_proxy = os.getenv('http_proxy')
https_proxy = os.getenv('https_proxy')
proxies = {
'http': http_proxy,
'https': https_proxy,
}


def get_verbosity():
""" Get the global verbosity level
Expand Down Expand Up @@ -113,7 +120,7 @@ def get_url(url, headers={}, params={}):
response = None
try:
debug_message.send(sender=None, text=f'Trying {url} headers:{headers} params:{params}')
response = requests.get(url, headers=headers, params=params, stream=True, timeout=30)
response = requests.get(url, headers=headers, params=params, stream=True, proxies=proxies, timeout=30)
debug_message.send(sender=None, text=f'{response.status_code}: {response.headers}')
if response.status_code in [403, 404]:
return response
Expand Down
7 changes: 3 additions & 4 deletions util/filterspecs.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
# Copyright 2010 VPAC
# Copyright 2014-2021 Marcus Furlong <furlongm@gmail.com>
# Copyright 2014-2025 Marcus Furlong <furlongm@gmail.com>
#
# This file is part of Patchman.
#
# Patchman is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# the Free Software Foundation, version 3 only.
#
# Patchman is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Patchman If not, see <http://www.gnu.org/licenses/>.
# along with Patchman. If not, see <http://www.gnu.org/licenses/>

from django.utils.safestring import mark_safe
from django.db.models.query import QuerySet
Expand Down
Loading