Commit 6d813cf9 authored by Joel Rennich's avatar Joel Rennich

lots of additional updates

parent f74a2326
Pipeline #38449781 failed with stage
in 22 seconds
......@@ -7,6 +7,7 @@
//
enum HintType: String {
case networkSignIn
case noMADUser
case noMADDomain
case noMADPass
......@@ -19,6 +20,11 @@ enum HintType: String {
case kerberos_principal
}
// attribute statics
let kODAttributeADUser = "dsAttrTypeStandard:ADUser"
let kODAttributeNetworkSignIn = "dsAttrTypeStandard:NetworkSignIn"
protocol ContextAndHintHandling {
var mech: MechanismRecord? {get}
func setContextString(type: String, value: String)
......
......@@ -23,10 +23,18 @@ enum Preferences: String {
case CreateAdminIfGroupMember
/// Should existing mobile accounts be converted into plain local accounts? Set as a Bool`.
case DemobilizeUsers
/// Dissallow local auth, and always do network authentication
case DenyLocal
/// Users to allow locally when DenyLocal is on
case DenyLocalExcluded
/// List of groups that should have it's members allowed to sign in. Set as an Array of Strings of the group name
case DenyLoginUnlessGroupMember
/// Should FDE be enabled at first login on APFS disks? Set as a `Bool`.
case EnableFDE
/// Should the PRK be saved to disk for the MDM Escrow Service to collect? Set as a `Bool`.
case EnableFDERecoveryKey
// Specify a custom path for the recovery key
case EnableFDERecoveryKeyPath
/// Path for where the EULA acceptance info goes
case EULAPath
/// Text for EULA as a `String`.
......@@ -45,6 +53,10 @@ enum Preferences: String {
case KeychainReset
/// Force LDAP lookups to use SSL connections. Requires certificate trust be established. Set as a `Bool`.
case LDAPOverSSL
/// Force specific LDAP servers instead of finding them via DNS
case LDAPServers
/// Fallback to local auth if the network is not available
case LocalFallback
/// A filesystem path to an image to display on the login screen as a `String`.
case LoginLogo
/// A Base64 encoded string of an image to display on the login screen.
......@@ -57,6 +69,8 @@ enum Preferences: String {
case ScriptPath
/// Arguments for the script, if any
case ScriptArgs
/// Use the CN from AD as the full name
case UseCNForFullName
/// A string to show as the placeholder in the Username textfield
case UsernameFieldPlaceholder
/// A filesystem path to an image to set the user profile image to as a `String`
......@@ -78,23 +92,27 @@ enum Preferences: String {
/// - Parameter key: A member of the `Preferences` enum
/// - Returns: The value, if any, for the preference. If no preference is set, returns `nil`
func getManagedPreference(key: Preferences) -> Any? {
if let preference = UserDefaults(suiteName: "com.trusourcelabs.NoMAD")?.value(forKey: key.rawValue) {
os_log("Checking menu.nomad.login.ad preference domain.", type: .debug)
if let preference = UserDefaults(suiteName: "menu.nomad.login.ad")?.value(forKey: key.rawValue) {
os_log("Found managed preference: %{public}@", type: .debug, key.rawValue)
return preference
}
os_log("No NoMAD preferences found. Checking NoLoAD", type: .debug)
os_log("No menu.nomad.login.ad preference found. Checking menu.nomad.NoMADLoginAD", type: .debug)
if let preference = UserDefaults(suiteName: "menu.nomad.NoMADLoginAD")?.value(forKey: key.rawValue) {
os_log("Found managed preference: %{public}@", type: .debug, key.rawValue)
return preference
}
os_log("No menu.nomad.NoMADLoginAD preference found. Checking com.trusourcelabs.NoMAD", type: .debug)
os_log("No NoLoAD preferences found. Checking new menu.nomad.login.ad", type: .debug)
if let preference = UserDefaults(suiteName: "menu.nomad.login.ad")?.value(forKey: key.rawValue) {
if let preference = UserDefaults(suiteName: "com.trusourcelabs.NoMAD")?.value(forKey: key.rawValue) {
os_log("Found managed preference: %{public}@", type: .debug, key.rawValue)
return preference
}
return nil
}
......@@ -86,6 +86,10 @@ class CreateUser: NoLoMechanism {
} else {
// no user to create
os_log("Skipping local account creation", log: createUserLog, type: .default)
// Set the login timestamp if requested
setTimestampFor(nomadUser as? String ?? "")
os_log("Account creation skipped, allowing login", log: createUserLog, type: .debug)
}
let _ = allowLogin()
......@@ -121,7 +125,7 @@ class CreateUser: NoLoMechanism {
let picData = NSData(contentsOf: picURL)
let picString = picData?.description ?? ""
let attrs: [AnyHashable:Any] = [
var attrs: [AnyHashable:Any] = [
kODAttributeTypeFullName: [first + " " + last],
kODAttributeTypeNFSHomeDirectory: [ "/Users/" + shortName ],
kODAttributeTypeUserShell: ["/bin/bash"],
......@@ -129,9 +133,18 @@ class CreateUser: NoLoMechanism {
kODAttributeTypePrimaryGroupID: [gid],
kODAttributeTypeAuthenticationHint: [""],
kODAttributeTypePicture: [userPicture],
kODAttributeTypeJPEGPhoto: [picString]
kODAttributeTypeJPEGPhoto: [picString],
kODAttributeADUser: [getHint(type: .kerberos_principal) as? String ?? ""]
]
if getManagedPreference(key: .UseCNForFullName) as? Bool ?? false {
attrs[kODAttributeTypeFullName] = [getHint(type: .noMADFull) as? String ?? ""]
}
if let signInTime = getHint(type: .networkSignIn) {
attrs[kODAttributeNetworkSignIn] = [signInTime]
}
do {
os_log("Creating user account in local ODNode", log: createUserLog, type: .debug)
let node = try ODNode.init(session: session, type: ODNodeType(kODNodeTypeLocalNodes))
......@@ -383,4 +396,15 @@ class CreateUser: NoLoMechanism {
return "Non_localized"
}
}
fileprivate func setTimestampFor(_ nomadUser: String) {
// Add network sign in stamp
if let signInTime = getHint(type: .networkSignIn) {
if NoLoMechanism.updateSignIn(name: nomadUser, time: signInTime as AnyObject) {
os_log("Sign in time updated", log: createUserLog, type: .default)
} else {
os_log("Dould not add timestamp", log: createUserLog, type: .error)
}
}
}
}
......@@ -7,6 +7,7 @@
//
import Cocoa
import NoMAD_ADAuth
class EnableFDE : NoLoMechanism {
......@@ -20,7 +21,13 @@ class EnableFDE : NoLoMechanism {
// FileVault
if getManagedPreference(key: .EnableFDE) as? Bool == true {
enableFDE()
// check to see if we're already FileVaulted
if isFdeEnabled() {
os_log("FileVault is already enabled, skipping mechanism.", log: enableFDELog, type: .debug)
} else {
enableFDE()
}
}
// Always let login through
......@@ -81,10 +88,36 @@ class EnableFDE : NoLoMechanism {
// write out the PRK if asked to
// write out the PRK if asked to
if getManagedPreference(key: .EnableFDERecoveryKey) as? Bool == true {
var recoveryPath = "/var/db/NoMADFDE"
if let newPath = getManagedPreference(key: .EnableFDERecoveryKeyPath) as? String {
recoveryPath = newPath
}
let fm = FileManager.default
if !fm.fileExists(atPath: recoveryPath, isDirectory: nil) {
do {
os_log("Creating folder for recovery key storage.", log: enableFDELog)
try fm.createDirectory(atPath: recoveryPath, withIntermediateDirectories: true, attributes: [FileAttributeKey.posixPermissions : 0o750])
} catch {
os_log("Unable to create file path for PRK, defaulting to /var/db/", log: enableFDELog)
// reset recovery path to something we know will exist
recoveryPath = "/var/db/"
}
}
recoveryPath += "/NoMADFDESetup.plist"
do {
os_log("Attempting to write key to: %{public}@", log: enableFDELog, type: .debug, "/var/db/.NoMADFDESetup")
try output.write(toFile: "/var/db/.NoMADFDESetup", atomically: true, encoding: String.Encoding.ascii)
os_log("Attempting to write key to: %{public}@", log: enableFDELog, type: .default, recoveryPath)
try output.write(toFile: recoveryPath, atomically: true, encoding: String.Encoding.ascii)
} catch {
os_log("Unable to finish fdesetup: %{public}@", log: enableFDELog, type: .error, errorMessage ?? "Unkown error")
}
......@@ -118,4 +151,13 @@ class EnableFDE : NoLoMechanism {
return false
}
}
fileprivate func isFdeEnabled() -> Bool {
// determine if FV is already running
if cliTask("/usr/bin/fdesetup", arguments: ["status"]).contains("FileVault is Off") {
return false
} else {
return true
}
}
}
......@@ -6,6 +6,7 @@
// Copyright © 2018 Orchard & Grove Inc. All rights reserved.
//
import Cocoa
import Security
import os.log
import OpenDirectory
......@@ -140,18 +141,19 @@ class KeychainAdd : NoLoMechanism {
secApps.append(nomadTrust!)
}
} else {
os_log("NoMAD not installed.", log: keychainAddLog, type: .error)
os_log("Checking for NoMAD anywhere on the device.", log: keychainAddLog, type: .error)
let ws = NSWorkspace.shared
if let customPath = ws.absolutePathForApplication(withBundleIdentifier: "com.trusourcelabs.NoMAD") {
err = SecTrustedApplicationCreateFromPath(customPath, &nomadTrust)
if err == 0 {
secApps.append(nomadTrust!)
}
} else {
os_log("Unable to get custom NoMAD path", log: keychainAddLog, type: .error)
}
}
// if fm.fileExists(atPath: "/Applications/NoMAD Pro.app", isDirectory: nil) {
// err = SecTrustedApplicationCreateFromPath("/Applications/NoMAD Pro.app", &nomadProTrust)
// if err == 0 {
// secApps.append(nomadProTrust!)
// }
// } else {
// os_log("NoMAD Pro not installed.", log: keychainAddLog, type: .error)
// }
itemAttrs[kSecAttrType as String] = "genp" as AnyObject
//itemAttrs[kSecValueRef as String ] = kItemPass as AnyObject
itemAttrs[kSecAttrLabel as String] = "NoMAD" as AnyObject
......
......@@ -296,6 +296,45 @@ class NoLoMechanism: NSObject {
return records.first?.recordName
}
}
/// Updates a timestamp on a local account
///
/// - Parameters:
/// - name: the shortname of the user to check as a `String`.
/// - time: The time to add as a `String`.
/// - Returns: `true` if time attribute can be added, false if not.
class func updateSignIn(name: String, time: AnyObject ) -> Bool {
os_log("Checking for local username", log: noLoMechlog, type: .default)
var records = [ODRecord]()
let odsession = ODSession.default()
do {
let node = try ODNode.init(session: odsession, type: ODNodeType(kODNodeTypeLocalNodes))
let query = try ODQuery.init(node: node, forRecordTypes: kODRecordTypeUsers, attribute: kODAttributeTypeRecordName, matchType: ODMatchType(kODMatchEqualTo), queryValues: name, returnAttributes: kODAttributeTypeAllAttributes, maximumResults: 0)
records = try query.resultsAllowingPartial(false) as! [ODRecord]
} catch {
let errorText = error.localizedDescription
os_log("ODError while trying to check for local user: %{public}@", log: noLoMechlog, type: .error, errorText)
return false
}
let isLocal = records.isEmpty ? false : true
os_log("Results of local user check %{public}@", log: noLoMechlog, type: .default, isLocal.description)
if !isLocal {
return isLocal
}
// now to update the attribute
do {
try records.first?.setValue(time, forAttribute: kODAttributeNetworkSignIn)
} catch {
os_log("Unable to add sign in time to record", log: noLoMechlog, type: .error)
return false
}
return true
}
}
......
......@@ -25,7 +25,43 @@ class RunScript : NoLoMechanism {
task.launchPath = scriptPath
if let args = getManagedPreference(key: .ScriptArgs) as? [String] {
task.arguments = args
var cleanArgs = [String]()
for arg in args {
if arg == "<<User>>" {
if let setupUser = getHint(type: .noMADUser) as? String {
cleanArgs.append(setupUser)
} else {
cleanArgs.append(kArgError)
}
} else if arg == "<<First>>" {
if let setupPass = getHint(type: .noMADFirst) as? String {
cleanArgs.append(setupPass)
} else {
cleanArgs.append(kArgError)
}
} else if arg == "<<Last>>" {
if let adminUser = getHint(type: .noMADLast) as? String {
cleanArgs.append(adminUser)
} else {
cleanArgs.append(kArgError)
}
} else if arg == "<<Principal>>" {
if let setupAdminPass = getHint(type: .kerberos_principal) as? String {
cleanArgs.append(setupAdminPass)
} else {
cleanArgs.append(kArgError)
}
} else {
cleanArgs.append(arg)
}
}
task.arguments = cleanArgs
}
do {
......
......@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.2.3b1</string>
<string>1.2.2b1</string>
<key>CFBundleVersion</key>
<string>307</string>
<string>330</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2018 Orchard &amp; Grove. All rights reserved.</string>
<key>NSPrincipalClass</key>
......
......@@ -39,6 +39,7 @@ class SignIn: NSWindowController {
@IBOutlet weak var oldPassword: NSSecureTextField!
@IBOutlet weak var newPassword: NSSecureTextField!
@IBOutlet weak var newPasswordConfirmation: NSSecureTextField!
@IBOutlet weak var alertText: NSTextField!
//MARK: - UI Methods
override func windowDidLoad() {
......@@ -216,6 +217,12 @@ class SignIn: NSWindowController {
oldPassword.becomeFirstResponder()
}
fileprivate func authFail() {
session = nil
password.stringValue = ""
alertText.stringValue = "Authentication Failed"
loginStartedUI()
}
/// Simple toggle to change the state of the NoLo window UI between active and inactive.
fileprivate func loginStartedUI() {
......@@ -240,34 +247,75 @@ class SignIn: NSWindowController {
os_log("No username entered", log: uiLog, type: .default)
return
}
// clear any alerts
alertText.stringValue = ""
loginStartedUI()
prepareAccountStrings()
if NoLoMechanism.checkForLocalUser(name: shortName) {
os_log("Verify local user login for %{public}@", log: uiLog, type: .default, shortName)
if getManagedPreference(key: .DenyLocal) as? Bool ?? false {
os_log("DenyLocal is enabled, looking for %{public}@ in excluded users", log: uiLog, type: .default, shortName)
var exclude = false
if let excludedUsers = getManagedPreference(key: .DenyLocalExcluded) as? [String] {
if excludedUsers.contains(shortName) {
os_log("Allowing local sign in via exclusions %{public}@", log: uiLog, type: .default, shortName)
exclude = true
}
}
if !exclude {
os_log("No exclusions for %{public}@, denying local login. Forcing network auth", log: uiLog, type: .default, shortName)
networkAuth()
return
}
}
if NoLoMechanism.verifyUser(name: shortName, auth: passString) {
os_log("Allowing local user login for %{public}@", log: uiLog, type: .default, shortName)
setRequiredHintsAndContext()
completeLogin(authResult: .allow)
os_log("Allowing local user login for %{public}@", log: uiLog, type: .default, shortName)
setRequiredHintsAndContext()
completeLogin(authResult: .allow)
return
} else {
os_log("Could not verify %{public}@", log: uiLog, type: .default, shortName)
completeLogin(authResult: .deny)
}
} else {
session = NoMADSession.init(domain: domainName, user: shortName)
os_log("NoMAD Login User: %{public}@, Domain: %{public}@", log: uiLog, type: .default, shortName, domainName)
guard let session = session else {
os_log("Could not create NoMADSession from SignIn window", log: uiLog, type: .error)
authFail()
return
}
session.useSSL = isSSLRequired
session.userPass = passString
session.delegate = self
if let ignoreSites = getManagedPreference(key: .IgnoreSites) as? Bool {
session.siteIgnore = ignoreSites
}
os_log("Attempt to authenticate user", log: uiLog, type: .debug)
session.authenticate()
} else {
networkAuth()
}
}
fileprivate func networkAuth() {
session = NoMADSession.init(domain: domainName, user: shortName)
os_log("NoMAD Login User: %{public}@, Domain: %{public}@", log: uiLog, type: .default, shortName, domainName)
guard let session = session else {
os_log("Could not create NoMADSession from SignIn window", log: uiLog, type: .error)
return
}
session.useSSL = isSSLRequired
session.userPass = passString
session.delegate = self
if let ignoreSites = getManagedPreference(key: .IgnoreSites) as? Bool {
os_log("Ignoring AD sites", log: uiLog, type: .debug)
session.siteIgnore = ignoreSites
}
if let ldapServers = getManagedPreference(key: .LDAPServers) as? [String] {
os_log("Adding custom LDAP servers", log: uiLog, type: .debug)
session.ldapServers = ldapServers
}
os_log("Attempt to authenticate user", log: uiLog, type: .debug)
session.authenticate()
}
......@@ -414,7 +462,8 @@ extension SignIn: NoMADUserSessionDelegate {
return
default:
os_log("NoMAD Login Authentication failed with: %{public}@", log: uiLog, type: .error, description)
completeLogin(authResult: .deny)
authFail()
return
}
}
......@@ -433,13 +482,45 @@ extension SignIn: NoMADUserSessionDelegate {
func NoMADUserInformation(user: ADUserRecord) {
os_log("NoMAD Login Looking up info for: %{public}@", log: uiLog, type: .default, user.shortName)
setRequiredHintsAndContext()
setHint(type: .noMADFirst, hint: user.firstName)
setHint(type: .noMADLast, hint: user.lastName)
setHint(type: .noMADDomain, hint: domainName)
setHint(type: .noMADGroups, hint: user.groups)
completeLogin(authResult: .allow)
var allowedLogin = true
os_log("Checking for DenyLogin groups", log: uiLog, type: .debug)
if let adminGroups = getManagedPreference(key: .DenyLoginUnlessGroupMember) as? [String] {
os_log("Found a CreateAdminIfGroupMember key value: %{public}@ ", log: uiLog, type: .debug, adminGroups)
// set the allowed login to false for now
allowedLogin = false
user.groups.forEach { group in
if adminGroups.contains(group) {
allowedLogin = true
os_log("User is a member of %{public}@ group. Setting allowedLogin = true ", log: uiLog, type: .debug, group)
}
}
}
if allowedLogin {
os_log("NoMAD Login Looking up info for: %{public}@", log: uiLog, type: .default, user.shortName)
setRequiredHintsAndContext()
setHint(type: .noMADFirst, hint: user.firstName)
setHint(type: .noMADLast, hint: user.lastName)
setHint(type: .noMADDomain, hint: domainName)
setHint(type: .noMADGroups, hint: user.groups)
setHint(type: .noMADFull, hint: user.cn)
setHint(type: .kerberos_principal, hint: user.userPrincipal)
// set the network auth time to be added to the user record
setHint(type: .networkSignIn, hint: String(describing: Date.init().description))
completeLogin(authResult: .allow)
} else {
authFail()
alertText.stringValue = "Not authorized to login."
showResetUI()
}
}
}
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment