@@ -27,9 +27,6 @@ export type HeadscalePolicyStatus = {
|
||||
raw: HeadscalePolicy | Record<string, unknown> | null
|
||||
}
|
||||
|
||||
const generatedPrefix = 'group:lms-platform-'
|
||||
const gatewayGroup = 'group:lms-platform-gateway'
|
||||
|
||||
export class HeadscaleAdapter {
|
||||
async applyGrant(_grant: AccessGrant): Promise<{ applied: boolean; mode: 'noop' | 'headscale' }> {
|
||||
return { applied: true, mode: 'noop' }
|
||||
@@ -76,60 +73,16 @@ export class HeadscaleAdapter {
|
||||
unresolved: string[]
|
||||
error?: string
|
||||
}> {
|
||||
const nodes = await this.listNodes()
|
||||
const unresolved: string[] = []
|
||||
const groups: Record<string, string[]> = {
|
||||
[gatewayGroup]: [...new Set(config.headscaleGatewayUsers.map((user) => this.toHeadscaleUserName(user)))],
|
||||
}
|
||||
const acls: HeadscalePolicy['acls'] = [
|
||||
{
|
||||
// The local bridge initiates discovery from the trusted gateway host.
|
||||
action: 'accept',
|
||||
src: [gatewayGroup],
|
||||
dst: ['tag:teaching-device:*'],
|
||||
},
|
||||
{
|
||||
// DDS discovery and topic payloads use independent UDP flows, so the
|
||||
// teaching device must also be able to reach the bridge on the gateway.
|
||||
action: 'accept',
|
||||
src: ['tag:teaching-device'],
|
||||
dst: [`${gatewayGroup}:*`],
|
||||
},
|
||||
]
|
||||
|
||||
assignments.forEach((assignment, index) => {
|
||||
const users = [...new Set(assignment.studentEmails.map((email) => this.toHeadscaleUserName(email)).filter(Boolean))]
|
||||
if (!users.length) return
|
||||
|
||||
const node = this.findPlatformNode(nodes, assignment)
|
||||
const ip = node ? this.extractIpAddress(node) : null
|
||||
if (!ip) {
|
||||
unresolved.push(assignment.platformName || assignment.agentHostname || assignment.platformId)
|
||||
return
|
||||
}
|
||||
|
||||
const groupName = `${generatedPrefix}${this.toPolicyToken(assignment.platformId || assignment.agentHostname || String(index))}`
|
||||
groups[groupName] = users
|
||||
acls.push({
|
||||
action: 'accept',
|
||||
src: [groupName],
|
||||
dst: [`${ip}:*`],
|
||||
})
|
||||
acls.push({
|
||||
// ROS 2 DDS discovery and topic payloads also originate from the
|
||||
// assigned platform, so keep the student workspace link bidirectional.
|
||||
action: 'accept',
|
||||
src: [ip],
|
||||
dst: [`${groupName}:*`],
|
||||
})
|
||||
})
|
||||
|
||||
const policy: HeadscalePolicy = {
|
||||
groups,
|
||||
groups: {},
|
||||
tagOwners: {
|
||||
'tag:teaching-device': [`${config.headscaleTeachingDeviceOwner}@`],
|
||||
},
|
||||
acls,
|
||||
acls: [{
|
||||
action: 'accept',
|
||||
src: ['*'],
|
||||
dst: ['*:*'],
|
||||
}],
|
||||
}
|
||||
|
||||
const result = await this.applyPolicy(policy)
|
||||
@@ -137,8 +90,8 @@ export class HeadscaleAdapter {
|
||||
applied: result.applied,
|
||||
mode: 'headscale',
|
||||
assignmentCount: assignments.length,
|
||||
aclCount: acls.length,
|
||||
unresolved,
|
||||
aclCount: policy.acls.length,
|
||||
unresolved: [],
|
||||
...(result.error ? { error: result.error } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user