Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Radius Audit Log UI displays WLAN-AKM-Suite, WLAN-Group-Cipher, and WLAN-Pairwise-Cipher as float/double values and TLS cert dates are unprocessed #8358

Open
E-ThanG opened this issue Oct 24, 2024 · 2 comments · May be fixed by #8380

Comments

@E-ThanG
Copy link
Contributor

E-ThanG commented Oct 24, 2024

Describe the bug
The "Radius Audit Log" UI displays WLAN-AKM-Suite, WLAN-Group-Cipher, and WLAN-Pairwise-Cipher as float/double values. Also TLS cert valid from/to dates aren't processed. These issues exists in PF 14.0 and 13.2, I can't confirm if it exists in other versions.

To Reproduce
Steps to reproduce the behavior:

  1. Generate a RADIUS audit log entry for an EAP-TLS client authentication.
  2. View the WLAN-AKM-Suite, WLAN-Group-Cipher, WLAN-Pairwise-Cipher, TLS-Cert-Expiration, TLS-Cert-Valid-Since, TLS-Client-Cert-Expiration, and TLS-Client-Cert-Valid-Since fields

Expected behavior
The WLAN-AKM-Suite, WLAN-Group-Cipher, and WLAN-Pairwise-Cipher should not be displayed as float/double values. Either display raw hex or parse to display the decoded fields. The TLS-Cert-Expiration, TLS-Cert-Valid-Since, TLS-Client-Cert-Expiration, and TLS-Client-Cert-Valid-Since should display a date. There are other fields that are displayed as hex, such as EAP-Message, State, PacketFence-KeyBalanced and others. If those can easily be parsed, they should be. Hex is fine if the alternatives aren't simple or are resource prohibitive.

@E-ThanG
Copy link
Contributor Author

E-ThanG commented Nov 1, 2024

Part of the mystery is revealed. The Redis cache has base64 encoded JSON data. In that data the WLAN attributes are integers. But once that infomation is serialized and put in the SQL database the 7 digit integers have become floats. The TLS cert dates are quoted strings, these are unchanged in the SQL database. There is most likely implicit type conversion happening during JSON serialization or URL encoding. Perhaps if the integers were quoted strings they wouldn't be converted to floats.

Also, it the attributes were to be parsed, it'd be easiest to do so while the radius request and response are JSON. So this feature would most likely be implemented prior to inserting into redis.

There are only a few possible values for the AKM suite and cipher suites
https://community.hpe.com/t5/networking/a-closer-look-at-wi-fi-security-ie-information-elements/ba-p/7218976

A table based integer to string conversion method should be relatively straight forward to implement.

Redis cache:

"WLAN-Pairwise-Cipher": 1027076,
"WLAN-Group-Cipher": 1027076,
"WLAN-AKM-Suite": 1027075,
"TLS-Client-Cert-Valid-Since": "241025235210Z",

SQL

=0AWLAN-AKM-Suite =3D =221.027075e=2B06=22=2C=0AWLAN-Group-Cipher =3D =221.027076e=2B06=22=2C=0AWLAN-Pairwise-Cipher =3D =221.027076e=2B06=22
and 
TLS-Client-Cert-Valid-Since =3D =22241025235210Z=22=2C=0A

URL encoding removed:

WLAN-AKM-Suite="1.027075e+06",
WLAN-Group-Cipher="1.027076e+06",
WLAN-Pairwise-Cipher="1.027076e+06"
TLS-Client-Cert-Valid-Since="241025235210Z",

@E-ThanG
Copy link
Contributor Author

E-ThanG commented Nov 5, 2024

The root of the issue regarding integers being parsed as floats is because of how Golang's json.Unmarshal handles numbers. All numbers are interpreted as float64s. The conversion happens in pfcron $PF/go/cron/flush_radius_audit_log_job.go

I tinkered with a custom decoder that uses the UserNumber() method to interpret the values as json.Number. But decided against it. I had too many issues with that conversion and later function requirements.

