Skip to content

Commit 2e51aa2

Browse files
committed
Dockerize and add new feature
1 parent 5b7bfd3 commit 2e51aa2

File tree

3 files changed

+168
-3
lines changed

3 files changed

+168
-3
lines changed

.github/workflows/docker-publish.yml

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
name: Docker
2+
3+
on:
4+
push:
5+
branches: ["master"]
6+
tags: ["*.*.*"]
7+
workflow_dispatch:
8+
9+
env:
10+
REGISTRY: ghcr.io
11+
12+
jobs:
13+
build:
14+
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
17+
packages: write
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
platform:
22+
- linux/386
23+
- linux/amd64
24+
- linux/arm/v6
25+
- linux/arm/v7
26+
- linux/arm64/v8
27+
- linux/ppc64le
28+
- linux/s390x
29+
steps:
30+
- name: Checkout repository
31+
uses: actions/checkout@v4
32+
33+
- name: Convert image name to lowercase
34+
run: |
35+
GITHUB_REPOSITORY="${{ github.repository }}"
36+
echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "${GITHUB_ENV}"
37+
38+
- name: Set up Docker Buildx
39+
uses: docker/setup-buildx-action@v3 # v3.0.0
40+
41+
- name: "Log into registry ${{ env.REGISTRY }}"
42+
uses: docker/login-action@v3 # v3.0.0
43+
with:
44+
registry: "${{ env.REGISTRY }}"
45+
username: "${{ github.actor }}"
46+
password: "${{ secrets.GITHUB_TOKEN }}"
47+
48+
- name: Extract Docker metadata
49+
id: meta
50+
uses: docker/metadata-action@v5 # v5.0.0
51+
with:
52+
images: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
53+
54+
- name: Build and push Docker image
55+
id: build
56+
uses: docker/build-push-action@v5 # v5.0.0
57+
with:
58+
context: .
59+
labels: "${{ steps.meta.outputs.labels }}"
60+
cache-from: type=gha
61+
cache-to: type=gha,mode=max
62+
platforms: "${{ matrix.platform }}"
63+
provenance: true
64+
sbom: true
65+
outputs: "type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true"
66+
67+
- name: Export digest
68+
run: |
69+
mkdir -p "/tmp/digests/"
70+
digest="${{ steps.build.outputs.digest }}"
71+
touch "/tmp/digests/${digest#sha256:}"
72+
73+
- name: "Get arch name (compatible with docker manifest)"
74+
id: get_arch_name
75+
run: |
76+
SAFENAME="$(echo "${{ matrix.platform }}" | sed 's/\//_/g')"
77+
echo "ARCH_NAME=${SAFENAME}" >> "${GITHUB_OUTPUT}"
78+
79+
- name: Upload digest
80+
uses: actions/upload-artifact@v4
81+
with:
82+
name: "digests-plat-${{ steps.get_arch_name.outputs.ARCH_NAME }}"
83+
path: "/tmp/digests/*"
84+
if-no-files-found: error
85+
retention-days: 1
86+
87+
merge:
88+
runs-on: ubuntu-latest
89+
needs:
90+
- build
91+
permissions:
92+
packages: write
93+
steps:
94+
- name: Convert image name to lowercase
95+
run: |
96+
GITHUB_REPOSITORY="${{ github.repository }}"
97+
echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "${GITHUB_ENV}"
98+
99+
- name: Download digests
100+
uses: actions/download-artifact@v4
101+
with:
102+
path: "/tmp/digests/"
103+
104+
- name: Move to one directory
105+
run: |
106+
mkdir -p /tmp/digests-all
107+
mv /tmp/digests/*/* /tmp/digests-all
108+
109+
- name: Set up Docker Buildx
110+
uses: docker/setup-buildx-action@v3
111+
112+
- name: Checkout repository
113+
uses: actions/checkout@v4
114+
115+
- name: Docker meta
116+
id: meta
117+
uses: docker/metadata-action@v5
118+
with:
119+
images: "${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}"
120+
121+
- name: "Log into registry ${{ env.REGISTRY }}"
122+
uses: docker/login-action@v3
123+
with:
124+
registry: "${{ env.REGISTRY }}"
125+
username: "${{ github.actor }}"
126+
password: "${{ secrets.GITHUB_TOKEN }}"
127+
128+
- name: Create manifest list and push
129+
working-directory: "/tmp/digests-all"
130+
run: |
131+
# shellcheck disable=SC2046
132+
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)

Dockerfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM python:3.13.0-alpine
2+
3+
WORKDIR /app
4+
COPY ./requirements.txt /app/requirements.txt
5+
RUN pip install --upgrade pip
6+
RUN pip install -r /app/requirements.txt
7+
8+
COPY ./main.py /app/main.py
9+
10+
ENTRYPOINT ["python3", "/app/main.py"]

main.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import argparse
2+
import socket
23

34
from OpenSSL import crypto
45
import re
56
from requests import Session
67
import sys
8+
from ssl import get_server_certificate
79

810
ca_url_pattern = re.compile(r'CA Issuers - URI:(\S+)')
911

@@ -68,13 +70,34 @@ def build_chain(self) -> str:
6870

6971
def main(args=None):
7072
parser = argparse.ArgumentParser(description="Build a certificate chain from a certificate")
71-
parser.add_argument("cert", help="The certificate to build the chain from")
73+
parser.add_argument("cert", help="The certificate to build the chain from (either a file path or a hostname/hostname+port combination, use '-' to read from stdin)")
7274
parser.add_argument("--out", help="The file to write the chain to (otherwise writes to stdout)")
7375
parser.add_argument("--verbose", help="Logs intermediate certificate URLs", action="store_true")
7476
parser.add_argument("--save-intermediate-certificates", help="Saves intermediate certificates", action="store_true")
7577
args = parser.parse_args(args)
76-
with open(args.cert, "rb") as f:
77-
cert_data = f.read()
78+
if args.cert == "-":
79+
cert_data = sys.stdin.buffer.read()
80+
else:
81+
try:
82+
with open(args.cert, "rb") as f:
83+
cert_data = f.read()
84+
except FileNotFoundError:
85+
# Treat it as a hostname or hostname+port combination
86+
if "/" in args.cert:
87+
print("Invalid certificate path: " + args.cert, file=sys.stderr)
88+
sys.exit(1)
89+
hostname, sep, port = args.cert.partition(":")
90+
if not sep:
91+
port = 443
92+
else:
93+
port = int(port)
94+
try:
95+
if args.verbose:
96+
print(f"Fetching certificate for {hostname}:{port}")
97+
cert_data = get_server_certificate((hostname, port)).encode("utf-8")
98+
except socket.gaierror:
99+
print(f"Could not resolve hostname: {hostname}", file=sys.stderr)
100+
sys.exit(1)
78101
builder = CertChainBuilder(verbose=args.verbose)
79102
builder.feed(cert_data)
80103
chain = builder.build_chain()

0 commit comments

Comments
 (0)