Install macOS helper from PKG for XPC enroll
Browser enrollment used osascript on every elevate. Ship a signed privileged helper installed at PKG time so probo:// can enroll over XPC with no second admin prompt. Add make install/uninstall/clean for local PKG test loops, and show alerts only on failure. Mirror the Go lint path for the macOS SPM package: Make targets, root configs, and a Linux CI job. Keep checks syntax-only so they do not need a macOS SDK. Format the existing sources so the new gates start clean. Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import Foundation
|
||||
import ProboAgentShared
|
||||
import os
|
||||
|
||||
final class Helper: NSObject, ProboAgentHelperProtocol, NSXPCListenerDelegate {
|
||||
private static let log = Logger(
|
||||
subsystem: "com.probo.agent.helper",
|
||||
category: "Helper"
|
||||
)
|
||||
|
||||
func getVersion(withReply reply: @escaping (String) -> Void) {
|
||||
reply(ProboAgentHelperConstants.helperVersion)
|
||||
}
|
||||
|
||||
func ping(withReply reply: @escaping (Bool) -> Void) {
|
||||
Self.log.info("ping")
|
||||
reply(true)
|
||||
}
|
||||
|
||||
func install(
|
||||
serverURL: String,
|
||||
enrollmentToken: String,
|
||||
configDir: String,
|
||||
withReply reply: @escaping (Int32, String?) -> Void
|
||||
) {
|
||||
let trimmedServer = serverURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let trimmedToken = enrollmentToken.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let dir = configDir.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
guard !trimmedServer.isEmpty, !trimmedToken.isEmpty else {
|
||||
reply(1, "server URL and enrollment token are required")
|
||||
return
|
||||
}
|
||||
|
||||
// Pass the token via env rather than argv so it does not show up in
|
||||
// process listings (ps / Activity Monitor). Install already accepts
|
||||
// PROBO_ENROLLMENT_TOKEN when --enrollment-token is omitted.
|
||||
var args = [
|
||||
"install",
|
||||
"--server", trimmedServer,
|
||||
]
|
||||
if !dir.isEmpty {
|
||||
args.append(contentsOf: ["--dir", dir])
|
||||
}
|
||||
|
||||
var environment = ProcessInfo.processInfo.environment
|
||||
environment["PROBO_ENROLLMENT_TOKEN"] = trimmedToken
|
||||
|
||||
let result = runAgent(args: args, environment: environment)
|
||||
reply(result.exitCode, result.output)
|
||||
}
|
||||
|
||||
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection connection: NSXPCConnection)
|
||||
-> Bool
|
||||
{
|
||||
guard ClientAuth.accepts(connection: connection) else {
|
||||
Self.log.error(
|
||||
"refused XPC connection pid=\(connection.processIdentifier, privacy: .public)")
|
||||
return false
|
||||
}
|
||||
|
||||
Self.log.info(
|
||||
"accepted XPC connection pid=\(connection.processIdentifier, privacy: .public)")
|
||||
connection.exportedInterface = NSXPCInterface(with: ProboAgentHelperProtocol.self)
|
||||
connection.exportedObject = self
|
||||
connection.resume()
|
||||
return true
|
||||
}
|
||||
|
||||
private struct CommandResult {
|
||||
let exitCode: Int32
|
||||
let output: String?
|
||||
}
|
||||
|
||||
private func runAgent(args: [String], environment: [String: String]? = nil) -> CommandResult {
|
||||
let process = Process()
|
||||
process.executableURL = URL(fileURLWithPath: ProboAgentHelperConstants.agentExecutablePath)
|
||||
process.arguments = args
|
||||
if let environment {
|
||||
process.environment = environment
|
||||
}
|
||||
|
||||
let pipe = Pipe()
|
||||
process.standardOutput = pipe
|
||||
process.standardError = pipe
|
||||
|
||||
do {
|
||||
try process.run()
|
||||
let data = try pipe.fileHandleForReading.readToEnd() ?? Data()
|
||||
process.waitUntilExit()
|
||||
let text = String(data: data, encoding: .utf8)?
|
||||
.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
return CommandResult(
|
||||
exitCode: process.terminationStatus,
|
||||
output: text?.isEmpty == false ? text : nil
|
||||
)
|
||||
} catch {
|
||||
return CommandResult(exitCode: 1, output: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.probo.agent.helper</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>Probo Agent Helper</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>@@VERSION@@</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>@@VERSION@@</string>
|
||||
<key>SMAuthorizedClients</key>
|
||||
<array>
|
||||
<string>@@CLIENT_DESIGNATED_REQUIREMENT@@</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.probo.agent.helper</string>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.probo.agent.helper</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>AssociatedBundleIdentifiers</key>
|
||||
<array>
|
||||
<string>com.probo.agent.url-handler</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,13 @@
|
||||
import Foundation
|
||||
import ProboAgentShared
|
||||
import os
|
||||
|
||||
private let log = Logger(subsystem: "com.probo.agent.helper", category: "main")
|
||||
|
||||
let helper = Helper()
|
||||
let listener = NSXPCListener(machServiceName: ProboAgentHelperConstants.machServiceName)
|
||||
listener.delegate = helper
|
||||
listener.resume()
|
||||
log.info("listening on \(ProboAgentHelperConstants.machServiceName, privacy: .public)")
|
||||
|
||||
RunLoop.main.run()
|
||||
Reference in New Issue
Block a user