Skip to content

Commit caa168c

Browse files
committed
Make testssl parallel
1 parent 40e7eb1 commit caa168c

16 files changed

+127
-103
lines changed

.env

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
ENVIRONMENT=development
22
DEBUG=true
3+
TEST_SSL_WORKDIR=/home/testssl_user
34
TEST_SSL_CONTAINER_NAME=testssl.sh
45
TEST_SSL_OUTPUT_FILE=output.json
5-
TEST_SSL_INPUT_FILE=input.txt
6-
TEST_SSL_DATA_DIR=/data
6+
TEST_SSL_INPUT_FILE=/data/input.txt
7+
TEST_SSL_COMMANDS_FILE=commands.txt
78
CLICKHOUSE_HOST=clickhouse
89
CLICKHOUSE_PORT=8123
910
CLICKHOUSE_USER=default

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ SOURCES = app tests
22

33
.DEFAULT_GOAL := help
44

5-
DOCKER_COMPOSE_FILE = contrib/docker-compose.yml
5+
DOCKER_COMPOSE_FILE = deploy/docker-compose.clickhouse.yaml
66
DOCKER_COMPOSE_PROJECT_NAME = app
77

88
help: ## Display this help screen
@@ -33,7 +33,7 @@ run: ## Run the development Django server
3333
.PHONY: run
3434

3535
compose-up: ## Run the development Django server with docker-compose
36-
COMPOSE_PROJECT_NAME=${DOCKER_COMPOSE_PROJECT_NAME} docker-compose -f ${DOCKER_COMPOSE_FILE} --env-file .env up --build --remove-orphans --force-recreate
36+
docker-compose --project-name ${DOCKER_COMPOSE_PROJECT_NAME} --env-file .env -f ${DOCKER_COMPOSE_FILE} up --force-recreate --build --remove-orphans
3737
.PHONY: compose-up
3838

3939
compose-down: ## Stop the development Django server with docker-compose

app/lib/testssl/__init__.py

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from .container import TestSSLContainer
2+
from .parser import TestSSLJsonParser
3+
4+
__all__ = [
5+
"TestSSLContainer",
6+
"TestSSLJsonParser",
7+
]

app/lib/docker.py app/lib/testssl/container.py

+21-5
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@ def __init__(
1818
*,
1919
client: docker.DockerClient,
2020
container_name: str,
21-
output_path: pathlib.Path,
21+
workdir: pathlib.Path,
22+
output_file_name: pathlib.Path,
23+
commands_file_name: pathlib.Path,
2224
) -> None:
2325
self._client = client
2426
self._container_name = container_name
25-
self._output_path = output_path
27+
self._workdir = workdir
28+
self._output_file_name = output_file_name
29+
self._commands_file_name = commands_file_name
2630

2731
@property
2832
def container(self) -> Container:
@@ -34,20 +38,32 @@ def stop(self) -> None:
3438
def wait_for_complete(self) -> None:
3539
try:
3640
logger.info("Waiting for container to complete")
37-
self.container.wait()
41+
_, output = self.container.exec_run(
42+
f"parallel --jobs 16 --bar -a {self._commands_file_name.as_posix()}",
43+
tty=True,
44+
stream=True,
45+
user="testssl_user",
46+
workdir=self._workdir.as_posix(),
47+
)
48+
for line in output:
49+
logger.info(line.decode("utf-8").strip())
50+
logger.info("Finished executing commands")
3851
except requests.exceptions.ReadTimeout:
3952
logger.error("Container timeout")
4053
self.container.kill()
4154

4255
def get_json(self) -> list[TestSSLRecord]:
43-
tar_gz, _ = self.container.get_archive(self._output_path, encode_stream=True)
56+
logger.info(self._workdir / self._output_file_name.name)
57+
tar_gz, _ = self.container.get_archive(
58+
self._workdir / self._output_file_name.name, encode_stream=True
59+
)
4460

