From 4e129d527ab29b84f5400ba01e0f8056c0073631 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 30 Mar 2024 01:48:35 -0400 Subject: [PATCH] feat: implement context sanity checks --- app/routes/_data.dns._index/route.tsx | 6 +- app/routes/_data.tsx | 34 ++++++++--- app/utils/config.ts | 84 +++++++++++++++++++++++---- 3 files changed, 102 insertions(+), 22 deletions(-) diff --git a/app/routes/_data.dns._index/route.tsx b/app/routes/_data.dns._index/route.tsx index 21b1abe..6361c01 100644 --- a/app/routes/_data.dns._index/route.tsx +++ b/app/routes/_data.dns._index/route.tsx @@ -18,8 +18,12 @@ import RenameModal from './rename' // We do not want to expose every config value export async function loader() { - const config = await getConfig() const context = await getContext() + if (!context.hasConfig) { + throw new Error('No configuration is available') + } + + const config = await getConfig() const dns = { prefixes: config.prefixes, diff --git a/app/routes/_data.tsx b/app/routes/_data.tsx index 8f2d9a9..d035ee9 100644 --- a/app/routes/_data.tsx +++ b/app/routes/_data.tsx @@ -1,9 +1,10 @@ import { Cog8ToothIcon, CpuChipIcon, GlobeAltIcon, LockClosedIcon, ServerStackIcon, UsersIcon } from '@heroicons/react/24/outline' import { type LoaderFunctionArgs, redirect } from '@remix-run/node' -import { Outlet, useRouteError } from '@remix-run/react' +import { Outlet, useLoaderData, useRouteError } from '@remix-run/react' import { ErrorPopup } from '~/components/Error' import TabLink from '~/components/TabLink' +import { getContext } from '~/utils/config' import { HeadscaleError, pull } from '~/utils/headscale' import { destroySession, getSession } from '~/utils/sessions' @@ -32,11 +33,12 @@ export async function loader({ request }: LoaderFunctionArgs) { throw error } - // eslint-disable-next-line unicorn/no-null - return null + const context = await getContext() + return context } export default function Layout() { + const data = useLoaderData() return ( <>
@@ -48,9 +50,13 @@ export default function Layout() {
}/> }/> - }/> - }/> - }/> + {data.hasAcl ? }/> : undefined} + {data.hasConfig ? ( + <> + }/> + }/> + + ) : undefined}
@@ -63,6 +69,12 @@ export default function Layout() { } export function ErrorBoundary() { + const data = useLoaderData() + const error = useRouteError() + if (!data) { + throw error + } + return ( <>
@@ -74,9 +86,13 @@ export function ErrorBoundary() {
}/> }/> - }/> - }/> - }/> + {data.hasAcl ? }/> : undefined} + {data.hasConfig ? ( + <> + }/> + }/> + + ) : undefined}
diff --git a/app/utils/config.ts b/app/utils/config.ts index f6ac9cb..eef1aae 100644 --- a/app/utils/config.ts +++ b/app/utils/config.ts @@ -142,7 +142,10 @@ export async function patchConfig(partial: Record) { type Context = { hasDockerSock: boolean; + hasConfig: boolean; hasConfigWrite: boolean; + hasAcl: boolean; + hasAclWrite: boolean; } export let context: Context @@ -151,24 +154,16 @@ export async function getContext() { if (!context) { context = { hasDockerSock: await checkSock(), - hasConfigWrite: await checkConfigWrite() + hasConfig: await hasConfig(), + hasConfigWrite: await hasConfigW(), + hasAcl: await hasAcl(), + hasAclWrite: await hasAclW() } } return context } -async function checkConfigWrite() { - try { - await getConfig() - return true - } catch (error) { - console.error('Failed to read config file', error) - } - - return false -} - async function checkSock() { try { await access('/var/run/docker.sock', constants.R_OK) @@ -181,3 +176,68 @@ async function checkSock() { return false } + +async function hasConfig() { + try { + await getConfig() + return true + } catch {} + + return false +} + +async function hasConfigW() { + const path = resolve(process.env.CONFIG_FILE ?? '/etc/headscale/config.yaml') + try { + await access(path, constants.W_OK) + return true + } catch {} + + return false +} + +async function hasAcl() { + let path = process.env.ACL_FILE + if (!path) { + try { + const config = await getConfig() + path = config.acl_policy_path + } catch {} + + return false + } + + if (!path) { + return false + } + + try { + await access(path, constants.R_OK) + return true + } catch {} + + return false +} + +async function hasAclW() { + let path = process.env.ACL_FILE + if (!path) { + try { + const config = await getConfig() + path = config.acl_policy_path + } catch {} + + return false + } + + if (!path) { + return false + } + + try { + await access(path, constants.W_OK) + return true + } catch {} + + return false +}