This commit is contained in:
Max Goedjen 2025-09-01 15:09:27 -07:00
parent 9299bf343f
commit c8d90ba455
No known key found for this signature in database
6 changed files with 51 additions and 36 deletions

View File

@ -4,7 +4,7 @@ import OSLog
/// Manages storage and lookup for OpenSSH certificates. /// Manages storage and lookup for OpenSSH certificates.
public actor OpenSSHCertificateHandler: Sendable { public actor OpenSSHCertificateHandler: Sendable {
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: NSHomeDirectory()) private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory)
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "OpenSSHCertificateHandler") private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "OpenSSHCertificateHandler")
private let writer = OpenSSHPublicKeyWriter() private let writer = OpenSSHPublicKeyWriter()
private var keyBlobsAndNames: [AnySecret: (Data, Data)] = [:] private var keyBlobsAndNames: [AnySecret: (Data, Data)] = [:]

View File

@ -5,12 +5,12 @@ import OSLog
public final class PublicKeyFileStoreController: Sendable { public final class PublicKeyFileStoreController: Sendable {
private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "PublicKeyFileStoreController") private let logger = Logger(subsystem: "com.maxgoedjen.secretive.secretagent", category: "PublicKeyFileStoreController")
private let directory: String private let directory: URL
private let keyWriter = OpenSSHPublicKeyWriter() private let keyWriter = OpenSSHPublicKeyWriter()
/// Initializes a PublicKeyFileStoreController. /// Initializes a PublicKeyFileStoreController.
public init(homeDirectory: String) { public init(homeDirectory: URL) {
directory = homeDirectory.appending("/PublicKeys") directory = homeDirectory.appending(component: "PublicKeys")
} }
/// Writes out the keys specified to disk. /// Writes out the keys specified to disk.
@ -20,7 +20,7 @@ public final class PublicKeyFileStoreController: Sendable {
logger.log("Writing public keys to disk") logger.log("Writing public keys to disk")
if clear { if clear {
let validPaths = Set(secrets.map { publicKeyPath(for: $0) }).union(Set(secrets.map { sshCertificatePath(for: $0) })) let validPaths = Set(secrets.map { publicKeyPath(for: $0) }).union(Set(secrets.map { sshCertificatePath(for: $0) }))
let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory)) ?? [] let contentsOfDirectory = (try? FileManager.default.contentsOfDirectory(atPath: directory.path())) ?? []
let fullPathContents = contentsOfDirectory.map { "\(directory)/\($0)" } let fullPathContents = contentsOfDirectory.map { "\(directory)/\($0)" }
let untracked = Set(fullPathContents) let untracked = Set(fullPathContents)
@ -29,7 +29,7 @@ public final class PublicKeyFileStoreController: Sendable {
try? FileManager.default.removeItem(at: URL(fileURLWithPath: path)) try? FileManager.default.removeItem(at: URL(fileURLWithPath: path))
} }
} }
try? FileManager.default.createDirectory(at: URL(fileURLWithPath: directory), withIntermediateDirectories: false, attributes: nil) try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: false, attributes: nil)
for secret in secrets { for secret in secrets {
let path = publicKeyPath(for: secret) let path = publicKeyPath(for: secret)
let data = Data(keyWriter.openSSHString(secret: secret).utf8) let data = Data(keyWriter.openSSHString(secret: secret).utf8)
@ -44,14 +44,14 @@ public final class PublicKeyFileStoreController: Sendable {
/// - Warning: This method returning a path does not imply that a key has been written to disk already. This method only describes where it will be written to. /// - Warning: This method returning a path does not imply that a key has been written to disk already. This method only describes where it will be written to.
public func publicKeyPath<SecretType: Secret>(for secret: SecretType) -> String { public func publicKeyPath<SecretType: Secret>(for secret: SecretType) -> String {
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "") let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
return directory.appending("/").appending("\(minimalHex).pub") return directory.appending(component: "\(minimalHex).pub").path()
} }
/// Short-circuit check to ship enumerating a bunch of paths if there's nothing in the cert directory. /// Short-circuit check to ship enumerating a bunch of paths if there's nothing in the cert directory.
public var hasAnyCertificates: Bool { public var hasAnyCertificates: Bool {
do { do {
return try FileManager.default return try FileManager.default
.contentsOfDirectory(atPath: directory) .contentsOfDirectory(atPath: directory.path())
.filter { $0.hasSuffix("-cert.pub") } .filter { $0.hasSuffix("-cert.pub") }
.isEmpty == false .isEmpty == false
} catch { } catch {
@ -65,7 +65,7 @@ public final class PublicKeyFileStoreController: Sendable {
/// - Warning: This method returning a path does not imply that a key has a SSH certificates. This method only describes where it will be. /// - Warning: This method returning a path does not imply that a key has a SSH certificates. This method only describes where it will be.
public func sshCertificatePath<SecretType: Secret>(for secret: SecretType) -> String { public func sshCertificatePath<SecretType: Secret>(for secret: SecretType) -> String {
let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "") let minimalHex = keyWriter.openSSHMD5Fingerprint(secret: secret).replacingOccurrences(of: ":", with: "")
return directory.appending("/").appending("\(minimalHex)-cert.pub") return directory.appending(component: "\(minimalHex)-cert.pub").path()
} }
} }

View File

@ -21,7 +21,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
}() }()
private let updater = Updater(checkOnLaunch: true) private let updater = Updater(checkOnLaunch: true)
private let notifier = Notifier() private let notifier = Notifier()
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: NSHomeDirectory()) private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.homeDirectory)
private lazy var agent: Agent = { private lazy var agent: Agent = {
Agent(storeList: storeList, witness: notifier) Agent(storeList: storeList, witness: notifier)
}() }()