4561
with tarfile.open(
4662
fileobj=io.BytesIO(b"".join(tar_gz)),
4763
mode="r",
4864
) as tar:
4965
try:
50-
if _file := tar.extractfile(self._output_path.name):
66+
if _file := tar.extractfile(self._output_file_name.name):
5167
json_bytes = _file.read()
5268
else:
5369
raise KeyError
File renamed without changes.

app/repository/provider.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
from loguru import logger
66

77
from app.dto.entities.fqdn import FQDN
8-
from app.lib.docker import TestSSLContainer
9-
from app.lib.test_ssl_parser import TestSSLJsonParser
8+
from app.lib.testssl import TestSSLContainer, TestSSLJsonParser
109

1110

1211
@t.final

app/services/service.py

+6-5
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import docker
66
from loguru import logger
77

8-
from app.lib.docker import TestSSLContainer
9-
from app.lib.test_ssl_parser import TestSSLJsonParser
8+
from app.lib.testssl import TestSSLContainer, TestSSLJsonParser
109
from app.repository.db import DB
1110
from app.repository.provider import TestSSLDataProviderRepository
1211
from app.services.ssl_checker import SSLCheckerService
@@ -20,22 +19,24 @@
2019
user=settings.CLICKHOUSE_USER,
2120
password=settings.CLICKHOUSE_PASSWORD,
2221
secure=False,
23-
connect_timeout=15,
22+
connect_timeout=settings.CLICKHOUSE_CONNECT_TIMEOUT,
2423
)
2524

2625
test_ssl_parser = TestSSLJsonParser()
2726
test_ssl_container = TestSSLContainer(
2827
client=docker_client,
2928
container_name=settings.TEST_SSL_CONTAINER_NAME,
30-
output_path=settings.TEST_SSL_DATA_DIR / settings.TEST_SSL_OUTPUT_FILE,
29+
workdir=settings.TEST_SSL_WORKDIR,
30+
output_file_name=settings.TEST_SSL_OUTPUT_FILE,
31+
commands_file_name=settings.TEST_SSL_COMMANDS_FILE,
3132
)
3233

3334
# Repository Layer
3435
db = DB(client=clickhouse_client, table_name=settings.CLICKHOUSE_TABLE_NAME)
3536
data_provider_repo = TestSSLDataProviderRepository(
3637
container=test_ssl_container,
3738
json_parser=test_ssl_parser,
38-
input_path=settings.TEST_SSL_DATA_DIR / settings.TEST_SSL_INPUT_FILE,
39+
input_path=settings.TEST_SSL_INPUT_FILE,
3940
)
4041

4142

app/settings.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ class Settings(BaseSettings):
1919
TEST_SSL_CONTAINER_NAME: str
2020
TEST_SSL_OUTPUT_FILE: pathlib.Path
2121
TEST_SSL_INPUT_FILE: pathlib.Path
22-
TEST_SSL_DATA_DIR: pathlib.Path
22+
TEST_SSL_COMMANDS_FILE: pathlib.Path
23+
TEST_SSL_WORKDIR: pathlib.Path
2324

2425
CLICKHOUSE_HOST: str
2526
CLICKHOUSE_PORT: int
2627
CLICKHOUSE_USER: str
2728
CLICKHOUSE_PASSWORD: str
2829
CLICKHOUSE_TABLE_NAME: str
30+
CLICKHOUSE_CONNECT_TIMEOUT: int = 15
2931

3032
@property
3133
def is_production(self) -> bool:

deploy/common.yaml

-24
This file was deleted.

deploy/data/output.json

-16
This file was deleted.

deploy/docker-compose.mongo.yaml

-39
This file was deleted.

deploy/docker-compose.clickhouse.yaml deploy/docker-compose.yaml

+24-7
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,39 @@ version: "3.9"
33
services:
44
web:
55
container_name: web
6-
extends:
7-
file: common.yaml
8-
service: web
6+
build:
7+
context: ..
8+
dockerfile: deploy/web/Dockerfile.win
9+
env_file:
10+
- ../.env
11+
volumes:
12+
- ../data:/data
13+
- /var/run/docker.sock:/var/run/docker.sock
914
depends_on:
1015
- clickhouse
16+
- testssl.sh
1117
networks:
1218
- clickhouse-network
1319

