// Usage: node apparmor-docker-default.js const path = require("node:path"); const fs = require("node:fs"); const profileDirectory = "/etc/apparmor.d" /** * @typedef ProfileData * @property {string} name * @property {string} daemonProfile * @property {string[]} imports * @property {string[]} innerImports */ // Output "docker-default" profile. { getDefault().then(profile => { console.log(profile); }); } async function loadTemplate() { const response = await fetch("https://raw.githubusercontent.com/moby/moby/master/profiles/apparmor/template.go"); const text = await response.text(); const startTag = "const baseTemplate = `"; const startIndex = text.indexOf(startTag) + startTag.length; const endIndex = text.lastIndexOf("`"); return text.slice(startIndex, endIndex); } // https://github.com/moby/moby/blob/master/profiles/apparmor/apparmor.go#L59 async function getDefault(name = "docker-default") { // Figure out the daemon profile. let currentProfile = ""; try { currentProfile = fs.readFileSync("/proc/self/attr/current", "utf8").trim(); } catch { // If we couldn't get the daemon profile, assume we are running // unconfined which is generally the default. } const daemonProfile = currentProfile.split(" ")[0] || "unconfined"; return generateDefault(name, daemonProfile) } // https://github.com/moby/moby/blob/master/profiles/apparmor/apparmor.go#L32 /** * @param {string} name * @param {string} daemonProfile */ async function generateDefault(name, daemonProfile) { const template = await loadTemplate(); /** @type {ProfileData} */ const p = { name, daemonProfile, imports: [], innerImports: [], }; if (macroExists("tunables/global")) { p.imports.push("#include "); } else { p.imports.push("@{PROC}=/proc/"); } if (macroExists("abstractions/base")) { p.innerImports.push("#include "); } return parseTemplate(template, p); } /** * @param {string} template * @param {ProfileData} p */ function parseTemplate(template, p) { const endTag = "{{end}}"; // Replace "{{range $value := .Imports}}". const importsStartIndex = template.indexOf("{{range $value := .Imports}}"); const importsEndIndex = template.indexOf(endTag, importsStartIndex) + endTag.length; template = template.substring(0, importsStartIndex) + p.imports.join("\n") + template.substring(importsEndIndex); // Replace "{{range $value := .InnerImports}}". const innerImportsStartIndex = template.indexOf("{{range $value := .InnerImports}}"); const innerImportsEndIndex = template.indexOf(endTag, innerImportsStartIndex) + endTag.length; template = template.substring(0, innerImportsStartIndex) + p.innerImports.map(i => " " + i).join("\n") + template.substring(innerImportsEndIndex); // Replace "{{.Name}}". template = template.replaceAll(/\{\{\.Name\}\}/g, p.name); // Replace "{{.DaemonProfile}}" template = template.replaceAll(/\{\{\.DaemonProfile\}\}/g, p.daemonProfile); return template; } // https://github.com/moby/moby/blob/master/profiles/apparmor/apparmor.go#L52 function macroExists(m) { return fs.existsSync(path.join(profileDirectory, m)); }