feat: implement context sanity checks

This commit is contained in:
Aarnav Tale 2024-03-30 01:48:35 -04:00
parent e216d4171f
commit 4e129d527a
No known key found for this signature in database
3 changed files with 102 additions and 22 deletions

View File

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

View File

@ -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<typeof loader>()
return (
<>
<header className='mb-16 bg-gray-800 text-white dark:bg-gray-700'>
@ -48,9 +50,13 @@ export default function Layout() {
<div className='flex items-center gap-x-4'>
<TabLink to='/machines' name='Machines' icon={<ServerStackIcon className='w-5 h-5'/>}/>
<TabLink to='/users' name='Users' icon={<UsersIcon className='w-5 h-5'/>}/>
<TabLink to='/acls' name='Access Control' icon={<LockClosedIcon className='w-5 h-5'/>}/>
<TabLink to='/dns' name='DNS' icon={<GlobeAltIcon className='w-5 h-5'/>}/>
<TabLink to='/settings' name='Settings' icon={<Cog8ToothIcon className='w-5 h-5'/>}/>
{data.hasAcl ? <TabLink to='/acls' name='Access Control' icon={<LockClosedIcon className='w-5 h-5'/>}/> : undefined}
{data.hasConfig ? (
<>
<TabLink to='/dns' name='DNS' icon={<GlobeAltIcon className='w-5 h-5'/>}/>
<TabLink to='/settings' name='Settings' icon={<Cog8ToothIcon className='w-5 h-5'/>}/>
</>
) : undefined}
</div>
</nav>
</header>
@ -63,6 +69,12 @@ export default function Layout() {
}
export function ErrorBoundary() {
const data = useLoaderData<typeof loader>()
const error = useRouteError()
if (!data) {
throw error
}
return (
<>
<header className='mb-16 bg-gray-800 text-white dark:bg-gray-700'>
@ -74,9 +86,13 @@ export function ErrorBoundary() {
<div className='flex items-center gap-x-4'>
<TabLink to='/machines' name='Machines' icon={<ServerStackIcon className='w-5 h-5'/>}/>
<TabLink to='/users' name='Users' icon={<UsersIcon className='w-5 h-5'/>}/>
<TabLink to='/acls' name='Access Control' icon={<LockClosedIcon className='w-5 h-5'/>}/>
<TabLink to='/dns' name='DNS' icon={<GlobeAltIcon className='w-5 h-5'/>}/>
<TabLink to='/settings' name='Settings' icon={<Cog8ToothIcon className='w-5 h-5'/>}/>
{data.hasAcl ? <TabLink to='/acls' name='Access Control' icon={<LockClosedIcon className='w-5 h-5'/>}/> : undefined}
{data.hasConfig ? (
<>
<TabLink to='/dns' name='DNS' icon={<GlobeAltIcon className='w-5 h-5'/>}/>
<TabLink to='/settings' name='Settings' icon={<Cog8ToothIcon className='w-5 h-5'/>}/>
</>
) : undefined}
</div>
</nav>
</header>

View File

@ -142,7 +142,10 @@ export async function patchConfig(partial: Record<string, unknown>) {
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
}