Since I was in the file, I added logic to parse the attributes mentioned above and convert them to friendly strings. I left the numbers as float64 until actually parsed and convert from float to int64 when needed.

TLS-Cert-Expiration = "2039-04-10 17:00:00",
TLS-Cert-Valid-Since = "2019-04-10 16:51:00",
TLS-Client-Cert-Expiration = "2029-09-27 21:14:28",
TLS-Client-Cert-Valid-Since = "2024-09-27 21:04:00",
WLAN-AKM-Suite = "FT over 802.1X",
WLAN-Group-Cipher = "CCMP-128",
WLAN-Pairwise-Cipher = "CCMP-128"

It probably makes sense to leave these alone here and wait until they are retrieved from the database to be parsed. That way it's not something that happens at every RADIUS packet and only when the RADIUS log entry is displayed in the UI. At any rate, here is the Go code:

func (j *FlushRadiusAuditLogJob) argsFromEntry(entry []interface{}) []interface{} {
        args := make([]interface{}, RADIUS_AUDIT_LOG_COLUMN_COUNT)
        var request, reply, control map[string]interface{}
        request = entry[1].(map[string]interface{})
        reply = entry[2].(map[string]interface{})
        control = entry[3].(map[string]interface{})
        request = parseRequestArgs(request)          <--- Insert function call here, no other changes to existing code.
        args[2] = formatRequestValue(request["PacketFence-Computer-Name"], "N/A")
        args[0] = formatRequestValue(request["Calling-Station-Id"], "N/A")
        args[1] = formatRequestValue(request["Framed-IP-Address"], "N/A")
        args[3] = formatRequestValue(request["User-Name"], "N/A")
        args[4] = formatRequestValue(request["Stripped-User-Name"], "N/A")
        args[5] = formatRequestValue(request["Realm"], "N/A")
        args[6] = "Radius-Access-Request"
        <SNIP>

func parseRequestArgs(request map[string]interface{}) map[string]interface{} {
    if val, ok := request["WLAN-AKM-Suite"].(float64); ok {
        request["WLAN-AKM-Suite"] = mapAKMSuite(int(val))
    }
    if val, ok := request["WLAN-Group-Cipher"].(float64); ok {
        request["WLAN-Group-Cipher"] = mapCipherSuite(int(val))
    }
    if val, ok := request["WLAN-Pairwise-Cipher"].(float64); ok {
        request["WLAN-Pairwise-Cipher"] = mapCipherSuite(int(val))
    }
    if val, ok := request["TLS-Cert-Expiration"].(string); ok {
        request["TLS-Cert-Expiration"] = formatDate(val)
    }
    if val, ok := request["TLS-Cert-Valid-Since"].(string); ok {
        request["TLS-Cert-Valid-Since"] = formatDate(val)
    }
    if val, ok := request["TLS-Client-Cert-Expiration"].(string); ok {
        request["TLS-Client-Cert-Expiration"] = formatDate(val)
    }
    if val, ok := request["TLS-Client-Cert-Valid-Since"].(string); ok {
        request["TLS-Client-Cert-Valid-Since"] = formatDate(val)
    }
    return request
}

type AKMSuite int

const (
    AKMReserved AKMSuite = iota // 0 - Reserved
    IEEE8021X           // 1 - 802.1X
    PSK                 // 2 - PSK
    FT_8021X            // 3 - FT over 802.1X
    FT_PSK              // 4 - FT over PSK
    WPA_8021X           // 5 - WPA with 802.1X
    WPA_PSK             // 6 - WPA with PSK
    OWE                 // 7 - OWE
    OWE_Transition      // 8 - OWE Transition Mode
    SAE                 // 9 - Simultaneous Authentication of Equals
    FT_SAE              // 10 - FT over SAE
    FILS_SHA256         // 11 - FILS-SHA256
    FILS_SHA384         // 12 - FILS-SHA384
    FT_FILS_SHA256      // 13 - FT over FILS-SHA256
    FT_FILS_SHA384      // 14 - FT over FILS-SHA384
    OWE_transition_mode // 15 - OWE transition mode
)

type CipherSuite int

const (
    CipherReserved CipherSuite = iota // 0 - Reserved
    WEP40            // 1 - WEP-40
    TKIP             // 2 - TKIP
    CipherReserved3  // 3 - Reserved
    CCMP128          // 4 - CCMP-128
    WEP104           // 5 - WEP-104
    BIPCMAC128       // 6 - BIP-CMAC-128
    GCMP128          // 7 - GCMP-128
    GCMP256          // 8 - GCMP-256
    CCMP256          // 9 - CCMP-256
    BIPGMAC128       // 10 - BIP-GMAC-128
    BIPGMAC256       // 11 - BIP-GMAC-256
    SMS4             // 12 - SMS4
    CKIP128          // 13 - CKIP-128
    CKIP128_PMK      // 14 - CKIP-128 with PMK caching
    CipherReserved15 // 15 - Reserved
)

func(c CipherSuite) String() string {
    switch c {
        case WEP40:
            return "WEP-40"
        case TKIP:
            return "TKIP"
        case CCMP128:
            return "CCMP-128"
        case WEP104:
            return "WEP-104"
        case GCMP128:
            return "GCMP-128"
        case GCMP256:
            return "GCMP-256"
        case CCMP256:
            return "CCMP-256"
        case BIPCMAC128:
            return "BIP-CMAC-128"
        case BIPGMAC128:
            return "BIP-GMAC-128"
        case BIPGMAC256:
            return "BIP-GMAC-256"
        case SMS4:
            return "SMS4"
        case CKIP128:
            return "CKIP-128"
        case CKIP128_PMK:
            return "CKIP-128 with PMK caching"
        case CipherReserved3, CipherReserved15:
            return "Reserved"
        default:
            return fmt.Sprintf("Unknown cipher suite (Value: %d)", c)
    }
}

func(a AKMSuite) String() string {
    switch a {
        case IEEE8021X:
            return "802.1X"
        case PSK:
            return "PSK"
        case FT_8021X:
            return "FT over 802.1X"
        case FT_PSK:
            return "FT over PSK"
        case WPA_8021X:
            return "WPA with 802.1X"
        case WPA_PSK:
            return "WPA with PSK"
        case OWE:
            return "OWE"
        case OWE_Transition:
            return "OWE Transition Mode"
        case SAE:
            return "SAE"
        case FT_SAE:
            return "FT over SAE"
        case FILS_SHA256:
            return "FILS-SHA256"
        case FILS_SHA384:
            return "FILS-SHA384"
        case FT_FILS_SHA256:
            return "FT over FILS-SHA256"
        case FT_FILS_SHA384:
            return "FT over FILS-SHA384"
        case OWE_transition_mode:
            return "OWE transition mode"
        default:
            return fmt.Sprintf("Unknown or Reserved AKM suite (Value: %d)", a)
    }
}

func mapAKMSuite(akmSuiteInt int) string {
    akmSuiteSelector: = akmSuiteInt & 0x000000FF
    return AKMSuite(akmSuiteSelector).String()
}

func mapCipherSuite(cipherSuiteInt int) string {
    cipherSuiteSelector: = cipherSuiteInt & 0x000000FF
    return CipherSuite(cipherSuiteSelector).String()
}

func formatDate(dateStr string) string {
    const dateFormat2Digit = "060102150405Z"
    const dateFormat4Digit = "20060102150405Z"

    var t time.Time
    var err error
    t, err = time.Parse(dateFormat2Digit, dateStr)
    if err != nil {
        t, err = time.Parse(dateFormat4Digit, dateStr)
        if err != nil {
            return dateStr // Return the original string if parsing fails
        }
    }

    return t.Format("2006-01-02 15:04:05")
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant