Skip to content

Commit 852d918

Browse files
MK8S-25: Disable HTTP directory listing for RPM repository
Security fix to prevent exposing repository structure on port 8080. Changed autoindex from on to off in nginx configuration. Create empty index.html files only in repository-specific directories to prevent 403 errors when autoindex is disabled, while avoiding doit task conflicts from multiple repositories targeting the same shared directory files. This maintains the original health check functionality while preventing directory structure exposure. Related: RD-680
1 parent 5dab1dc commit 852d918

File tree

4 files changed

+134
-8
lines changed

4 files changed

+134
-8
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
### Bug Fixes
1919

20+
- Disable HTTP directory listing for RPM repository to improve security
21+
(PR[#4651](https://github.com/scality/metalk8s/pull/4651))
22+
2023
- Fix a Bug where NodeSystemSaturation alert triggers too early after only 15 minutes of high load
2124
(PR[#4641](https://github.com/scality/metalk8s/pull/4641))
2225

buildchain/buildchain/packaging.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import doit # type: ignore
3333

3434
from buildchain import builder
35+
from buildchain import config
3536
from buildchain import constants
3637
from buildchain import coreutils
3738
from buildchain import docker_command
@@ -45,7 +46,7 @@
4546

4647

4748
def _list_packages_to_build(
48-
pkg_cats: Mapping[str, Mapping[str, Tuple[targets.Package, ...]]]
49+
pkg_cats: Mapping[str, Mapping[str, Tuple[targets.Package, ...]]],
4950
) -> Dict[str, List[str]]:
5051
return {
5152
version: [pkg.name for pkg in pkg_list]
@@ -149,11 +150,27 @@ def task__package_mkdir_iso_root() -> types.TaskDict:
149150

150151
def task__package_mkdir_redhat_iso_root() -> types.TaskDict:
151152
"""Create the RedHat packages root directory on the ISO."""
152-
return targets.Mkdir(
153+
154+
def clean() -> None:
155+
"""Clean RedHat directory including saltenv directory structure."""
156+
# Remove saltenv directory structure created by repository index files
157+
saltenv_dir = (
158+
constants.REPO_REDHAT_ROOT
159+
/ f"{config.PROJECT_NAME.lower()}-{versions.VERSION}"
160+
)
161+
if saltenv_dir.exists():
162+
coreutils.rm_rf(saltenv_dir)
163+
164+
mkdir_task = targets.Mkdir(
153165
directory=constants.REPO_REDHAT_ROOT,
154166
task_dep=["_package_mkdir_iso_root"],
155167
).task
156168

169+
# Add our custom cleanup function
170+
mkdir_task["clean"] = [clean]
171+
172+
return mkdir_task
173+
157174

158175
def _package_mkdir_redhat_release_iso_root(releasever: str) -> types.TaskDict:
159176
"""
@@ -263,6 +280,7 @@ def task__build_redhat_8_repositories() -> Iterator[types.TaskDict]:
263280
# }}}
264281
# RPM packages and repository {{{
265282

283+
266284
# Packages to build, per repository.
267285
def _rpm_package(name: str, releasever: str, sources: List[Path]) -> targets.RPMPackage:
268286
try:
@@ -362,11 +380,11 @@ def _rpm_package_metalk8s_sosreport(releasever: str) -> targets.RPMPackage:
362380

363381

364382
# Store these versions in a dict to use with doit.tools.config_changed
365-
_TO_DOWNLOAD_RPM_CONFIG: Dict[
366-
str, Dict[str, Optional[str]]
367-
] = _list_packages_to_download(
368-
versions.REDHAT_PACKAGES,
369-
_RPM_TO_BUILD_PKG_NAMES,
383+
_TO_DOWNLOAD_RPM_CONFIG: Dict[str, Dict[str, Optional[str]]] = (
384+
_list_packages_to_download(
385+
versions.REDHAT_PACKAGES,
386+
_RPM_TO_BUILD_PKG_NAMES,
387+
)
370388
)
371389

372390

buildchain/buildchain/targets/repository.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
from buildchain import constants
4141
from buildchain import types
4242
from buildchain import utils
43+
from buildchain import versions
4344
from buildchain import docker_command
4445

4546
from . import base
@@ -206,6 +207,107 @@ def clean() -> None:
206207
task["file_dep"].extend([self.get_rpm_path(pkg) for pkg in self.packages])
207208
return task
208209

210+
def create_index_files(self) -> types.TaskDict:
211+
"""Create index.html files for repository directories."""
212+
213+
def clean() -> None:
214+
"""Delete the index.html files created by this task."""
215+
# Clean repository-specific index files
216+
index_files = [
217+
self.rootdir / "index.html",
218+
self.rootdir / self.ARCH / "index.html",
219+
]
220+
# Clean shared directory index files if this is scality repo
221+
if self.name == "scality":
222+
saltenv_dir = (
223+
self._repo_root
224+
/ f"{config.PROJECT_NAME.lower()}-{versions.VERSION}"
225+
)
226+
index_files.extend(
227+
[
228+
saltenv_dir / "index.html",
229+
saltenv_dir / "redhat" / "index.html",
230+
saltenv_dir / "redhat" / str(self._releasever) / "index.html",
231+
]
232+
)
233+
234+
for index_file in index_files:
235+
if index_file.exists():
236+
index_file.unlink()
237+
238+
def create_index() -> None:
239+
"""Create empty index.html files in repository directories."""
240+
repository_directories = [
241+
self.rootdir, # Repository root (e.g., metalk8s-scality-el8)
242+
self.rootdir / self.ARCH, # Architecture directory (e.g., x86_64)
243+
]
244+
245+
# Also create shared directory index files, but only from scality repo
246+
# to avoid doit task conflicts between multiple repositories
247+
if self.name == "scality":
248+
saltenv_dir = (
249+
self._repo_root
250+
/ f"{config.PROJECT_NAME.lower()}-{versions.VERSION}"
251+
)
252+
repository_directories.extend(
253+
[
254+
saltenv_dir, # /{saltenv}/ (needed for health check)
255+
saltenv_dir / "redhat",
256+
saltenv_dir / "redhat" / str(self._releasever),
257+
]
258+
)
259+
260+
for repository_directory in repository_directories:
261+
# Create directory if it doesn't exist, then create index.html
262+
repository_directory.mkdir(parents=True, exist_ok=True)
263+
index_file = repository_directory / "index.html"
264+
index_file.touch()
265+
# Set readable permissions for nginx
266+
index_file.chmod(0o644)
267+
268+
# Build targets list - include shared directories only for scality repo
269+
targets = [
270+
self.rootdir / "index.html",
271+
self.rootdir / self.ARCH / "index.html",
272+
]
273+
if self.name == "scality":
274+
saltenv_dir = (
275+
self._repo_root / f"{config.PROJECT_NAME.lower()}-{versions.VERSION}"
276+
)
277+
targets.extend(
278+
[
279+
saltenv_dir / "index.html",
280+
saltenv_dir / "redhat" / "index.html",
281+
saltenv_dir / "redhat" / str(self._releasever) / "index.html",
282+
]
283+
)
284+
285+
task = self.basic_task
286+
task.update(
287+
{
288+
"name": self._get_task_name("create_index_files"),
289+
"actions": [create_index],
290+
"doc": f"Create index.html files for {self.name} repository "
291+
f"directories.",
292+
"title": utils.title_with_target1("CREATE INDEX FILES"),
293+
"targets": targets,
294+
"uptodate": [True],
295+
"verbosity": 0,
296+
"clean": [clean],
297+
"task_dep": [self._get_task_name("build_repodata", with_basename=True)],
298+
}
299+
)
300+
return task
301+
302+
@property
303+
def execution_plan(self) -> List[types.TaskDict]:
304+
"""Override execution plan to include index.html file creation."""
305+
tasks = [self.build_repo()]
306+
if self._packages:
307+
tasks.extend(self.build_packages())
308+
tasks.append(self.create_index_files())
309+
return tasks
310+
209311
def build_packages(self) -> List[types.TaskDict]:
210312
"""Build the RPMs from SRPMs."""
211313
tasks = [self._mkdir_repo_root(), self._mkdir_repo_arch()]

salt/metalk8s/repo/files/nginx.conf.j2

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ server {
44

55
location / {
66
root /var/www/repositories;
7-
autoindex on;
7+
# Security fix: Disable directory listing to prevent exposing repository structure
8+
autoindex off;
9+
# Serve index.html when directory is accessed
10+
index index.html;
811
}
912

1013
include conf.d/*.inc;

0 commit comments

Comments
 (0)