Troubleshooting Radius – IP Changes

Troubleshooting Radius – IP Changes

Last October, I ran across a client with a broken radius. I want to go through the process I used to troubleshoot the issue. The goal of this to bring you a level of understanding of the troubleshooting processes. Not every process is the same for each It related item. Getting exposed to different steps from different people helps out.

Scenario

Here is the scenario. The client called and stated that no one is able to connect to the wifi. I looked at the device and saw that they were connecting via Radius. Radius allows you to use your username and password for the domain to login into the wifi. It’s one of the more secure ways to setup wifi. I had no documentation to fall back on. Thus, I knew nothing about the radius setup. However, I did know about the wifi controller. It was an Unifi controller.

Troubleshooting Radius – Discovery

Since we know that the devices are connecting to the wifi that is controlled through the Unifi controller, the first logical step is to go to the Unifi controller. I logged into the Controller and went to the settings button at the bottom of the left-hand side of the menu. From there I clicked on the WiFi menu option. I want to look at the wifi profile of the Corporate wifi. The one they are trying to connect to. Next, I scrolled down to the Security area.

Under the Security area, You will see the Radius Profile. Take note of this name. We will call our bob. Once you have that name, Click the profile on the left-hand side of the screen.

At this point, we have discovered the Radius Profile Name. Next, we need to dig into the Profile itself. More information the better when troubleshooting radius. Once you click on the Profile, scroll down to the Radius Section. From here, I found the name of the profile from before and clicked it.

Here we could see the Authentication servers’ IP addresses and ports. Now we know which server Radius is living on. From here, I go to the devices and find the Device they are trying to connect to. Thankfully, the device was named correctly. If it isn’t, then that’s a whole other ball game. I noted the IP address and mac address of the device. The device was active with no connections.

Troubleshooting Radius on the Server

I used RDP to access the IP address with success. I am thankful because sometimes the radius can be setup using compliance of some sort. Next, I connected to the Network Policy Server. After that, I connected to the Radius clients. Looking over the Friendly names, and IP addresses from the Unifi controller and the Radius Server, the problem was clear.

DHCP change occurred on the access points. This meant the NPS radius client IPs were wrong. To correct this, all I have to do is update the NPS Radius client’s IP addresses. However, I don’t want this to happen again. So, here are the steps I took.

  1. Changed all the Access Points to Static instead of DHCP
  2. Change the NPS Radius Client IP addresses to match.

Once I did this, The client was able to reconnect to their wifi using their windows domain credentials.

Additional Reading:

Get Client Information From your Unifi Controller

Get Client Information From your Unifi Controller

Today I would like to go over how to get client information from your Unifi Controller. We will first connect to our Unifi Controller with Powershell using the Unifi API. Then from there we will pull each site and create a list of different sites. Finally, we will use the site information to pull the client information from the controller.

Powershell and API the Unifi Controller

The first step is to create a connection with our PowerShell. We will need a few items:

  • User Account that Has at least read access
  • The IP address of our controller
  • The API port.
  • An Unifi Controller that is configured correctly. (Most are now)

Ask your system administrator to create an account with read access. Check your configuration for the IP addresses and the Port numbers. By default, the API port is 8443. Now we have those pieces of information, its time to discuss what is needed.

Connecting to the API

To connect to the Unifi controller API, you need a Cookie. This is not like how we connected to the Graph API in our previous blog. To get this cookie, we will need to execute a post. The API URL is the IP address followed by the port number, API and login. Very standard.

$uri = "https://$($IPAddress):8443/api/login"

We will need to create a header that accepts jsons. To do this we simply say accept equal application/json.

$headers = @{'Accept' = 'application/json' }

Now we will create our Parameters for the body. Since this is going to be a Post command, we have to have a body. This is where we will place our Username and Password. We are going to build out a Hashtable and then convert that into a json since the API is expecting a json. We will use the convertto-json command to accomplish this step.

$params = @{
        'username' = $username;
        'password' = $Password;
}
$body = $params | ConvertTo-Json

Now we have our Body built out, it’s time to create a session. We will use the invoke-restmethod command to do this. We feed the URI we made a few steps back. The body will be the body we just built out. The content type will be ‘application/json’. Our headers will be the header we made a few steps back. We will skip the certificate check unless we are using a cert. Most of the time we are not. Finally, the secret sauce. We want a session. Thus, we use the Session Variable and give it a session name. Let’s go with S for the session.

$response = Invoke-RestMethod -Uri $uri `
        -Body $body `
        -Method Post `
        -ContentType 'application/json' `
        -Headers $headers `
        -SkipCertificateCheck `
        -SessionVariable s

Grabbing Sites

Now we have our Session cookie, its’ time to grab some information. We will use the same headers but this time we will use the Session Name, S. The URL for sites is /api/Self/Sites.

$uri = "https://$($IPaddress):8443/api/self/sites"

