Category: System Administration

DNF5 History *UNDO*?!?

This is cool – I’ve only tested with a package I didn’t need and didn’t matter if it got mucked up. No idea if there’s an undo for, say, kernels. Or if undo on an update would roll back to the previous version. That’s the sort of testing to do on a sandbox that you don’t want running 30 minutes from now!

[root@fedora log]# dnf5 install bvi
Updating and loading repositories:
Repositories loaded.
Package Arch Version Repository Size
Installing:
bvi x86_64 1.5.0-1.fc43 fedora 157.1 KiB

Transaction Summary:
Installing: 1 package

Total size of inbound packages is 83 KiB. Need to download 83 KiB.
After this operation, 157 KiB extra will be used (install 157 KiB, remove 0 B).
Is this ok [y/N]: y
[1/1] bvi-0:1.5.0-1.fc43.x86_64 100% | 351.6 KiB/s | 83.0 KiB | 00m00s
———————————————————————————————————————————————————————————————————————————————
[1/1] Total 100% | 164.9 KiB/s | 83.0 KiB | 00m01s
Running transaction
[1/3] Verify package files 100% | 142.0 B/s | 1.0 B | 00m00s
[2/3] Prepare transaction 100% | 2.0 B/s | 1.0 B | 00m00s
[3/3] Installing bvi-0:1.5.0-1.fc43.x86_64 100% | 243.9 KiB/s | 160.0 KiB | 00m01s
Complete!
[root@fedora log]# dnf5 history list
ID Command line Date and time Action(s) Altered
29 dnf install bvi 2026-02-06 04:50:43 1
28 dnf remove kernel-core-6.18.3-200.fc43.x86_64 kernel-modules-6.18.3-200.fc43.x86_64 kernel-modules-extra-6.18.3-200.fc43.x86_64 2026-01-14 05:07:22 4
27 dnf update 2026-01-14 05:01:58 70
26 dnf remove kernel-core-6.17.9-300.fc43.x86_64 kernel-modules-6.17.9-300.fc43.x86_64 kernel-modules-extra-6.17.9-300.fc43.x86_64 2026-01-14 05:01:28 4
25 dnf update 2026-01-09 19:19:48 42
24 dnf update 2026-01-08 19:57:05 618
23 dnf5 remove kernel-core-6.11.10-300.fc41.x86_64 kernel-modules-6.11.10-300.fc41.x86_64 kernel-modules-core-6.11.10-300.fc41.x86_64 2026-01-08 19:53:42 3
22 dnf remove kernel-6.11.10-300.fc41 2025-12-26 02:06:47 1
21 dnf install speedtest-cli 2025-12-19 23:06:12 1
20 yum install chromedriver 2025-12-08 00:20:24 3
19 dnf system-upgrade download –releasever=43 2025-12-06 18:40:14 3792
18 dnf upgrade –refresh 2025-12-06 08:22:19 1422
17 dnf install -y cloud-utils-growpart 2025-12-06 08:16:00 1
16 dnf install xmlsec1 xmlsec1-openssl 2025-10-27 20:44:39 1
15 yum install xmlsec1 2025-10-27 20:42:29 1
14 dnf install mod_md 2025-07-03 19:22:36 1
13 yum install npm 2025-06-23 20:05:13 5
12 dnf update 2025-02-23 19:56:36 482
11 dnf update 2025-01-31 16:01:33 626
10 dnf5 install dnf5-plugin-automatic 2025-01-31 15:59:29 9
9 yum install xxd 2025-01-15 21:38:38 1
8 dnf install mosquitto 2025-01-04 23:40:52 3
7 dnf update 2025-01-03 04:51:41 14
6 dnf update 2025-01-01 00:19:22 358
5 dnf update 2024-12-06 04:30:32 18
4 dnf update 2024-12-06 04:14:41 404
3 dnf update 2024-11-22 17:21:43 65
2 dnf update 2024-11-18 18:33:18 116
1 dnf update 2024-11-14 18:58:09 54

[root@fedora log]# dnf5 history undo 29
Updating and loading repositories:
Repositories loaded.
Package Arch Version Repository Size
Removing:
bvi x86_64 1.5.0-1.fc43 fedora 157.1 KiB

Transaction Summary:
Removing: 1 package

