public async Task Add() { ExternalDialogWindow w = new ExternalDialogWindow { Title = "Add authorization rule", SaveButtonIsDefault = true, Height = childWindowHeight, Width = childWindowWidth, }; var m = new SecurityDescriptorTarget(); var vm = await this.factory.CreateViewModelAsync(m, this.ChildDisplaySettings); w.DataContext = vm; if (w.ShowDialog() == true) { m.CreatedBy = WindowsIdentity.GetCurrent().User.ToString(); m.Created = DateTime.UtcNow; m.LastModifiedBy = WindowsIdentity.GetCurrent().User.ToString(); m.LastModified = m.Created; this.Model.Add(m); this.ViewModels.Add(vm); } }
public void SerializeSecurityDescriptorTarget() { SecurityDescriptorTarget s = new SecurityDescriptorTarget(); s.AuthorizationMode = AuthorizationMode.PowershellScript; s.Description = TestContext.CurrentContext.Random.GetString(); s.Id = TestContext.CurrentContext.Random.GetString(); s.Script = TestContext.CurrentContext.Random.GetString(); s.SecurityDescriptor = TestContext.CurrentContext.Random.GetString(); s.Target = TestContext.CurrentContext.Random.GetString(); s.Type = TargetType.Container; s.Laps.ExpireAfter = TimeSpan.FromSeconds(TestContext.CurrentContext.Random.Next()); s.Laps.RetrievalLocation = PasswordStorageLocation.LithnetAttribute; s.Jit.AuthorizingGroup = TestContext.CurrentContext.Random.GetString(); s.Jit.ExpireAfter = TimeSpan.FromSeconds(TestContext.CurrentContext.Random.Next()); s.Notifications.OnFailure.Add(TestContext.CurrentContext.Random.GetString()); s.Notifications.OnSuccess.Add(TestContext.CurrentContext.Random.GetString()); SecurityDescriptorTarget n = JsonConvert.DeserializeObject <SecurityDescriptorTarget>(JsonConvert.SerializeObject(s)); Assert.AreEqual(s.AuthorizationMode, n.AuthorizationMode); Assert.AreEqual(s.Description, n.Description); Assert.AreEqual(s.Id, n.Id); Assert.AreEqual(s.Script, n.Script); Assert.AreEqual(s.SecurityDescriptor, n.SecurityDescriptor); Assert.AreEqual(s.Target, n.Target); Assert.AreEqual(s.Type, n.Type); Assert.AreEqual(s.Laps.ExpireAfter, n.Laps.ExpireAfter); Assert.AreEqual(s.Laps.RetrievalLocation, n.Laps.RetrievalLocation); Assert.AreEqual(s.Jit.AuthorizingGroup, n.Jit.AuthorizingGroup); Assert.AreEqual(s.Jit.ExpireAfter, n.Jit.ExpireAfter); CollectionAssert.AreEqual(s.Notifications.OnFailure, n.Notifications.OnFailure); CollectionAssert.AreEqual(s.Notifications.OnSuccess, n.Notifications.OnSuccess); }
public SecurityDescriptorTargetViewModel(SecurityDescriptorTarget model, SecurityDescriptorTargetViewModelDisplaySettings displaySettings, INotificationChannelSelectionViewModelFactory notificationChannelFactory, IFileSelectionViewModelFactory fileSelectionViewModelFactory, IAppPathProvider appPathProvider, ILogger <SecurityDescriptorTargetViewModel> logger, IDialogCoordinator dialogCoordinator, IModelValidator <SecurityDescriptorTargetViewModel> validator, IDirectory directory, IDomainTrustProvider domainTrustProvider, IDiscoveryServices discoveryServices, ILocalSam localSam, IObjectSelectionProvider objectSelectionProvider, ScriptTemplateProvider scriptTemplateProvider, IAmsLicenseManager licenseManager, IShellExecuteProvider shellExecuteProvider) { this.directory = directory; this.Model = model; this.logger = logger; this.dialogCoordinator = dialogCoordinator; this.notificationChannelFactory = notificationChannelFactory; this.Validator = validator; this.domainTrustProvider = domainTrustProvider; this.discoveryServices = discoveryServices; this.localSam = localSam; this.displaySettings = displaySettings ?? new SecurityDescriptorTargetViewModelDisplaySettings(); this.objectSelectionProvider = objectSelectionProvider; this.scriptTemplateProvider = scriptTemplateProvider; this.licenseManager = licenseManager; this.shellExecuteProvider = shellExecuteProvider; this.Script = fileSelectionViewModelFactory.CreateViewModel(model, () => model.Script, appPathProvider.ScriptsPath); this.Script.DefaultFileExtension = "ps1"; this.Script.Filter = "PowerShell script|*.ps1"; this.Script.NewFileContent = this.scriptTemplateProvider.GetAuthorizationResponse; this.Script.ShouldValidate = false; this.Script.PropertyChanged += Script_PropertyChanged; this.Initialization = this.Initialize(); }
public async Task <SecurityDescriptorTargetViewModel> CreateViewModelAsync(SecurityDescriptorTarget model, SecurityDescriptorTargetViewModelDisplaySettings settings) { var item = new SecurityDescriptorTargetViewModel(model, settings, channelSelectionViewModelFactory, fileSelectionViewModelFactory, appPathProvider, logger, dialogCoordinator, validator.Invoke(), directory, domainTrustProvider, discoveryServices, localSam, objectSelectionProvider, scriptTemplateProvider, licenseManager, shellExecuteProvider); await item.Initialization; return(item); }
public SecurityDescriptorTargetViewModel(SecurityDescriptorTarget model, INotificationChannelSelectionViewModelFactory notificationChannelFactory, IFileSelectionViewModelFactory fileSelectionViewModelFactory, IAppPathProvider appPathProvider, ILogger <SecurityDescriptorTargetViewModel> logger, IDialogCoordinator dialogCoordinator) { this.directory = new ActiveDirectory(); this.Model = model; this.logger = logger; this.dialogCoordinator = dialogCoordinator; this.Script = fileSelectionViewModelFactory.CreateViewModel(model, () => model.Script, appPathProvider.ScriptsPath); this.Script.DefaultFileExtension = "ps1"; this.Script.Filter = "PowerShell script|*.ps1"; this.Script.NewFileContent = ScriptTemplates.AuthorizationScriptTemplate; this.Script.ShouldValidate = false; this.Notifications = notificationChannelFactory.CreateViewModel(model.Notifications); }
private SecurityIdentifier GetSid(SecurityDescriptorTarget target) { if (target.Type == TargetType.Container) { return(null); } if (target.Target == null) { throw new ArgumentNullException(nameof(target.Target), "The target was null"); } return(new SecurityIdentifier(target.Target)); }
private int GetSortOrderInternal(SecurityDescriptorTarget target) { try { if (target.Type == TargetType.Container && !string.IsNullOrWhiteSpace(target.Target)) { X500DistinguishedName x500 = new X500DistinguishedName(target.Target); return(x500.Decode(X500DistinguishedNameFlags.UseNewLines)?.Split("\r\n")?.Length ?? 0); } } catch (Exception ex) { this.logger.LogWarning(EventIDs.DNParseError, ex, $"Unable to parse DN {target.Target}. Using default sort order of 0"); } return(0); }
private SecurityDescriptorTarget ConvertToTarget(OUPrincipalMapping entry, HashSet <SecurityIdentifier> admins) { this.logger.LogTrace("Creating new target for OU {ou} with the following principals\r\n{admins}", entry.AdsPath, string.Join(", ", admins)); SecurityDescriptorTarget target = new SecurityDescriptorTarget() { AuthorizationMode = AuthorizationMode.SecurityDescriptor, Description = settings.RuleDescription?.Replace("{targetName}", entry.OUName, StringComparison.OrdinalIgnoreCase), Target = entry.OUName, Type = TargetType.Container, Id = Guid.NewGuid().ToString(), Notifications = settings.Notifications, Jit = new SecurityDescriptorTargetJitDetails() { AuthorizingGroup = settings.JitAuthorizingGroup, ExpireAfter = settings.JitExpireAfter }, Laps = new SecurityDescriptorTargetLapsDetails() { ExpireAfter = settings.LapsExpireAfter } }; AccessMask mask = 0; mask |= settings.AllowLaps ? AccessMask.LocalAdminPassword : 0; mask |= settings.AllowJit ? AccessMask.Jit : 0; mask |= settings.AllowLapsHistory ? AccessMask.LocalAdminPasswordHistory : 0; mask |= settings.AllowBitLocker ? AccessMask.BitLocker : 0; DiscretionaryAcl acl = new DiscretionaryAcl(false, false, admins.Count); foreach (var sid in admins) { acl.AddAccess(AccessControlType.Allow, sid, (int)mask, InheritanceFlags.None, PropagationFlags.None); } CommonSecurityDescriptor sd = new CommonSecurityDescriptor(false, false, ControlFlags.DiscretionaryAclPresent, new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), null, null, acl); target.SecurityDescriptor = sd.GetSddlForm(AccessControlSections.All); return(target); }
public TargetData GetTargetData(SecurityDescriptorTarget target) { var item = this.targetDataCache.Get <TargetData>(target.Id); if (item == null || item.Target != target.Target) { item = new TargetData() { ContainerGuid = this.GetContainerGuid(target), Target = target.Target, Sid = this.GetSid(target), SortOrder = this.GetSortOrderInternal(target) }; } this.targetDataCache.Set(target.Id, item); return(item); }
private Guid GetContainerGuid(SecurityDescriptorTarget target) { try { if (target.Type == TargetType.Container) { DirectoryEntry de = new DirectoryEntry($"LDAP://{target.Target}"); return(de.Guid); } else { return(Guid.Empty); } } catch (Exception ex) { this.logger.LogError(EventIDs.TargetDirectoryLookupError, ex, $"Could not find GUID for target {target.Target}"); return(Guid.Empty); } }
public SecurityDescriptorTargetViewModel CreateViewModel(SecurityDescriptorTarget model) { return(new SecurityDescriptorTargetViewModel(model, channelSelectionViewModelFactory, fileSelectionViewModelFactory, appPathProvider, logger, dialogCoordinator)); }
private AuthorizationResponse BuildAuthZResponseSuccess(AccessMask requestedAccess, SecurityDescriptorTarget matchedTarget, IComputer computer) { AuthorizationResponse response; if (requestedAccess == AccessMask.LocalAdminPassword) { response = new LapsAuthorizationResponse() { ExpireAfter = matchedTarget.Laps.ExpireAfter, RetrievalLocation = matchedTarget.Laps.RetrievalLocation }; } else if (requestedAccess == AccessMask.LocalAdminPasswordHistory) { response = new LapsHistoryAuthorizationResponse(); } else if (requestedAccess == AccessMask.Jit) { response = new JitAuthorizationResponse() { ExpireAfter = matchedTarget.Jit.ExpireAfter, AuthorizingGroup = this.jitResolver.GetJitGroup(computer, matchedTarget.Jit.AuthorizingGroup).MsDsPrincipalName, }; } else { throw new AccessManagerException("An invalid access mask was requested"); } response.MatchedRule = matchedTarget.Id; response.MatchedRuleDescription = matchedTarget.Description ?? $"{matchedTarget.Type}: {this.TryGetNameIfSid(matchedTarget.Target)}"; response.Code = AuthorizationResponseCode.Success; response.NotificationChannels = this.GetNotificationRecipients(matchedTarget.Notifications, true); return(response); }
private AuthorizationResponse BuildAuthZResponseRateLimitExceeded(IUser user, IComputer computer, AccessMask requestedAccess, RateLimitResult result, IPAddress ip, SecurityDescriptorTarget matchedTarget) { this.logger.LogError(result.IsUserRateLimit ? EventIDs.RateLimitExceededUser : EventIDs.RateLimitExceededIP, $"User {user.MsDsPrincipalName} on IP {ip} is denied {requestedAccess} access for computer {computer.MsDsPrincipalName} because they have exceeded the {(result.IsUserRateLimit ? "user" : "IP")} rate limit of {result.Threshold}/{result.Duration.TotalSeconds} seconds"); AuthorizationResponse response = AuthorizationResponse.CreateAuthorizationResponse(requestedAccess); response.Code = result.IsUserRateLimit ? AuthorizationResponseCode.UserRateLimitExceeded : AuthorizationResponseCode.IpRateLimitExceeded; response.NotificationChannels = this.GetNotificationRecipients(matchedTarget.Notifications, false); return(response); }
public int GetSortOrder(SecurityDescriptorTarget target) { return(this.GetTargetData(target).SortOrder); }
public ImportResults Import() { ImportResults results = new ImportResults(); string globalChannelId = null; bool onSuccessGlobal = false; bool onFailureGlobal = false; Dictionary <string, SmtpNotificationChannelDefinition> notificationDefinitions = null; string xml = File.ReadAllText(settings.ImportFile); XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); XmlNode appRootNode = doc.SelectSingleNode("/configuration/lithnet-laps"); if (appRootNode == null) { throw new ImportException("The specified file did not appear to be a Lithnet LAPS Web App config file"); } if (settings.ImportNotifications) { notificationDefinitions = CreateNotificationChannelDefinitions(appRootNode); foreach (KeyValuePair <string, SmtpNotificationChannelDefinition> item in notificationDefinitions) { results.NotificationChannels.Smtp.Add(item.Value); } globalChannelId = this.GetNotificationChannelDefinitionId(appRootNode, notificationDefinitions, out onSuccessGlobal, out onFailureGlobal); } XmlNodeList targetNodes = doc.SelectNodes("/configuration/lithnet-laps/targets/target"); if (targetNodes == null || targetNodes.Count == 0) { return(results); } foreach (XmlElement targetNode in targetNodes.OfType <XmlElement>()) { this.OnItemProcessStart?.Invoke(this, new ImportProcessingEventArgs($"Processing {targetNode.SelectSingleNode("@name")?.Value}")); SecurityDescriptorTarget target = this.ConvertToSecurityDescriptorTarget(targetNode, out List <DiscoveryError> discoveryErrors); if (discoveryErrors.Count > 0) { results.DiscoveryErrors.AddRange(discoveryErrors); } if (target == null) { continue; } if (settings.ImportNotifications) { string channelId = this.GetNotificationChannelDefinitionId(targetNode, notificationDefinitions, out bool onSuccess, out bool onFailure); if (channelId != null) { if (onSuccess) { target.Notifications.OnSuccess.Add(channelId); } if (onFailure) { target.Notifications.OnFailure.Add(channelId); } } if (onSuccessGlobal && globalChannelId != null) { target.Notifications.OnSuccess.Add(globalChannelId); } if (onFailureGlobal && globalChannelId != null) { target.Notifications.OnFailure.Add(globalChannelId); } } results.Targets.Add(target); this.OnItemProcessFinish?.Invoke(this, new ImportProcessingEventArgs($"Processing {targetNode.SelectSingleNode("@name")?.Value}")); } return(results); }
private SecurityDescriptorTarget ConvertToSecurityDescriptorTarget(XmlElement node, out List <DiscoveryError> discoveryErrors) { discoveryErrors = new List <DiscoveryError>(); SecurityDescriptorTarget target = new SecurityDescriptorTarget(); string name = node.SelectSingleNode("@name")?.Value; string type = node.SelectSingleNode("@type")?.Value; string expireAfter = node.SelectSingleNode("@expire-after")?.Value; List <string> readers = node.SelectNodes("readers/reader/@principal")?.OfType <XmlAttribute>().Select(t => t.Value).ToList(); string targetFriendlyName; if (string.IsNullOrWhiteSpace(name)) { this.logger.LogWarning("XmlElement had a null name"); return(null); } if (string.Equals(type, "container", StringComparison.OrdinalIgnoreCase)) { target.Type = TargetType.Container; target.Target = name; targetFriendlyName = name; } else if (string.Equals(type, "computer", StringComparison.OrdinalIgnoreCase)) { target.Type = TargetType.Computer; if (this.directory.TryGetComputer(name, out IComputer computer)) { target.Target = computer.Sid.ToString(); targetFriendlyName = computer.MsDsPrincipalName; } else { discoveryErrors.Add(new DiscoveryError { Target = name, Type = DiscoveryErrorType.Error, Message = $"The computer was not found in the directory" }); return(null); } } else if (string.Equals(type, "group", StringComparison.OrdinalIgnoreCase)) { target.Type = TargetType.Group; if (this.directory.TryGetGroup(name, out IGroup group)) { target.Target = group.Sid.ToString(); targetFriendlyName = group.MsDsPrincipalName; } else { discoveryErrors.Add(new DiscoveryError { Target = name, Type = DiscoveryErrorType.Error, Message = $"The group was not found in the directory" }); return(null); } } else { discoveryErrors.Add(new DiscoveryError { Target = name, Type = DiscoveryErrorType.Error, Message = $"Target was of an unknown type: {type}" }); return(null); } if (readers == null || readers.Count == 0) { discoveryErrors.Add(new DiscoveryError { Target = name, Type = DiscoveryErrorType.Warning, Message = $"Target had not authorized readers" }); return(null); } if (!string.IsNullOrWhiteSpace(expireAfter)) { if (TimeSpan.TryParse(expireAfter, out TimeSpan timespan)) { if (timespan.TotalMinutes > 0) { target.Laps.ExpireAfter = timespan; } } } target.AuthorizationMode = AuthorizationMode.SecurityDescriptor; target.Description = settings.RuleDescription.Replace("{targetName}", targetFriendlyName, StringComparison.OrdinalIgnoreCase); foreach (string onSuccess in settings.Notifications.OnSuccess) { target.Notifications.OnSuccess.Add(onSuccess); } foreach (string onFailure in settings.Notifications.OnFailure) { target.Notifications.OnFailure.Add(onFailure); } AccessMask mask = AccessMask.LocalAdminPassword; mask |= settings.AllowLapsHistory ? AccessMask.LocalAdminPasswordHistory : 0; mask |= settings.AllowJit ? AccessMask.Jit : 0; mask |= settings.AllowBitLocker ? AccessMask.BitLocker : 0; DiscretionaryAcl acl = new DiscretionaryAcl(false, false, readers.Count); foreach (string reader in readers) { if (directory.TryGetPrincipal(reader, out ISecurityPrincipal principal)) { acl.AddAccess(AccessControlType.Allow, principal.Sid, (int)mask, InheritanceFlags.None, PropagationFlags.None); } else { discoveryErrors.Add(new DiscoveryError { Target = name, Type = DiscoveryErrorType.Warning, Principal = reader, Message = $"The principal could not be found in the directory" }); } } if (acl.Count == 0) { discoveryErrors.Add(new DiscoveryError { Target = name, Type = DiscoveryErrorType.Warning, Message = $"Target had no authorized readers" }); return(null); } CommonSecurityDescriptor sd = new CommonSecurityDescriptor(false, false, ControlFlags.DiscretionaryAclPresent, new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), null, null, acl); target.SecurityDescriptor = sd.GetSddlForm(AccessControlSections.All); target.Jit.AuthorizingGroup = settings.JitAuthorizingGroup; target.Jit.ExpireAfter = settings.JitExpireAfter; return(target); }