Compare commits
10 commits
trustchain
...
outposts/f
Author | SHA1 | Date | |
---|---|---|---|
4e9a466d64 | |||
9bd8cfbac0 | |||
e18c2fe084 | |||
205f11532f | |||
bc6d66cd88 | |||
609e9a00b4 | |||
d5708d22e0 | |||
71ac1282f9 | |||
cf9d8f64a2 | |||
1cda01511b |
|
@ -84,6 +84,8 @@ RUN apt-get update && \
|
|||
apt-get install -y --no-install-recommends libxmlsec1-openssl libmaxminddb0 && \
|
||||
# Required for bootstrap & healtcheck
|
||||
apt-get install -y --no-install-recommends runit && \
|
||||
# Required for outposts
|
||||
apt-get install -y --no-install-recommends openssh-client && \
|
||||
pip install --no-cache-dir -r /requirements.txt && \
|
||||
apt-get remove --purge -y build-essential pkg-config libxmlsec1-dev && \
|
||||
apt-get autoremove --purge -y && \
|
||||
|
@ -91,8 +93,9 @@ RUN apt-get update && \
|
|||
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/ && \
|
||||
adduser --system --no-create-home --uid 1000 --group --home /authentik authentik && \
|
||||
mkdir -p /certs /media /blueprints && \
|
||||
mkdir -p /authentik/.ssh && \
|
||||
chown authentik:authentik /certs /media /authentik/.ssh
|
||||
chown authentik:authentik /certs /media && \
|
||||
chmod g+w /etc/ssh/ssh_config.d/ && \
|
||||
chgrp authentik /etc/ssh/ssh_config.d/
|
||||
|
||||
COPY ./authentik/ /authentik
|
||||
COPY ./pyproject.toml /
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
"""Docker controller"""
|
||||
from subprocess import SubprocessError # nosec
|
||||
from time import sleep
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
@ -9,7 +10,6 @@ from docker import DockerClient as UpstreamDockerClient
|
|||
from docker.errors import DockerException, NotFound
|
||||
from docker.models.containers import Container
|
||||
from docker.utils.utils import kwargs_from_env
|
||||
from paramiko.ssh_exception import SSHException
|
||||
from structlog.stdlib import get_logger
|
||||
from yaml import safe_dump
|
||||
|
||||
|
@ -58,8 +58,9 @@ class DockerClient(UpstreamDockerClient, BaseClient):
|
|||
super().__init__(
|
||||
base_url=connection.url,
|
||||
tls=tls_config,
|
||||
use_ssh_client=True,
|
||||
)
|
||||
except SSHException as exc:
|
||||
except SubprocessError as exc:
|
||||
if self.ssh:
|
||||
self.ssh.cleanup()
|
||||
raise ServiceConnectionInvalid(exc) from exc
|
||||
|
|
|
@ -7,8 +7,7 @@ from docker.errors import DockerException
|
|||
|
||||
from authentik.crypto.models import CertificateKeyPair
|
||||
|
||||
HEADER = "### Managed by authentik"
|
||||
FOOTER = "### End Managed by authentik"
|
||||
SSH_CONFIG_DIR = Path("/etc/ssh/ssh_config.d/")
|
||||
|
||||
|
||||
def opener(path, flags):
|
||||
|
@ -28,70 +27,54 @@ class DockerInlineSSH:
|
|||
|
||||
key_path: str
|
||||
config_path: Path
|
||||
header: str
|
||||
|
||||
def __init__(self, host: str, keypair: CertificateKeyPair) -> None:
|
||||
self.host = host
|
||||
self.keypair = keypair
|
||||
self.config_path = Path("~/.ssh/config").expanduser()
|
||||
if self.config_path.exists() and HEADER not in self.config_path.read_text(encoding="utf-8"):
|
||||
# SSH Config file already exists and there's no header from us, meaning that it's
|
||||
# been externally mapped into the container for more complex configs
|
||||
raise SSHManagedExternallyException(
|
||||
"SSH Config exists and does not contain authentik header"
|
||||
)
|
||||
self.config_path = SSH_CONFIG_DIR / Path(self.host + ".conf")
|
||||
with open(self.config_path, "w", encoding="utf-8") as _config:
|
||||
if not _config.writable():
|
||||
# SSH Config file already exists and there's no header from us, meaning that it's
|
||||
# been externally mapped into the container for more complex configs
|
||||
raise SSHManagedExternallyException(
|
||||
"SSH Config exists and does not contain authentik header"
|
||||
)
|
||||
if not self.keypair:
|
||||
raise DockerException("keypair must be set for SSH connections")
|
||||
self.header = f"{HEADER} - {self.host}\n"
|
||||
|
||||
def write_config(self, key_path: str) -> bool:
|
||||
def write_config(self, key_path: str):
|
||||
"""Update the local user's ssh config file"""
|
||||
with open(self.config_path, "a+", encoding="utf-8") as ssh_config:
|
||||
if self.header in ssh_config.readlines():
|
||||
return False
|
||||
with open(self.config_path, "w", encoding="utf-8") as ssh_config:
|
||||
ssh_config.writelines(
|
||||
[
|
||||
self.header,
|
||||
f"Host {self.host}\n",
|
||||
f" IdentityFile {key_path}\n",
|
||||
f" IdentityFile {str(key_path)}\n",
|
||||
" StrictHostKeyChecking No\n",
|
||||
" UserKnownHostsFile /dev/null\n",
|
||||
f"{FOOTER}\n",
|
||||
"\n",
|
||||
]
|
||||
)
|
||||
return True
|
||||
|
||||
def write_key(self):
|
||||
def write_key(self) -> Path:
|
||||
"""Write keypair's private key to a temporary file"""
|
||||
path = Path(gettempdir(), f"{self.keypair.pk}_private.pem")
|
||||
with open(path, "w", encoding="utf8", opener=opener) as _file:
|
||||
_file.write(self.keypair.key_data)
|
||||
return str(path)
|
||||
return path
|
||||
|
||||
def write(self):
|
||||
"""Write keyfile and update ssh config"""
|
||||
self.key_path = self.write_key()
|
||||
was_written = self.write_config(self.key_path)
|
||||
if not was_written:
|
||||
try:
|
||||
self.write_config(self.key_path)
|
||||
except OSError:
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
"""Cleanup when we're done"""
|
||||
try:
|
||||
os.unlink(self.key_path)
|
||||
with open(self.config_path, "r", encoding="utf-8") as ssh_config:
|
||||
start = 0
|
||||
end = 0
|
||||
lines = ssh_config.readlines()
|
||||
for idx, line in enumerate(lines):
|
||||
if line == self.header:
|
||||
start = idx
|
||||
if start != 0 and line == f"{FOOTER}\n":
|
||||
end = idx
|
||||
with open(self.config_path, "w+", encoding="utf-8") as ssh_config:
|
||||
lines = lines[:start] + lines[end + 2 :]
|
||||
ssh_config.writelines(lines)
|
||||
os.unlink(self.config_path)
|
||||
except OSError:
|
||||
# If we fail deleting a file it doesn't matter that much
|
||||
# since we're just in a container
|
||||
|
|
|
@ -85,7 +85,7 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
|
|||
html`<ak-label color=${item.local ? PFColor.Grey : PFColor.Green}>
|
||||
${item.local ? t`Yes` : t`No`}
|
||||
</ak-label>`,
|
||||
html`${itemState.healthy
|
||||
html`${itemState?.healthy
|
||||
? html`<ak-label color=${PFColor.Green}>${ifDefined(itemState.version)}</ak-label>`
|
||||
: html`<ak-label color=${PFColor.Red}>${t`Unhealthy`}</ak-label>`}`,
|
||||
html` <ak-forms-modal>
|
||||
|
|
Reference in a new issue