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