Showing posts with label Security. Show all posts
Showing posts with label Security. Show all posts

Tuesday, April 8, 2014

Enumerating TFS Permissions for items within the TPC for audit and logging

Below is a powershell cmdlet that I threw together to enumerate data out of our TFS instance.  This is used to keep track of permissions in the event we need to re-create them and to allow us to audit it to make sure things don't change.

This can also be used to keep a pre-production environment's permissions in sync with Production.

In 2014 I was not the best at Powershell but it works :)

param ($serverName = $(throw 'please specify a TFS server name'))

function GetGroupMembership {
 [CmdletBinding()]
 [OutputType([System.Data.Datatable])]
 PARAM (
  [Parameter(Mandatory=$true, Position = 0)]
  [Microsoft.TeamFoundation.Framework.Client.TeamFoundationIdentity]
  $TFIdentity
 )
 
 $tabName = $TFIdentity.DisplayName + "Membership"
 $table = New-Object system.Data.DataTable “$tabName”
 $col1 = New-Object system.Data.DataColumn GroupName,([string])
 $col2 = New-Object system.Data.DataColumn DisplayName,([string])
 $col3 = New-Object system.Data.DataColumn UniqueName,([string])

 #Add the Columns
 $table.columns.add($col1)
 $table.columns.add($col2)
 $table.columns.add($col3)
  
 #Membership
 $GroupIdentity = $ims.ReadIdentity($TFIdentity.Descriptor,[Microsoft.TeamFoundation.Framework.Common.MembershipQuery]::Direct,[Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::TrueSid)
 $members = $ims.ReadIdentities($GroupIdentity.Members,[Microsoft.TeamFoundation.Framework.Common.MembershipQuery]::Direct,[Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::TrueSid)
 foreach($member in $members)
 {
  $row = $table.NewRow()

  #Enter data in the row
  $row.GroupName = $TFIdentity.DisplayName
  $row.DisplayName = $member.DisplayName
  $row.UniqueName = $member.UniqueName
  
  #Add the row to the table
  $table.Rows.Add($row)
 }
 
 return @(,$table )
}

function GetPermissions {
 [CmdletBinding()]
 [OutputType([System.Data.Datatable])]
 PARAM (
  [Parameter(Mandatory=$true, Position = 0)]
  $QueryName,
  [Parameter(Mandatory=$true, Position = 1)]
  [Microsoft.TeamFoundation.Framework.Client.AccessControlList]
  $acl,
  [Parameter(Mandatory=$true, Position = 2)]
  $namespace,
  [Parameter(Mandatory=$true, Position = 3)]
  $objectType,
  [Parameter(Mandatory=$true, Position = 4)]
  $objectPath
 )
 
 $PermissionstabName = $QueryName
 
 $table = New-Object system.Data.DataTable “$PermissionstabName”
 $col1 = New-Object system.Data.DataColumn ObjectPath,([string])
 $col2 = New-Object system.Data.DataColumn ObjectType,([string])
 $col3 = New-Object system.Data.DataColumn Name,([string])
 $col4 = New-Object system.Data.DataColumn Permission,([string])
 $col5 = New-Object system.Data.DataColumn Value,([string])

 #Add the Columns
 $table.columns.add($col1)
 $table.columns.add($col2)
 $table.columns.add($col3)
 $table.columns.add($col4)
 $table.columns.add($col5)

 foreach ($ace in $acl.AccessControlEntries)
 {
  if ($ace.IsEmpty)
  {
   continue
  }
  
  $haspermission = $false
  $DenyPermissions = @{}
  $CalculatedPermissions = @{}
  $AllowPermissions = @{}
  foreach ($action in $namespace.description.Actions)
  {
   
   $allowed = ($action.bit -band $ace.Allow) 
   $denied =  ($action.bit -band $ace.Deny)
   if (-not $allowed -and -not $denied)
   {
    continue
   }
   $haspermission  =$true
   if ($allowed)
   { 
    $CalculatedPermissions.Add($action.Name,"Allow")
    $AllowPermissions.Add($action.Name,$action.Name)
   }
   else
   {
    $CalculatedPermissions.Add($action.Name,"Deny")
    $DenyPermissions.Add($action.Name,$action.Name)
   }
   
  }
  
  if ($haspermission)
  {
   
   $identity = $ims.ReadIdentity($ace.Descriptor,[Microsoft.TeamFoundation.Framework.Common.MembershipQuery]::None,[Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::IncludeReadFromSource)
   
   $name = $identity.DisplayName
   
   Foreach($permission in $CalculatedPermissions.GetEnumerator())
   {
    $row = $table.NewRow()

    #Enter data in the row
    $row.ObjectPath = $objectPath 
    $row.ObjectType = $objectType
    $row.Name = $name
    $row.Permission = $permission.Name
    $row.Value = $permission.value

    #Add the row to the table
    $table.Rows.Add($row)
   }
  }
 }
 
 return @(,$table )
}

[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.VersionControl.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.WorkItemTracking.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Build.Client')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation.Build.Common')
[void][Reflection.Assembly]::LoadWithPartialName('Microsoft.TeamFoundation')

$tfs = [Microsoft.TeamFoundation.Client.TeamFoundationServerFactory]::GetServer($serverName)

$css = $tfs.GetService([Microsoft.TeamFoundation.Server.ICommonStructureService])
$auth = $tfs.GetService([Microsoft.TeamFoundation.Server.IAuthorizationService])
$gss = $tfs.GetService([Microsoft.TeamFoundation.Server.IGroupSecurityService])
$ss = $tfs.GetService([Microsoft.TeamFoundation.Framework.Client.ISecurityService])
$vcs = $tfs.GetService([Microsoft.TeamFoundation.VersionControl.Client.VersionControlServer])
$ims = $tfs.GetService([Microsoft.TeamFoundation.Framework.Client.IIdentityManagementService])
$wis = $tfs.GetService([Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemStore]) 
$cssNamespace = $ss.GetSecurityNamespace([Microsoft.TeamFoundation.Server.AuthorizationSecurityConstants]::CommonStructureNodeSecurityGuid)

$membershipds = New-Object System.Data.DataSet
$permissiondt = New-Object System.Data.DataSet
$Objectpermissiondt = New-Object System.Data.DataSet

#get all TPC Groups
$tpcGroups = $IMS.ListApplicationGroups($null, [Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::None)

foreach ($tpcgroup in $tpcGroups)
{
 
 $members = GetGroupMembership $tpcgroup
 if ($members.rows.count -gt 0)
 {
  $membershipds.Tables.Add($members)
 }
}
$Namespaces = $ss.GetSecurityNamespaces()
#Get all the permissions for top level NameSpaces
foreach($namespace in $Namespaces)
{
 
 $NameSpaceToken = ""
 
    switch ($namespace.Description.DisplayName)
 {
  "Server" { $NameSpaceToken =  [Microsoft.TeamFoundation.Framework.Common.FrameworkSecurity]::FrameworkNamespaceToken}
  "Build" { $NameSpaceToken =  [Microsoft.TeamFoundation.Build.Common.BuildSecurity]::PrivilegesToken}
  "BuildAdministration" { $NameSpaceToken =  [Microsoft.TeamFoundation.Build.Common.BuildSecurity]::PrivilegesToken}
  "Workspaces" {  $NameSpaceToken =  [Microsoft.TeamFoundation.VersionControl.Common.SecurityConstants]::GlobalSecurityResource}
  "Collection" {}#Namespace:
  "WorkItemTrackingAdministration" {} #
  "CSS" {  }
  "Iteration" {}
  "VersionControlPrivileges" {$NameSpaceToken =  [Microsoft.TeamFoundation.VersionControl.Common.SecurityConstants]::GlobalSecurityResource} 
  "WorkItemQueryFolders" {}
  "Project" {  $NameSpaceToken = [Microsoft.TeamFoundation.PermissionNamespaces]::Project}
  default
  {
  continue
  }
 }
 try
 {
  $groupAcl = $namespace.QueryAccessControlList($NameSpaceToken,$null,$false)
  $table = GetPermissions $namespace.Discription.Name $groupAcl $namespace "Group" $namespace.Discription.Name
  $permissiondt.Tables.Add($table)
 }
 Catch
 {
 
 }
}

foreach ($project in $css.ListProjects())
{
 $projectGroups = $IMS.ListApplicationGroups($project.Uri,[Microsoft.TeamFoundation.Framework.Common.ReadIdentityOptions]::TrueSid)
  
 foreach($group in $projectGroups)
 {
 
  #only get the groups we care about
  if ($group.DisplayName -eq "[$($project.Name)]\Build Administrators" -or $group.DisplayName -eq "[$($project.Name)]\Project Administrators" -or $group.DisplayName -eq "[$($project.Name)]\Contributors" -or $group.DisplayName -eq "[$($project.Name)]\Readers")
  {
   
  }
  else
  {
   continue;
  }
  
  $members = GetGroupMembership $group
  if ($members.rows.count -gt 0)
  {
   $membershipds.Tables.Add($members)
  }
  
  $projectsecNameSpace = $ss.GetSecurityNamespace([Microsoft.TeamFoundation.Server.AuthorizationSecurityConstants]::ProjectSecurityGuid)
  
  #Get Project Permissions
  $ProjectSecurityToken = [Microsoft.TeamFoundation.Server.AuthorizationSecurityConstants]::ProjectSecurityPrefix + $project.Uri
  $groupacl = $projectsecNameSpace.QueryAccessControlList($ProjectSecurityToken,  [Microsoft.TeamFoundation.Framework.Client.IdentityDescriptor[]]@($group.descriptor), $false)
   
  $table =  GetPermissions $group.DisplayName  $groupAcl $projectsecNameSpace "Project" $project.Name
  $permissiondt.Tables.Add($table)
  
  #Get Top Level Build Permissions for the Project
  $BuildsecNameSpace = $ss.GetSecurityNamespace([Microsoft.TeamFoundation.Build.Common.BuildSecurity]::BuildNamespaceId)
  
  $teamProjectGuid = [Microsoft.TeamFoundation.LinkingUtilities]::DecodeUri($project.Uri).ToolSpecificId
  $groupacl = $BuildsecNameSpace.QueryAccessControlList($teamProjectGuid,  $null, $false)
  $tablename = $group.DisplayName + "Build Permissions"
  $table = GetPermissions $tablename $groupAcl $BuildsecNameSpace "Build" $project.Name
  
  $permissiondt.Tables.Add($table)
 }
 
 #Get Root Area Path Permissions
 $rootAreaNodeACL = $cssNamespace.QueryAccessControlList($wis.Projects[$project.Name].AreaRootNodeUri,$null,$false)
 $table = GetPermissions "$($project.Name) Root AreaPath Permissions" $rootAreaNodeACL  $cssNamespace "Area Path" $wis.Projects[$project.Name].Name
 $Objectpermissiondt.Tables.Add($table)

  
 #Get Child Area Path Permissions 
 foreach ($area in $wis.Projects[$project.Name].AreaRootNodes)
 {
  $areapath = $area.Path
  
  $areaseclist = $cssNamespace.QueryAccessControlList($area.uri, $null, $true)
  
  $table = GetPermissions "$areapath AreaPath Permissions" $areaseclist  $cssNamespace "Area Path" $areapath
  $Objectpermissiondt.Tables.Add($table)
 }
 
 try
 {
  $vcproject = $vcs.TryGetTeamProject($project.Name)
 }
 catch
 {
  continue
 }
 
 #Get Version Control permissions on all VC objects
 $vcAcls = $vcs.GetPermissions(@($vcproject.ServerItem), [Microsoft.TeamFoundation.VersionControl.Client.RecursionType]::Full)
  
 $table = New-Object system.Data.DataTable "$($project.Name)VCPermissions"
 $col1 = New-Object system.Data.DataColumn ObjectPath,([string])
 $col2 = New-Object system.Data.DataColumn ObjectType,([string])
 $col3 = New-Object system.Data.DataColumn Name,([string])
 $col4 = New-Object system.Data.DataColumn Permission,([string])
 $col5 = New-Object system.Data.DataColumn Value,([string])

 #Add the Columns
 $table.columns.add($col1)
 $table.columns.add($col2)
 $table.columns.add($col3)
 $table.columns.add($col4)
 $table.columns.add($col5)
 
 foreach($perm in $vcAcls)
 {
  foreach($entry in $perm.Entries)
  {
   foreach($allow in $entry.Allow)
   {
    
    $row = $table.NewRow()
    $row.ObjectPath = $perm.ServerItem
    $row.ObjectType = "VC"
    $row.Name = $entry.IdentityName
    $row.Permission = $allow
    $row.Value = "allow"
    
    #Add the row to the table
    $table.Rows.Add($row)
   }
   foreach($deny in $entry.Deny)
   {
    
    $row = $table.NewRow()
    $row.ObjectPath = $perm.ServerItem
    $row.ObjectType = "VC"
    $row.Name = $entry.IdentityName
    $row.Permission = $deny
    $row.Value = "deny"
    
    #Add the row to the table
    $table.Rows.Add($row)
   }
  }
 }
 
 $Objectpermissiondt.Tables.Add($table)
}

$membershipds.Tables | Format-Table -AutoSize 
$permissiondt.Tables | Format-Table -AutoSize 
$Objectpermissiondt.Tables | Format-Table -AutoSize

Saturday, June 18, 2011

Securing a TS connection with Smart Cards

Repost from old blog
Well today is going to be a busy day here at the Microsoft Enterprise Engineering Center. At about 11:30 I am going to have lunch some of the RedvsBlue people (http://www.redvsblue.com). Then I have the job of setting up and testing a remote access solution for a customer. Note that the steps below are technical and you should know a little something about Active Directory and PKI before you start.

The problem: How do you provide a customer secure remote access into a scenario?
Solution: Smart cards and Terminal Services on Windows Server 2003! Smart cards keep the Private PKI key stored on the card, and cannot be removed from the card. It requires two-factor authentication. Something you have (the smart card) and something you know (the PIN).

How: First we set up a DC (or 2). Then install cert services on box (domain member). Then install Terminal Services on to another box (aka the Jump box).

We are using the Microsoft Base CSP (http://www.microsoft.com/resources/documentation/WindowsServ/2003/all/techref/en-us/Default.asp?url=/Resources/Documentation/windowsserv/2003/all/techref/en-us/w2k3tr_certs_how.asp) on some Amtel Smart cards.

I create user accounts in the Domain for each person that will need remote access. Each Account has a Very LONG and Very RANDOM password. This is because the user will never use a password. They will not be able to use their smart card. Once the users are created make sure that you go in the properties of each user and under the Account tab select the following options: User Cannot change password, Password never expires and Smart Card is required for interactive logon.

Now create a separate Admin account. This will be the account that I use to manage the TS (terminal Services) jump box and issue the smart cards. Make sure it has a secure password.
 
To issue a smart card the issuing user account must have an Enrollment Agent Cert. To get this cert you must first enable it on the CA. (Open the CA MMC snapin -> select Certificate Templates -> Right-click on Certificate Templates -> Select New -> Select Certificate Template to issue. In the list select Enrollment Agent.

Repeat the steps above to also add the Smart Card Logon Cert. This is the cert that we will put on each Smart Card.

After you have enabled that cert (Enrollment Agent) you must now request it with the user account that will be issuing the Smart Cards. So for my solution that is the separate admin account. I will use runas.exe /u:<domainName>\<adminUser> "mmc certmgr.msc". This will allow me to open the MMC without logging in as that user.

To request the new cert select personal -> right-click on personal -> select all tasks -> select Request new Cert... Once the Wizard opens select Next to start it -> Select Enrollment Agent -> Select Next -> Enter a Name (like SC enrollment Cert) -> Select Next -> then Select Finish to issue the cert.

Now we will need to make sure we have our smart card reader installed on the machine that has our enrollment Cert and http access to the CA. With all of the above done you can now open a web browser to http://<CA Machine Name>/certsrv/certsces.asp (note: that you might have to add this site to your trusted sites to get the Active X control to load).

From this browser window we can select the type of Cert we want to put on the Smart Card. We are going to use Smart Card Logon Cert. You will need to select the Microsoft Base Smart Card Crypto Provider as your CSP. The Administrator Signing Certificate is the Enrollment Cert that we requested earlier. At this point you can select any user in the domain to issue a smart card for.

One thing to note is that when you have a smart card for user account it does not matter if the user changes their password. The smart card will allow the user to be authenticated without a password. So make sure that you keep accounts that have the Enrollment Agent Cert secure.

In the future I will also write about how to use the AD to further secure the Solution.

10 Years from last post

 Well world!   After the last almost 10 years I have been up to a few things in life and work.  Most recently I was working at Microsoft on ...