After this operation, 157 KiB will be freed (install 0 B, remove 157 KiB).
Is this ok [y/N]: y
Running transaction
[1/2] Prepare transaction 100% | 4.0 B/s | 1.0 B | 00m00s
[2/2] Removing bvi-0:1.5.0-1.fc43.x86_64 100% | 35.0 B/s | 20.0 B | 00m01s
Complete!

Did you know … Powershell can create Visio diagrams!?!

I had to create a number of Visio diagrams for a new project. Since Blender has a Python API, I wondered if I could do something similar with Visio. There does appear to be an VSDX library for Python, I also found that Powershell can just control the Visio instance on my laptop.

This is a demo creating a diagram for a simple web server with a database back end. You can, however, use any stencils and make more complicated diagrams. The lines aren’t great — part of my Visio diagramming process is moving things around to optimize placement to avoid overlapping and confusing lines. The programmatic approach doesn’t do that, but it gets everything in the diagram. You can then move them as needed.

# Sample Visio diagram: Firewall -> Load Balancer -> Web Servers -> Database
# Auto-discovers stencils
# Works on Windows PowerShell 5.x

$ErrorActionPreference = "Stop"

# Output
$docName = "WebApp-LB-Firewall-DB.vsdx"
$outPath = Join-Path $HOME "Documents\$docName"

# Start Visio
$visio = New-Object -ComObject Visio.Application
$visio.Visible = $true

# New document/page
$doc = $visio.Documents.Add("")
$page = $visio.ActivePage
$page.Name = "Architecture"
$page.PageSheet.CellsU("PageWidth").ResultIU  = 22.0
$page.PageSheet.CellsU("PageHeight").ResultIU = 14.0

# -------------------------------
# Stencil discovery and loading
# -------------------------------

$searchRoots = @(
    "$env:PROGRAMFILES\Microsoft Office\root\Office16\Visio Content",
    "$env:PROGRAMFILES\Microsoft Office\root\Office16\Visio Content\1033",
    "$env:ProgramFiles(x86)\Microsoft Office\root\Office16\Visio Content",
    "$env:ProgramFiles(x86)\Microsoft Office\root\Office16\Visio Content\1033",
    "$env:PROGRAMFILES\Microsoft Office\root\Office15\Visio Content",
    "$env:ProgramFiles(x86)\Microsoft Office\root\Office15\Visio Content",
    "$env:PROGRAMFILES\Microsoft",
    "$env:ProgramFiles(x86)\Microsoft",
    "$env:PROGRAMFILES",
    "$env:ProgramFiles(x86)"
) | Where-Object { Test-Path $_ }

# Keywords to select useful stencils (filename match, case-insensitive)
$stencilKeywords = @("network","server","compute","computer","azure","cloud","firewall","security","database","sql","load","balancer","web","iis")

function Find-StencilFiles {
    param([string[]]$roots, [string[]]$keywords)
    $results = @()
    foreach ($root in $roots) {
        try {
            Get-ChildItem -Path $root -Filter *.vssx -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
                $fname = $_.Name.ToLower()
                foreach ($kw in $keywords) {
                    if ($fname -match $kw) { $results += $_.FullName; break }
                }
            }
        } catch { }
    }
    $results | Select-Object -Unique
}

function Load-Stencils {
    param([string[]]$files)
    $loaded = @()
    foreach ($file in $files) {
        try {
            Write-Host "Loading stencil: $file"
            $loaded += $visio.Documents.OpenEx($file, 64) # read-only
        } catch {
            Write-Warning "Could not load stencil: $file"
        }
    }
    foreach ($docX in $visio.Documents) {
        if ($docX.FullName -ne $doc.FullName) { $loaded += $docX }
    }
    $loaded | Sort-Object FullName -Unique
}

$files = Find-StencilFiles -roots $searchRoots -keywords $stencilKeywords
$stencils = Load-Stencils -files $files

if (!$stencils -or $stencils.Count -eq 0) {
    Write-Warning "No stencil files loaded automatically. Fallback rectangles will be used."
} else {
    Write-Host "`nLoaded stencils:" -ForegroundColor Cyan
    foreach ($s in $stencils) { Write-Host " - $($s.FullName)" }
}

