Resolve Citrix Secure Ticket Authority (STA)

Problem this snippet solves:

Optimal Gateway Routing (OGR) for Citrix Storefront is a design whereby a Citrix web client is directed to an ICA Proxy Gateway (ICA-GW) anywhere in the world that is closest to the app/desktop hosting environment (XenApp and XenDesktop servers) which may not be on the same Citrix StoreFront ADC (NetScaler) Gateway (SF-GW) which has authenticated the user. This is in contrast to being directed to a single ADC Gateway device that hosts SF-GW and ICA-GW. In a Citrix ADC deployment, the ICA-GW (not the SF-GW) is responsible for validating/resolving the STA ticket provided by a Secure Ticket Authority (STA) server. Since the ICA-GW is responsible for this validation, it allows OGR to function and send ICA traffic to a different ICA-GW than what was used to download the ICA file from StoreFront.

This iRule will be used to resolve/validate the STA ticket which has already been extracted from the client's ICA proxy request.

How to use this snippet:

See DC Article "Solution for Citrix Optimal Gateway Routing" for implementation.

Code :

##
## Resolver iRule
## To enable detailed iRule debugging, set the static::debug_sta_rslv variable in the RULE_INIT event to 1
## Updated July 14, 2021 by b.otlin@f5.com
##

when RULE_INIT {
    set static::debug_sta_rslv 0
}

when HTTP_REQUEST {
    if { [HTTP::has_responded] } {
        if { $static::debug_sta_rslv } { log local0. "HTTP::has_responded" }
        return
        } else {
           if { $static::debug_sta_rslv } { log local0. "HTTP::has NOT responded" }
        }

    set sta_request [expr {[HTTP::path] == "/f5apm/ctx-sta"}]
    if { $static::debug_sta_rslv } { log local0. "req is [HTTP::uri]" }
    if { $static::debug_sta_rslv } { log local0. "sta req is $sta_request" }

    if {!$sta_request} {
        # exit event if the request was not a STA request
       if { $static::debug_sta_rslv } { log local0. "sta_request does NOT exist, exit event" }
        return
        } else {
        if { $static::debug_sta_rslv } { log local0. "sta_request does exist, continue" }
        }

    sharedvar internal_ica_file_request
    if { [info exists internal_ica_file_request] } {
        if { $static::debug_sta_rslv } { log local0. "internal_ica_file_request exists...return 200 ok to SF APM with mod ICA info" }
        HTTP::respond 200 content \
            "\[ApplicationServers\]\nApp=\n\[App\]\nAddress=;[HTTP::query]" \
            "Content-Type" "application/x-ica"
        if { $static::debug_sta_rslv } { log local0. "unset internal_ica_file_request and exit event" }
        unset internal_ica_file_request
        return
    }
    else {
        if { $static::debug_sta_rslv } { log local0. "internal_ica_file_request does NOT exist, continue" }
    }

    if { [info exists sta_request_sid] } {
        if { $static::debug_sta_rslv } { log local0. "sta_request_sid is $sta_request_sid" }
        if { $static::debug_sta_rslv } { log local0. "insert MRHSession cookie = $sta_request_sid and X-F5-Client header" }
        HTTP::header insert \
            "Cookie" "MRHSession=$sta_request_sid" \
            "X-F5-Client" "citrix-launch"
        VDI::enable
        if { $static::debug_sta_rslv } { log local0. "enable VDI" }

        set internal_ica_file_request 1
        if { $static::debug_sta_rslv } { log local0. "internal_ica_file_request set to 1" }
        SSL::disable serverside
        if { $static::debug_sta_rslv } { log local0. "disable SSL serverside" }
        virtual [virtual name]
        if { $static::debug_sta_rslv } { log local0. "go back to same VS" }

    } else {
        if { $static::debug_sta_rslv } { log local0. "sta_request_sid does NOT exist" }
        HTTP::header insert "clientless-mode" "1"
        if { $static::debug_sta_rslv } { log local0. "insert clientless-mode header" }
    }
}

when HTTP_RESPONSE {
    if { [HTTP::payload] contains "Address=;" } {
        if { $static::debug_sta_rslv } { log local0. "Response payload contains Address=; which denotes an ICA File" }

        set sta_address [HTTP::payload]
        regexp -line {^(?:[^;]*;){2}([^;]*)} $sta_address -> sta1
        regexp -line {^(?:[^;]*;){4}([^;]*)} $sta_address -> sta2
        set STA1 [class match -value -- $sta1 equals sta_dg]
        if { [info exists sta2] } {
            set STA2 [class match -value -- $sta2 equals sta_dg]
            ACCESS::session data set session.citrix.sta_servers "$STA1;$STA2"
             if { $static::debug_sta_rslv } { log local0. "STA from SF is $sta1 and $sta2 resolved to FQDN: $STA1 and $STA2 and adding to access session table" }
            }
        else {
            if { $static::debug_sta_rslv } { log local0. "STA from SF is $sta1 resolved to FQDN: $STA1 and adding to access session table" }
            ACCESS::session data set session.citrix.sta_servers "$STA1"
        }
    } else {
        if { $static::debug_sta_rslv } { log local0. "not an ICA" }
    }
}

when HTTP_RESPONSE_RELEASE {
    if { [HTTP::has_responded] } {
        if { $static::debug_sta_rslv } { log local0. "HTTP has_responded...exit event" }
        return
    }

    if {!$sta_request} {
        if { $static::debug_sta_rslv } { log local0. "sta_request does NOT exist...exit event" }
        return
        }

    if { [HTTP::status] == 200 } {
        if { $static::debug_sta_rslv } { log local0. "HTTP status is 200, remove Set-Cookie header" }
        # There is no need to expose this SID
        HTTP::header remove Set-Cookie
    } else {
        if { $static::debug_sta_rslv } { log local0. " HTTP status is NOT 200, remove access session" }
        # Remove session on failed STA resolution
        ACCESS::session remove
    }
}

when ACCESS_SESSION_STARTED {
    if { !$sta_request } {
        if { $static::debug_sta_rslv } { log local0. "sta_request does NOT exist...exit event" }
        return
        } else {
            if { $static::debug_sta_rslv } { log local0. "sta_request exists...continue" }
        }

    if { $static::debug_sta_rslv } { log local0. "sta_request exists so set 'session.external_sta_ticket' to 1" }
    ACCESS::session data set "session.external_sta_ticket" "1"
}

when ACCESS_POLICY_COMPLETED {
    if { !$sta_request } {
        if { $static::debug_sta_rslv } { log local0. "sta_request does NOT exist...exit event" }
        return
    } else {
            if { $static::debug_sta_rslv } { log local0. "sta_request exists...continue" }
        }

    set sta_request_sid [ACCESS::session sid]
    if { $static::debug_sta_rslv } { log local0. "sta_request_sid is $sta_request_sid" }
}

Tested this on version:

15.1
Published Jul 15, 2021
Version 1.0

Was this article helpful?

No CommentsBe the first to comment