mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-09-16 09:20:56 +00:00
WIP
This commit is contained in:
parent
9299bf343f
commit
c8d90ba455
@ -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)] = [:]
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}()
|
}()
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,6 @@ extension SetupView {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
SetupView(setupComplete: .constant(false))
|
SetupView(setupComplete: .constant(false))
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user