This URL will get the sites from your Unifi Controller. Once again we will use the Invoke-RestMethod to pull the data down. This time we are using a get method. The same Content type and headers will be used. Along with the skip certificate check. The Session Variable is going to be the Web Session and we will use the $S this time.

$sites = Invoke-RestMethod -Uri $uri `
        -Method Get `
        -ContentType 'application/json' `
        -Headers $headers `
        -SkipCertificateCheck `
        -Websession $s

The Sites variable will contain two items. a meta and a data variable. Everything we will need is inside the Data variable. To access it we can use the $sites.data to see all the data.

Here is an example of what the data will look like for the sites.

_id          : 5a604378614b1b0c6c3ef9a0
desc         : A descriptoin
name         : Ziipk5tAk
anonymous_id : 25aa6d32-a165-c1d5-73a0-ace01b433c14
role         : admin

Get Client Information from your Unifi Controller

Now that we have the sites, we can go one at a time and pull all the clients from that site. We will do this using a Foreach loop. There is something I want to point out first. Remember, the data that is returned is two variables, two arrays. The first one is Meta. Meta is the metadata of the session itself. It’s something we don’t need. We do need the Data. For our foreach loop, we will pull directly from the data array. We want to push everything in this loop into a variable. This way we can parse out useful data, sometime to much data is pointless.

$Clients = Foreach ($Site in $Sites.Data) {
    #Do something
}

Without using the $Sites.Data we will have to pull the data from the command itself. This can cause issues later if you want to do more complex things.

The URL we will be using is the /api/s/{site name}/stat/sta. We will be replacing the Site name with our $site.name variable and pushing all that into another Uri.

$Uri = "https://$($IPaddress):8443/api/s/$($Site.name)/stat/sta"

Then we will execute another invoke-restmethod. Same as before, we use the same headers, content type, and web session. The only difference is we wrap up the command inside preferences. This way we can pull the data directly while the command executes. Less code that way.

(Invoke-RestMethod -Uri $Uri `
            -Method Get `
            -ContentType 'application/json' `
            -Headers $headers `
            -SkipCertificateCheck `
            -Websession $s).data

Each time the command runs, it will release the data that we need and that will all drop into the $Clients variable. From here, we pull the information we want. The information that the client’s produce includes, the match addresses, times, IP addresses, possible names, IDs and more. So, it’s up to you at this point to pick and choose what you want.

The Script

The final script is different because I wanted to add site names and data to each output. But here is how I built it out. I hope you enjoy it.

function Get-UnifiSitesClients {
    param (
        [string]$IPaddress,
        [string]$username,
        [string]$portNumber,
        [string]$Password
    )
    
    $uri = "https://$($IPaddress):$portNumber/api/login"
    $headers = @{'Accept' = 'application/json' }
    $params = @{
        'username' = $username;
        'password' = $Password;
    }
    $body = $params | ConvertTo-Json

    $response = Invoke-RestMethod -Uri $uri `
        -Body $body `
        -Method Post `
        -ContentType 'application/json' `
        -Headers $headers `
        -SkipCertificateCheck `
        -SessionVariable s
    $uri = "https://$($IPaddress):$portNumber/api/self/sites"
    $sites = Invoke-RestMethod -Uri $uri `
        -Method Get `
        -ContentType 'application/json' `
        -Headers $headers `
        -SkipCertificateCheck `
        -Websession $s
    $Return = Foreach ($Site in $sites.data) {
        $Uri = "https://$($IPaddress):$portNumber/api/s/$($Site.name)/stat/sta"
        $Clients = (Invoke-RestMethod -Uri $Uri `
            -Method Get `
            -ContentType 'application/json' `
            -Headers $headers `
            -SkipCertificateCheck `
            -Websession $s).data
        Foreach ($Client in $Clients) {
            [pscustomobject][ordered]@{
                Site = $Site.name
                SiteDescritption = $Site.desc
                OUI = $client.OUI
                MacAddress = $client.mac 
                IPAddress = $Client.IP
                SwitchMac = $client.sw_mac
                SwitchPort = $client.sw_port
                WireRate = $client.wired_rate_mbps
            }
        }
    }
    $return 
}

Additional Resource

Time For a Break

Time For a Break

Welp is the end of the year. I have things in the works, however, I just started a new job. It requires me to learn many new and exciting things. While I learn these new things, I will be working on new blog posts for the upcoming year. Starting next year, I will be adding a monthly mental health blog post and tieing it into information technology. I will cover things like burnout, all-or-nothing thinking, avoiding becoming sour in IT, and much more. On the Information technology side of things, I will be adding items like a universal dashboard, Powershell with SQL, graph API, SharePoint items, and more. I will be diving deeper into items like unifi controllers, firewalls, and different networking stacks. Finally, I will be looking into more power automate and helping get people off the ground.

However, during this month, I need to take a break and focus on my family and the new job. Thank you all for supporting me this year with this transition.

Image created using Midjourney ai.

Azure AD Connect Unauthorized Error

Azure AD Connect Unauthorized Error