# -------------------------------
# Master selection helpers
# -------------------------------

function List-Masters {
    foreach ($st in $stencils) {
        Write-Host ("Stencil/Doc: {0}" -f $st.Name) -ForegroundColor Cyan
        foreach ($m in $st.Masters) {
            Write-Host ("  - {0} (NameU: {1})" -f $m.Name, $m.NameU)
        }
    }
}

function Get-MasterByPattern([string[]]$patterns) {
    foreach ($st in $stencils) {
        foreach ($m in $st.Masters) {
            foreach ($p in $patterns) {
                if ($m.NameU -match $p -or $m.Name -match $p) {
                    Write-Host ("Selected master '{0}' from '{1}' for pattern '{2}'" -f $m.Name, $st.Name, $p) -ForegroundColor Green
                    return $m
                }
            }
        }
    }
    return $null
}

# Drop master centered at x,y; keep default size; label it
function Add-Device([double]$x,[double]$y,[string]$label,[string[]]$patterns,[double]$fontSize=10) {
    $m = Get-MasterByPattern $patterns
    if ($null -eq $m) {
        Write-Warning ("No master matched patterns: {0}. Using fallback rectangle." -f ($patterns -join ", "))
        $w = 2.0; $h = 1.2
        $shape = $page.DrawRectangle($x - ($w/2), $y - ($h/2), $x + ($w/2), $y + ($h/2))
    } else {
        $shape = $page.Drop($m, $x, $y)
    }
    $shape.Text = $label
    $shape.CellsU("Char.Size").FormulaU = "$fontSize pt"
    return $shape
}

# Simple transparent containers (thin gray outline; sent behind shapes)
function Add-Container([double]$x,[double]$y,[double]$w,[double]$h,[string]$text) {
    $shape = $page.DrawRectangle($x, $y, $x + $w, $y + $h)
    $shape.CellsU("LineColor").FormulaU = "RGB(180,180,180)"
    $shape.CellsU("LineWeight").FormulaU = "1 pt"
    $shape.CellsU("FillForegnd").FormulaU = "RGB(255,255,255)"
    $shape.CellsU("FillForegndTrans").ResultIU = 1.0
    $shape.Text = $text
    $shape.CellsU("Char.Size").FormulaU = "12 pt"
    try { $shape.SendToBack() } catch {}
    return $shape
}

# Connector
function Connect($fromShape,$toShape,[string]$text="") {
    $conn = $page.Drop($visio.Application.ConnectorToolDataObject, 0, 0)
    $conn.CellsU("LineColor").FormulaU = "RGB(60,60,60)"
    $conn.CellsU("LineWeight").FormulaU = "0.75 pt"
    $fromShape.AutoConnect($toShape, 0, $conn)
    if ($text) { $conn.Text = $text }
    return $conn
}

# -------------------------------
# Diagram content
# -------------------------------

# Title
$title = $page.DrawRectangle(1.0, 13.4, 21.0, 13.9)
$title.Text = "Web App Architecture: Firewall -> Load Balancer -> Web Servers -> Database"
$title.CellsU("Char.Size").FormulaU = "14 pt"

# Patterns for official icons (broad to match common stencils)
$patFirewall    = @("Firewall|Security|Shield|Azure.*Firewall")
$patLoadBalancer= @("Load.*Balancer|Application.*Gateway|LB|Azure.*Load.*Balancer")
$patWebServer   = @("Web.*Server|IIS|Server(?! Rack)|Computer|Windows.*Server")
$patDatabase    = @("Database|SQL|Azure.*SQL|DB|Cylinder")

# Containers (optional zones)
$dmz     = Add-Container 1.0 10.8 20.0 2.0 "DMZ (Edge/Ingress)"
$webtier = Add-Container 4.0 6.8 14.0 3.2 "Web Tier"
$dbtier  = Add-Container 8.0 3.5 10.0 2.8 "Database Tier"
$clients = Add-Container 1.0 1.0 6.0 2.2 "Clients"

# Devices (kept at native size; spaced widely)
# Edge/Ingress
$fw      = Add-Device 3.0 11.8 "Firewall" $patFirewall 10
$lb      = Add-Device 8.0 11.8 "Load Balancer" $patLoadBalancer 10