1420
testssl.sh:
1521
container_name: testssl.sh
16-
extends:
17-
file: common.yaml
18-
service: testssl.sh
22+
build:
23+
context: ..
24+
dockerfile: deploy/testssl.Dockerfile
25+
environment:
26+
- MASS_TESTING_MODE=parallel
27+
- TEST_SSL_INPUT_FILE=./data/input.txt
28+
env_file:
29+
- ../.env
30+
deploy:
31+
resources:
32+
reservations:
33+
cpus: "4"
34+
memory: 4G
1935

2036
clickhouse:
2137
container_name: clickhouse
2238
image: clickhouse/clickhouse-server
23-
restart: always
2439
environment:
2540
- CLICKHOUSE_DB=${CLICKHOUSE_DB}
2641
- CLICKHOUSE_USER=${CLICKHOUSE_USER}
@@ -29,6 +44,8 @@ services:
2944
ports:
3045
- ${CLICKHOUSE_PORT}:8123
3146
- 9000:9000
47+
env_file:
48+
- ../.env
3249
networks:
3350
- clickhouse-network
3451
volumes:

deploy/testssl.Dockerfile

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Dockerfile for running testssl.sh in parallel
2+
FROM ubuntu:latest
3+
4+
# Create user
5+
RUN groupadd -r testssl_user && useradd -r -g testssl_user testssl_user
6+
7+
# Update and install necessary packages
8+
RUN apt-get update && apt-get install -y bash wget parallel bsdmainutils dnsutils
9+
RUN wget -O testssl.tar.gz https://github.com/drwetter/testssl.sh/archive/master.tar.gz && \
10+
mkdir /opt/testssl && \
11+
tar xf testssl.tar.gz --strip-components=1 -C /opt/testssl && \
12+
ln -s /opt/testssl/testssl.sh /usr/local/bin/testssl.sh && \
13+
rm -rf testssl.tar.gz && \
14+
mkdir -p ~/.parallel && touch ~/.parallel/will-cite && \
15+
chown -R testssl_user:testssl_user /opt/testssl /usr/local/bin/testssl.sh
16+
17+
# Switch to the non-root user
18+
USER testssl_user
19+
WORKDIR /home/testssl_user
20+
21+
# Copy necessary files
22+
COPY --chown=testssl_user:testssl_user ./scripts/get_cmd_lines.sh ./get_cmd_lines.sh
23+
COPY --chown=testssl_user:testssl_user ./data/input.txt ./input.txt
24+
25+
# Generate parallel commands as the non-root user
26+
RUN ./get_cmd_lines.sh ./input.txt ./commands.txt
27+
28+
ENTRYPOINT [ "tail", "-f", "/dev/null" ]

scripts/get_cmd_lines.sh

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
3+
# Check if the input file exists
4+
if [ ! -f "$1" ]; then
5+
echo "Input file not found: $1"
6+
exit 1
7+
fi
8+
9+
# Remove output file if it already exists
10+
if [ -f "$2" ]; then
11+
rm "$2"
12+
fi
13+
14+
while IFS= read -r line || [ -n "$line" ]; do
15+
host=$(echo "$line" | cut -d: -f1)
16+
port=$(echo "$line" | cut -d: -f2)
17+
cmd="MAX_PARALLEL=100 testssl.sh --jsonfile output.json --mode parallel --quiet --protocols --server-defaults --append $host:$port"
18+
19+
echo "$cmd" >>"$2"
20+
done <"$1"
21+
22+
chmod +x "$2"
23+
echo "commands generated in $2"

tests/units/test_app/test_output.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+


tests/units/test_app/test_main.py tests/units/test_app/test_parser.py

+8
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
from app.main import main
44

5+
TEST_OUTPUT_JSON = """
6+
{
7+
"data": {
8+
"hello": "world"
9+
}
10+
}
11+
"""
12+
513

614
def test_main() -> None:
715
main()

0 commit comments

Comments
 (0)