View File

@ -71,20 +71,24 @@ struct IntegrationsView: View {
case .gettingStarted: case .gettingStarted:
Text("TBD") Text("TBD")
case .tool: case .tool:
Section(selectedInstruction.tool) { ForEach(selectedInstruction.steps) { stepGroup in
ConfigurationItemView(title: "Configuration File", value: selectedInstruction.configPath, action: .revealInFinder( selectedInstruction.configPath)) Section {
ConfigurationItemView(title: "Add This:", action: .copy(selectedInstruction.configText)) { ConfigurationItemView(title: "Configuration File", value: stepGroup.path, action: .revealInFinder(stepGroup.path))
HStack { ForEach(stepGroup.steps, id: \.self) { step in
Text(selectedInstruction.configText) ConfigurationItemView(title: "Add This:", action: .copy(step)) {
.padding(8) HStack {
.font(.system(.subheadline, design: .monospaced)) Text(step)
Spacer() .padding(8)
} .font(.system(.subheadline, design: .monospaced))
.frame(maxWidth: .infinity) Spacer()
.background { }
RoundedRectangle(cornerRadius: 6) .frame(maxWidth: .infinity)
.fill(.black.opacity(0.05)) .background {
.stroke(.separator, lineWidth: 1) RoundedRectangle(cornerRadius: 6)
.fill(.black.opacity(0.05))
.stroke(.separator, lineWidth: 1)
}
}
} }
} }
} }
@ -140,27 +144,39 @@ struct ConfigurationGroup: Identifiable {
var instructions: [ConfigurationFileInstructions] = [] var instructions: [ConfigurationFileInstructions] = []
} }
struct ConfigurationFileInstructions: Identifiable, Hashable { struct ConfigurationFileInstructions: Hashable, Identifiable {
struct StepGroup: Hashable, Identifiable {
let path: String
let steps: [String]
var id: String { path }
}
var id: ID var id: ID
var tool: String var tool: String
var configPath: String var steps: [StepGroup]
var configText: String
var website: URL? var website: URL?
var note: String?
init(tool: String, configPath: String, configText: String, website: URL? = nil) { init(tool: String, configPath: String, configText: String, website: URL? = nil, note: String? = nil) {
self.id = .tool(tool) self.id = .tool(tool)
self.tool = tool self.tool = tool
self.configPath = configPath self.steps = [StepGroup(path: configPath, steps: [configText])]
self.configText = configText self.website = website
self.note = note
}
init(tool: String, steps: [StepGroup], website: URL? = nil, note: String? = nil) {
self.id = .tool(tool)
self.tool = tool
self.steps = steps
self.website = website self.website = website
} }
init(_ name: LocalizedStringResource, id: ID) { init(_ name: LocalizedStringResource, id: ID) {
self.id = id self.id = id
tool = String(localized: name) tool = String(localized: name)
configPath = "" self.steps = []
configText = ""
} }
enum ID: Identifiable, Hashable { enum ID: Identifiable, Hashable {

View File

@ -6,7 +6,7 @@ struct SecretDetailView<SecretType: Secret>: View {
let secret: SecretType let secret: SecretType
private let keyWriter = OpenSSHPublicKeyWriter() private let keyWriter = OpenSSHPublicKeyWriter()
private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.agentHomePath) private let publicKeyFileStoreController = PublicKeyFileStoreController(homeDirectory: URL.agentHomeURL)
var body: some View { var body: some View {
ScrollView { ScrollView {
@ -39,8 +39,8 @@ struct SecretDetailView<SecretType: Secret>: View {
extension URL { extension URL {
static var agentHomePath: String { static var agentHomeURL: URL {
URL.homeDirectory.path().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID) URL(fileURLWithPath: URL.homeDirectory.path().replacingOccurrences(of: Bundle.hostBundleID, with: Bundle.agentBundleID))
} }
} }

View File

@ -141,7 +141,6 @@ extension SetupView {
} }
#Preview { #Preview {
SetupView(setupComplete: .constant(false)) SetupView(setupComplete: .constant(false))
} }