# Web servers (pair)
$web1    = Add-Device 9.5 8.0 "Web Server 1\nIIS" $patWebServer 10
$web2    = Add-Device 13.5 8.0 "Web Server 2\nIIS" $patWebServer 10

# Database
$db      = Add-Device 13.0 4.6 "Database\nSQL" $patDatabase 10

# Clients
$client1 = Add-Device 2.0 1.8 "Client\nPC" @("Desktop|PC|Computer|Laptop") 10
$client2 = Add-Device 5.0 1.8 "Client\nServer" @("Server(?! Rack)|Windows.*Server|Computer") 10

# Connectors (flow: clients -> firewall -> LB -> web servers -> database)
Connect $client1 $fw "HTTPS"
Connect $client2 $fw "HTTPS"
Connect $fw $lb "Allow: 443"
Connect $lb $web1 "HTTP/HTTPS"
Connect $lb $web2 "HTTP/HTTPS"
Connect $web1 $db "SQL (1433/Encrypted)"
Connect $web2 $db "SQL (1433/Encrypted)"

# Save
$doc.SaveAs($outPath)
Write-Host "Saved Visio to: $outPath"

PingFederate SAML Issuance Criterion – Or Operator

We use the “Issuance Criteria” to restrict access to applications that do just-in-time provisioning without processing group memberships to restrict who gets provisioned. The GUI, however, has an implicit AND operator … which means you cannot be allowed to log on if you are a member of X or Y

To use an OR operator, you need to use an OGNL expression. Show the advanced criteria

Use an OGNL expression — this is an example allowing members of two groups

<urn:TokenAuthorizationIssuanceCriterion AttrName="" AttrSourceType="Expression" ComparisonValue="" ErrorResult="No Role Assigned">
     <urn:ExprText>#group = #this.get("memberOf"), #group.toString().contains("Scribe - Creator Access") || #group.toString().contains("Scribe - Viewer Access") ? @java.lang.Boolean@TRUE : @java.lang.Boolean@FALSE</urn:ExprText>
</urn:TokenAuthorizationIssuanceCriterion>

Expanding a qcow2-backed system disk (host + guest)

Expanding a qcow2-backed system disk (host + guest) — guest volume is lvm and xfs file system

HOST (resize qcow2)

  1. Optional backup:
    cp –reflink=auto /vms/fedora02.qcow2 /vms/fedora02.qcow2.bak
  2. Offline resize (VM stopped):
    qemu-img resize /vms/fedora02.qcow2 +5G
    # Start the VM after resizing.

GUEST (grow partition, PV, LV, filesystem)

  1. Confirm the disk shows the larger size:
    lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
    #If needed:
    #partprobe /dev/sda
  2. Grow the LVM partition (sda2) to the end of the disk:
    dnf install -y cloud-utils-growpart
    growpart /dev/sda 2
    partprobe /dev/sda
  3. Resize the LVM PV and extend the root LV:
    pvresize /dev/sda2
    lvextend -l +100%FREE /dev/fedora/root
  4. Grow the filesystem:
    xfs_growfs /
  5. Verify:
    lsblk -o NAME,SIZE,TYPE,MOUNTPOINT
    df -h /

Exchange SMTP – Sender Reputation DB

Our Exchange server was refusing mail

451 4.7.0 Temporary server error. Please try again later. PRX5

Attempts to send mail would connect, send data, and then hang for a few seconds before returning the tempfail error.

Looks like there’s “sender reputation” data stored at .\Exchange Server\V15\TransportRoles\data\SenderReputation that is used. Since I’m not actually doing filtering on the Exchange server, stopping the transport services, moving the files out of the folder, and then re-starting the services rebuilt the data and allowed mail to send again.

Linux: Getting Drive Serial Number

[lisa@FVD01 /mnt/lisa/]# smartctl -i /dev/sdc
smartctl 7.5 2025-04-30 r5714 [x86_64-linux-6.15.7-200.fc42.x86_64] (local build)
Copyright (C) 2002-25, Bruce Allen, Christian Franke, www.smartmontools.org