Today I was trying to sync a user’s account to the cloud and I received an error code that was new to me. Access denied, Unauthorized! I was using a domain admin account. I should have full access and rights. It threw me off as I have never seen such a thing. Why was I getting the “Azure AD Connect Unauthorized error”? This is what the error message looked like:

Retrieving the COM class factory for remote component with CLSID {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx}
from machine DC-01 failed due to the following error: 80070005 DC-01.
    + CategoryInfo          : WriteError: (Microsoft.Ident...ADSyncSyncCycle:StartADSyncSyncCycle)
      [Start-ADSyncSyncCycle], UnauthorizedAccessException
    + FullyQualifiedErrorId : Retrieving the COM class factory for remote component with CLSID
      {835BEE60-8731-4159-8BFF-941301D76D05} from machine DC-01 failed due to the following error: 
      80070005 DC-01.,Microsoft.IdentityManagement.PowerShell.Cmdlet.StartADSyncSyncCycle

I was lost for a second. After looking into ad for the ADSync groups and found that no user had access to any of the groups. There were 4 groups found: the Password Set, Operators, Browse, and admins.

I added the domain admins to the ADSync Admins. Once I logged out of the server and logged back into the server. Then I was able to complete an ADSync Cycle. This resolved the Azure AD Connect Unauthorized error message for me. Why were domain admins not present? I don’t know, but now I know where to look if I see this error again.

For more reading

URLs With PowerShell

URLs With PowerShell

Regex… Even the PowerShell Masters struggle with it from time to time. I helped a friend of mine with some URL chaos. The URL he had was a software download and the token was inside the URL. Yeah, it was weird but totally worth it. There were many different ways to handle this one. But the Matches was the best way to go. What was interesting about this interaction was I could later in another script. Unlike my other posts, this one’s format is going to be a little different. Just following along.

The URL (Example)

"https://.download.software.net/windows/64/Company_Software_TKN_0w6xBqqzwvw3PWkY87Tg301LTa2zRuPo09iBxamALBfs512rSgomfRROaohiwgJx9YH7bl9k72YwJ_riGzzD3wEFfXQ7jFZyi5USZfLtje2H68w/MSI/installer"

Here is the string example we are working with. Inside the software installer, we have the name of the software, “Company_Software_” and the token, “0w6xBqqzwvw3PWkY87Tg301LTa2zRuPo09iBxamALBfs512rSgomfRROaohiwgJx9YH7bl9k72YwJ_riGzzD3wEFfXQ7jFZyi5USZfLtje2H68w” The question is how do you extract the token from this URL? Regex’s Matches we summon you!

Matches is one of the regex’s powerhouse tools. It’s a simple method that allows us to search a string for a match of our Regex. This will allow us to pull the token from URLs with PowerShell. First, it’s best to link to useful documentation. Here is the official Microsoft documentation. Sometimes it’s helpful. Another very useful tool can be found here.

PowerShell’s Regex can be expressed in many different ways. The clearest and most concise way is to use the -match flag.

$String -match $Regex

This of course produces a true or false statement. Then if you call the $matches variable, you will see all of the matches. Another way of doing this is by using the type method.

[regex]::Matches($String, $Regex)

This method is the same as above, but sometimes it makes more sense when dealing with complex items. The types method is the closest to the “.net” framework.

The Regex

Next, let’s take a look at the Regex itself. We are wanting to pull everything between TKN_ and the next /. This was a fun one.

'_TKN_([^/]+)'

The first part is the token. We want our search to start at _TKN_. This clears out all of the following information automatically: https://.download.software.net/windows/64/Company_Software. A next part is a group. Notice the (). This creates a group for us to work with. Inside this group, we are searching for all the characters inside []. We are looking for Everything starting at where we are, the TKN_ to a matching /. We want all the information so we place a greedy little +. This selects our token. This regex produces two matches. One with the word _TKN_ and one without. Thus we will want to capture the second output, aka index of 1.

$String -match '_TKN_([^/]+)'
$Matches[1]

Another way to go about this is the method-type model.

$Token = [regex]::Matches($String, '_TKN_([^/]+)') | ForEach-Object { $_.Groups[1].Value }

It same concept, instead this time we are able to parse the output and grab from group one.

Replace Method

Another way to go about this is by using replace. This method is much easier to read for those without experience in regex. I always suggest making your scripts readable to yourself in 6 months. Breaking up the string using replace makes sense in this light. The first part of the string we want to pull out is everything before the _TKN_ and the TKN itself. The second part is everything after the /. Thus, we will be using two replaces for this process.

$String -replace(".*TKN_",'')

Here we are removing everything before and the TKN_ itself. The .* ensures this. Then we wrap this code up and run another replace.

$Token = ($String -replace(".*TKN_",'')) -replace('/.*','')

Now we have our token. This method is easier to follow.

Conclusion

In Conclusion, parsing URLs With PowerShell can be tough, but once you get a hang of it, life gets easier. Use the tools given to help understand what’s going on.

Additional Regex Posts

Image created with midjourney AI.