1
0
Fork 0

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
Dustin 2024-01-15 13:49:51 -06:00
parent e5eff964a1
commit 55df6f61a7
3 changed files with 7 additions and 128 deletions

1
.gitignore vendored
View File

@ -1,4 +1,3 @@
/.venv
__pycache__/
*.egg-info/
provisioner.password

View File

@ -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"]

View File

@ -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,
)