=== START OF INFORMATION SECTION ===
Model Family: Western Digital Red (CMR)
Device Model: WDC WD40EFRX-68N32N0
Serial Number: WD-WCC7K4HY5TKD
LU WWN Device Id: 5 0014ee 2b9a3d0c5
Firmware Version: 82.00A82
User Capacity: 4,000,787,030,016 bytes [4.00 TB]
Sector Sizes: 512 bytes logical, 4096 bytes physical
Rotation Rate: 5400 rpm
Form Factor: 3.5 inches
Device is: In smartctl database 7.5/5706
ATA Version is: ACS-3 T13/2161-D revision 5
SATA Version is: SATA 3.1, 6.0 Gb/s (current: 6.0 Gb/s)
Local Time is: Tue Dec 2 17:24:27 2025 EST
SMART support is: Available – device has SMART capability.
SMART support is: Enabled

2025-12-02 17:24:27 [root@FPP01 /mnt/MythAndZoneminder/]# smartctl -i /dev/sda
smartctl 7.5 2025-04-30 r5714 [x86_64-linux-6.15.7-200.fc42.x86_64] (local build)
Copyright (C) 2002-25, Bruce Allen, Christian Franke, www.smartmontools.org

=== START OF INFORMATION SECTION ===
Model Family: Western Digital Red (CMR)
Device Model: WDC WD40EFRX-68N32N0
Serial Number: WD-WCC7K7JZSZ0E
LU WWN Device Id: 5 0014ee 264576d5e
Firmware Version: 82.00A82
User Capacity: 4,000,787,030,016 bytes [4.00 TB]
Sector Sizes: 512 bytes logical, 4096 bytes physical
Rotation Rate: 5400 rpm
Form Factor: 3.5 inches
Device is: In smartctl database 7.5/5706
ATA Version is: ACS-3 T13/2161-D revision 5
SATA Version is: SATA 3.1, 6.0 Gb/s (current: 6.0 Gb/s)
Local Time is: Tue Dec 2 17:24:38 2025 EST
SMART support is: Available – device has SMART capability.
SMART support is: Enabled

 

Getting Cert Info From Host

An OpenSSL command to retrieve the cert chain from a host and parse out the CN and expiry info

[lisa@linux05 ~]# openssl s_client -connect 10.5.5.75:443 -servername lisa.rushworth.us -showcerts </dev/null 2>/dev/null | sed -n ‘/BEGIN CERTIFICATE/,/END CERTIFICATE/p’ | openssl x509 -noout -subject -startdate -enddate -nameopt RFC2253
subject=CN=lisa.rushworth.us
notBefore=Sep 2 03:28:34 2025 GMT
notAfter=Dec 1 03:28:33 2025 GMT

PingFederate – OGNL Customization of AuthnContext

After a recent merger, we have added federated authentication in our PingFederate environment that allows the incoming company to continue to use their Entra (ADFS) logon process to authenticate through PingFederate. All of the IDs exist in our directory, and contract attributes are populated based on the local account. But the authentication is handled by their existing system. It’s really cool, and works for 99.9% of the applications. One, however, was not happy with the resultant attribute contract. It worked fine for me, logging in directly with PingFederate. Anyone who authenticated through Entra, however, got a very specific error:

AuthenticatingAuthority array contains a value which is not a wellformed absolute uri

And, yes, I concur – there is absolutely an element in the AuthenticatingAuthority array that is not a well-formed absolute URI

Luckily, there appears to be a solution. On the ACS URL tab, select “Show Advanced Customizations”

A screenshot of a computer

AI-generated content may be incorrect.

Use the drop-down to select the message type of “AssertionType” and the expression provided at https://support.pingidentity.com/s/article/OGNL-Examples-Message-Customization#rm-authauthority to remove authenticating authority values when multiple are present (which also works when only one is present)

A close-up of a computer screen

AI-generated content may be incorrect.

Now I no longer have authenticating authorities but the AuthnContextClassRef is “urn:oasis:names:tc:SAML:2.0:ac:classes:Telephony” … so, in the assertion creation, we need to add SAML_AUTHN_CTX to the attribute contract

A screenshot of a computer

AI-generated content may be incorrect.

In the attribute contract fulfillment, map this to a static TEXT string – I am using “urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified” which is used as the default in PingFederate

Final Answer! I have an AuthnContext that does not contain any invalid URI strings and a AuthnContextClassRef that is expected.