Revert "sign_ssh_keys: Add hook to sign SSH host cert"
No longer using Step CA for SSH host certificates. Switched to sshca.
This reverts commit e5eff964a1
.
master
parent
e5eff964a1
commit
55df6f61a7
|
@ -1,4 +1,3 @@
|
|||
/.venv
|
||||
__pycache__/
|
||||
*.egg-info/
|
||||
provisioner.password
|
||||
|
|
|
@ -17,12 +17,9 @@ RUN --mount=type=cache,target=/var/cache/apt \
|
|||
RUN --mount=from=build,source=/tmp/build/dist,target=/tmp/wheels \
|
||||
python3 -m pip install -f /tmp/wheels \
|
||||
dch_webhooks \
|
||||
python-multipart \
|
||||
uvicorn \
|
||||
&& :
|
||||
|
||||
COPY --from=docker.io/smallstep/step-cli:0.25.0 /usr/local/bin/step /usr/local/bin/step
|
||||
|
||||
USER 1000:1000
|
||||
|
||||
CMD ["tini", "/usr/local/bin/uvicorn", "dch_webhooks:app"]
|
||||
|
|
131
dch_webhooks.py
131
dch_webhooks.py
|
@ -1,14 +1,10 @@
|
|||
import asyncio
|
||||
import base64
|
||||
import datetime
|
||||
import importlib.metadata
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from types import TracebackType
|
||||
from typing import Annotated, Optional, Self, Type
|
||||
from typing import Optional, Self, Type
|
||||
|
||||
import fastapi
|
||||
import httpx
|
||||
|
@ -38,17 +34,10 @@ EXCLUDE_DESCRIPTION_WORDS = {
|
|||
'the',
|
||||
}
|
||||
|
||||
ALLOW_RESIGNING_CERTS = os.environ.get('ALLOW_RESIGNING_CERTS') == '1'
|
||||
FIREFLY_URL = os.environ.get(
|
||||
'FIREFLY_URL',
|
||||
'http://firefly-iii',
|
||||
)
|
||||
MAX_KEY_FILE_SIZE = int(
|
||||
os.environ.get(
|
||||
'MAX_KEY_FILE_SIZE',
|
||||
8192,
|
||||
)
|
||||
)
|
||||
MAX_DOCUMENT_SIZE = int(
|
||||
os.environ.get(
|
||||
'MAX_DOCUMENT_SIZE',
|
||||
|
@ -61,10 +50,6 @@ PAPERLESS_URL = os.environ.get(
|
|||
)
|
||||
|
||||
|
||||
class SignError(Exception):
|
||||
...
|
||||
|
||||
|
||||
class FireflyIIITransactionSplit(pydantic.BaseModel):
|
||||
type: str
|
||||
date: datetime.datetime
|
||||
|
@ -93,12 +78,6 @@ class PaperlessNgxSearchResults(pydantic.BaseModel):
|
|||
results: list[PaperlessNgxDocument]
|
||||
|
||||
|
||||
class SSHKeySignResponse(pydantic.BaseModel):
|
||||
success: bool
|
||||
errors: Optional[list[str]] = None
|
||||
certificates: Optional[dict[str, str]]
|
||||
|
||||
|
||||
class HttpxClientMixin:
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
@ -165,13 +144,9 @@ class Firefly(HttpxClientMixin):
|
|||
rbody = r.json()
|
||||
attachment = rbody['data']
|
||||
url = f'{FIREFLY_URL}/api/v1/attachments/{attachment["id"]}/upload'
|
||||
r = await self.client.post(
|
||||
url,
|
||||
content=doc,
|
||||
headers={
|
||||
'Content-Type': 'application/octet-stream',
|
||||
},
|
||||
)
|
||||
r = await self.client.post(url, content=doc, headers={
|
||||
'Content-Type': 'application/octet-stream',
|
||||
})
|
||||
r.raise_for_status()
|
||||
|
||||
|
||||
|
@ -272,7 +247,9 @@ class Paperless(HttpxClientMixin):
|
|||
MAX_DOCUMENT_SIZE,
|
||||
)
|
||||
continue
|
||||
docs.append((response_filename(r), doc.title, await r.aread()))
|
||||
docs.append(
|
||||
(response_filename(r), doc.title, await r.aread())
|
||||
)
|
||||
return docs
|
||||
|
||||
|
||||
|
@ -302,12 +279,6 @@ async def handle_firefly_transaction(xact: FireflyIIITransaction) -> None:
|
|||
)
|
||||
|
||||
|
||||
async def check_host(hostname: str) -> bool:
|
||||
cmd = ['step', 'ssh', 'check-host', hostname]
|
||||
p = await asyncio.create_subprocess_exec(*cmd)
|
||||
return await p.wait() == 0
|
||||
|
||||
|
||||
def clean_description(text: str) -> str:
|
||||
matches = DESCRIPTION_CLEAN_PATTERN.sub('', text.lower())
|
||||
if not matches:
|
||||
|
@ -337,56 +308,6 @@ def response_filename(response: httpx.Response) -> str:
|
|||
return response.url.path.rstrip('/').rsplit('/', 1)[-1]
|
||||
|
||||
|
||||
async def sign_key(hostname, path: Path) -> tuple[str, str]:
|
||||
cmd = ['step', 'ssh', 'certificate', '--sign', '--host', hostname, path]
|
||||
p = await asyncio.create_subprocess_exec(
|
||||
*cmd,
|
||||
stdin=asyncio.subprocess.DEVNULL,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
)
|
||||
p_log = log.getChild('step')
|
||||
assert p.stdout
|
||||
buf = bytearray()
|
||||
while line := await p.stdout.readline():
|
||||
buf += line
|
||||
p_log.info(line.rstrip().decode('utf-8', 'replace'))
|
||||
rc = await p.wait()
|
||||
if rc != 0:
|
||||
raise SignError(
|
||||
f'Signing failed: process returned exit code {rc}: '
|
||||
f'{buf.decode("utf-8")}'
|
||||
)
|
||||
cert_path = path.parent / f'{path.stem}-cert.pub'
|
||||
log.info(
|
||||
'Successfully signed %s for %s as %s',
|
||||
path.name,
|
||||
hostname,
|
||||
cert_path.name,
|
||||
)
|
||||
with cert_path.open('r') as f:
|
||||
cert = await asyncio.to_thread(f.read)
|
||||
return (cert_path.name, cert)
|
||||
|
||||
|
||||
async def sign_uploaded_key(
|
||||
hostname: str, f: fastapi.UploadFile
|
||||
) -> tuple[str, str]:
|
||||
if f.size > MAX_KEY_FILE_SIZE:
|
||||
raise SignError(
|
||||
f'Refusing to sign key {f.filename}: file too large '
|
||||
f'({f.size} bytes, max {MAX_KEY_FILE_SIZE}'
|
||||
)
|
||||
with tempfile.TemporaryDirectory() as t:
|
||||
path = Path(t) / f.filename
|
||||
with path.open('wb') as o:
|
||||
d = await f.read(MAX_KEY_FILE_SIZE)
|
||||
if f.headers.get('Content-Transfer-Encoding') == 'base64':
|
||||
d = base64.b64decode(d)
|
||||
await asyncio.to_thread(o.write, d)
|
||||
return await sign_key(hostname, path)
|
||||
|
||||
|
||||
app = fastapi.FastAPI(
|
||||
name=DIST['Name'],
|
||||
version=DIST['Version'],
|
||||
|
@ -410,41 +331,3 @@ def status() -> str:
|
|||
@app.post('/hooks/firefly-iii/create')
|
||||
async def firefly_iii_create(hook: FireflyIIIWebhook) -> None:
|
||||
await handle_firefly_transaction(hook.content)
|
||||
|
||||
|
||||
@app.post('/sshkeys/sign', response_model=SSHKeySignResponse)
|
||||
async def sign_ssh_keys(
|
||||
response: fastapi.Response,
|
||||
hostname: Annotated[str, fastapi.Form()],
|
||||
keys: list[fastapi.UploadFile],
|
||||
):
|
||||
errors = []
|
||||
certificates = {}
|
||||
if '.' not in hostname:
|
||||
errors.append(
|
||||
f'Cannot sign certificate for Single-label hostname {hostname}'
|
||||
)
|
||||
if await check_host(hostname):
|
||||
msg = f'{hostname} already has a signed certificate'
|
||||
if ALLOW_RESIGNING_CERTS:
|
||||
log.warning('%s', msg)
|
||||
else:
|
||||
log.error('%s', msg)
|
||||
errors.append(msg)
|
||||
if not errors:
|
||||
tasks = [sign_uploaded_key(hostname, k) for k in keys]
|
||||
for coro in asyncio.as_completed(tasks):
|
||||
try:
|
||||
name, cert = await coro
|
||||
except Exception as e:
|
||||
log.error('%s', e)
|
||||
errors.append(str(e))
|
||||
else:
|
||||
certificates[name] = cert
|
||||
if errors:
|
||||
response.status_code = fastapi.status.HTTP_400_BAD_REQUEST
|
||||
return SSHKeySignResponse(
|
||||
success=not errors,
|
||||
errors=errors or None,
|
||||
certificates=certificates or None,
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue