Jump to content
Welcome to our new Citrix community!

Netscaler Policy assistance


Recommended Posts

Dear Community,

 

I am extremely new to the Netscaler ADC and need some assistance with writing a policy. I want to configure a policy on a VIP that inspects the HTTP URL for a certain regex or string that identifies the resource the request is trying to access. I want to restrict access to this server resource to only an approved list of public IP addresses. In the F5 world, I would write an iRule for this, but I have no experience with the Netscaler Polices and expressions. So for example, if someone tried to access the "images" directory of example.com (www.example.com/images) and I wanted to restrict access to the /images directory to only an approved list of IP addresses, how would I construct the policy to do that?

 

Any assistance you can provide would be greatly appreciated. Thanks!

Link to comment
Share on other sites

The Responder policy is a feature that you can give an expression (criteria) and then take an action like drop/reset/redirect to restrict access to a given URL.

https://docs.citrix.com/en-us/citrix-adc/13/appexpert/responder/configuring-responder-policy.html

 

The expression for the policy would be your conditions such as your url patterns. You can do string comparisons or more complicated regex-based expressions if needed.

Here's some quick example expressions to get you started:

For a sample URL like:

https://demo.company.com/dir/subdir/somepage.asp?a1=b1&a2=b2

Expressions                             would match:

http.req.url                  /dir/subdir/somepage.asp?a1=b1&a2=b2

http.req.url.path         /dir/subdir/somepage.asp

http.req.url.query       a1=b1&a2=b2

http.req.url.path_and_query     /dir/subdir/somepage.asp?a1=b1&a2=b2

And a few others:

http.req.url.path.get(1)         dir

http.req.url.path.get(3)         somepage.asp

http.req.url.path.get(4)         <undefined>  no 4th element in this particular path

http.req.url.query.value(0)   b1

http.req.url.query.value("a2")  b2

 

 

So some basic policy expressions to block access to certain url paths (or patterns):

http.req.url.path.set_text_mode(ignorecase).starts_with("/images")

http.req.url.path.set_text_mode(ignorecase).contains("/images")

 

For client IP addresses, you can use:

client.ip.src.eq(10.10.10.100)

or for a subnet

client.ip.src.in_subnet(192.168.10.0/24)

 

So now, if you wanted to block access to '/images/.*' for users not in a specific subnet:

http.req.url.path.set_text_mode(ignorecase).starts_with("/images") && !client.ip.src.in_subnet(192.168.10.0/24)

 

In a responder policy, if this is true, then the action would apply.

If you just want to block access to  a URL regardless of path from anyone  NOT in the approved subnet list:

!(client.ip.src.in_subnet(192.168.10.0/24) || (192.168.20.0/24) || (192.168.30.0/24))

------------

Responder Policies (which you can see more info on in admin guide) are based on a policy expression which identifies the trigger condition and the action, what you want to do.  And you can bind it to the lb vserver.

 

# redirects need a url path to redirect to can be absolute or relative...

add responder action rs_act_blockurls_badips1 redirect "/block.htm"
add responder policy rs_pol_blockurls_badips1 'http.req.url.path.set_text_mode(ignorecase).starts_with("/images") && !(client.ip.src.in_subnet(192.168.10.0/24) || client.ip.src.in_subnet(192.168.20.0/24))' rs_act_blockurls_badips1

bind lb vserver lb_vsrv_demos -policyName rs_pol_blockurls_badips1 -priority 100

 

# to DROP, the action is predefined and available when you create the policy

add responder policy rs_pol_blockurls_badips2 'http.req.url.path.set_text_mode(ignorecase).starts_with("/images") && !(client.ip.src.in_subnet(192.168.10.0/24) || client.ip.src.in_subnet(192.168.20.0/24))' BLOCK

bind lb vserver lb_vsrv_demo -policyName rs_pol_blockurls_badips2 -priority 200

 

-------

Final thoughts for more complex scenarios or long lists of IP addresses, http callouts and other features can be used with responder as well.

 

 

  • Like 1
Link to comment
Share on other sites

Rhonda,

 

Thank you for such a detailed reply. This is very helpful information. Is there a way to create a list of IP addresses to compare against vs using a subnet or client IP? The reason I ask is because I want to compare the source IP of the request against a pretty long list of public IP addresses that vary. In the F5 (I hate to keep bringing up the F5 but its the only LB im familiar with) you can create a "data group" which the iRule logic can then match against. Does the Netscaler have a similar feature?

 

Thanks. 

Link to comment
Share on other sites

Sorry, I was still teaching class and couldn't respond after the first draft.

 

So, for a large list of IPs instead of subnets, there are a couple of ways to do this on the ADC. (Just takes familiarity with the policy expression engine; once you get used to it you might like it more than irules :)  but it is a different approach than what you are used to.)  

 

If the list isn't obnoxiously long, just an OR clause of IPs is possible, but not great:

client.ip.src(192.168.10.10) || client.ip.src(192.168.10.11) || ....

 

So, next an expression can be based on a set of values using an advanced policy object called a pattern set for strings or a data set for numerical values.

Pattern sets and data sets are just indexed tables of values.  They are configured in GUI under AppExpert (or via cli).

So for a list of urls you could do something like this, depending on the type of expression you wanted to right:

add policy patset ps_pathlist 

bind policy patset ps_pathlist "/owa"

bind policy patset ps_pathlist "/images"

...

When evaluating a patternset, instead of writing a url comparison like:  (NOTE: comparison operators like eq(), contains(), before_str(), after_str() and others are case_sensitive unless made note case sensitive using the ignorecase mode operator which I'll demonstrate in the patternset examples...)

[1a] http.req.url.path.contains("/owa") || http.req.url.path.contains("/images")

[1b] http.req.url.path.starts_with("/owa") || http.req.url.path.startswith("/owa")

With patternset, you will use the <comparison operator>_any variant and will result in an OR clause of contains_any(), eq_any(), etc across the values of the patternset (for strings)

[2a] http.req.url.path.set_text_mode(ignorecase).contains_any("ps_pathlist")

[2b] http.req.url.path.set_text_mode(ignorecase).startswith_any("ps_pathlist")

 

Datasets are then similar but work for numerical data of various types like IPv4 addresses:

add policy dataset ds_ipwhitelist ipv4
bind policy dataset ds_ipwhitelist 192.168.10.10
bind policy dataset ds_ipwhitelist 192.168.10.11
bind policy dataset ds_ipwhitelist 192.168.20.101

 

The problem is the client.ip.src object as an IP data type can do an .eq() and a in_subnet(), but it doesn't have an equals any option like strings do. So its counterintuitive but we will typecast it to a string, to process a data-based dataset...

CLIENT.IP.SRC.TYPECAST_TEXT_T.EQUALS_ANY("ds_ipwhitelist")

 

So using both the paths list patternset (as the paths to protect) and block for any IP NOT in whitelist, your responder expression could be:

http.req.url.path.set_text_mode(ignorecase).startswith_any("ps_pathlist") && !CLIENT.IP.SRC.TYPECAST_TEXT_T.EQUALS_ANY("ds_ipwhitelist")

 

From CLI, compound expressions are wrapped in double quotes (") and internal policy quotes must be escaped (\"). OR you can wrap the expression in single quotes (') and then leave the internal double quotes as is. If you insert the expression in the GUI, the external/surround quotes are not needed and just regular double quotes in the parentheses.

 

[1a] - double quotes with escape charaters

add responder policy rs_pol_drop_urls_fromnonwhitelistips "http.req.url.path.set_text_mode(ignorecase).startswith_any(\"ps_pathlist\") && !CLIENT.IP.SRC.TYPECAST_TEXT_T.EQUALS_ANY(\"ds_ipwhitelist\")" DROP

[1b] - use single quotes instead

add responder policy rs_pol_drop_urls_fromnonwhitelistips 'http.req.url.path.set_text_mode(ignorecase).startswith_any("ps_pathlist") && !CLIENT.IP.SRC.TYPECAST_TEXT_T.EQUALS_ANY("ds_ipwhitelist")' DROP

 

--------

Now, for truly complex scenarios you can use http callouts that allow the policy to call out to web page on a remote entity that an outside utility can maintain the url list or the whitelist/blacklist for.    And a ip blacklist example is here:  https://docs.citrix.com/en-us/citrix-adc/13/appexpert/http-callout/use-case-filtering-clients-ip-blacklist.html

 

---

Final thought: when you work with policies use the GUI because there are a lot of syntax help baked in so you can see a lot of your options. But this should help you get started.

 

See if this helps you get started (I forgot to post this; sorry)

 

 

  • Like 1
Link to comment
Share on other sites

Rhonda,

 

Once again, thank you for your very detailed response. I think I get it. So to begin we need to create a patternset that defines the directory or resource were trying to protect. In the example I gave www.example.com/images, so the patternset might be http.req.url.path.contains("/images") correct?

 

Secondly I need to create a dataset that houses all the external IP addresses. This is done via the following correct?

add policy dataset ds_ipwhitelist ipv4
bind policy dataset ds_ipwhitelist 1.1.1.1
bind policy dataset ds_ipwhitelist 2.2.2.2
bind policy dataset ds_ipwhitelist 3.3.3.3

 

And thirdly I need to tie it all together by creating a responder policy?

 

add responder policy rspol_mypolicy 'http.req.url.path.set_text_mode(ignorecase).startswith_any("/images") && !CLIENT.IP.SRC.TYPECAST_TEXT_T.EQUALS_ANY("ds_ipwhitelist")' DROP

 

A few more questions:

 

-Will this policy allow all other traffic that is NOT destined for the protected path to pass? Or do I need to enter more statements that specifically allow this? 

-I am noticing the exclamation point argument before the CLIENT.IP.SRC.TYPECAST, It seems like this is used the same way the F5 uses it, to denote the "NOT" logic? As in "if the IP is NOT in the ds_ipwhitelist dataset, then take this action?

-Can all of this be created via the GUI or are there some pieces that have to be done via CLI?

 

Thank you. 

 

 

Link to comment
Share on other sites

Your mostly good, but just to clarify this part:  the pattern set will NOT be the policy expression:

But the things you want a policy expression to look for (its just strings):

So if you want the expression to be: http.req.url.path.contains("<stuf>")

Then your patternset would be:

 

add policy patset ps_pathlist 

bind policy patset ps_pathlist "/owa"

bind policy patset ps_pathlist "/images"

 

Then your expression would compare against the patternset (whether 2 or 20 objects) in one expression:

http.req.url.path.contains_any("ps_pathlist")

 

Contains, Contains_any(), eq(), equals_any() are all case-sensitive by default, so decide if you need the set_text_mode(ignorecase) as well.

 

The rest of your first, second, thirds above were correct :)

 

---

6 hours ago, Chris Craddock said:

-Will this policy allow all other traffic that is NOT destined for the protected path to pass? Or do I need to enter more statements that specifically allow this? 

-I am noticing the exclamation point argument before the CLIENT.IP.SRC.TYPECAST, It seems like this is used the same way the F5 uses it, to denote the "NOT" logic? As in "if the IP is NOT in the ds_ipwhitelist dataset, then take this action?

-Can all of this be created via the GUI or are there some pieces that have to be done via CLI?

 

Truth tables can help you figure this out.

[1] Will this policy allow all other traffic, not destined for protected paths to pass?

Depending on how the expression is constructed. Also remember for responder policy, the end result is for expression == true, do action (to redirect/block stuff). So anytime the compound expression is false, the responder does not apply and the traffic will NOT be impacted (and will therefore be allowed).

So the rule I gave you:   'http.req.url.path.set_text_mode(ignorecase).startswith_any("ps_urllist") && !CLIENT.IP.SRC.TYPECAST_TEXT_T.EQUALS_ANY("ds_ipwhitelist")'

Basically, will only result in TRUE  for phrase (A && !B). When it is TRUE responder will block traffic. When its FALSE it will do nothing.

This rule will only block traffic going to the paths in the patternset and when the IP is not in the allowed whitelist.

Any request that is not in the "urllist" to protect , will result in A being FALSE so it doesn't matter which IP it came from, responder will not apply.

Any request is is too the urllist to protect, now will only be dropped with !B is true (meaning you are not in the allowed ip list)

 

If you want to prevent access to things not in the allowed URL list, we would use a different expression here.

 

YES:  ! means "not" and is a negation 

 

All of these expression can be created in the GUI. You just won't need the quotes around the expressions you see in the cmd line, but the contents of string operators would need them.  And in fact the GUI has lots of policy build/syntax tools to help you explore what we're building and you can learn your syntax in the gui.

 

So this would be entered in the policy expression field:

http.req.url.path.contains("some string")

You don't need the quotes around the "<expr>" like we had in the command line, but strings inside contains("<string>"), still need those quotes inside the parentheses.

 

  • Like 1
Link to comment
Share on other sites

Rhonda,

 

Thank you! When trying to add ipv4 addresses in the datasets in the GUI I am noticing it wont allow me to add subnets. I have several subnets that I want to add to the whitelist but am not sure how to do this. How do I add subnets to the dataset "ds_ipwhitelist"?

 

Thanks. 

Link to comment
Share on other sites

datasets won't do subnets.

Your either back to the in_subnet(x.x.x.x/yy) operator and lots of OR clauses or at that point I think you need to look at a callout instead to make it easier to maintain the list.

I misread one of your earlier messages as saying you were doing ips instead of subnets.

 

But I'll see if there isn't another way to do it, if someone else doesn't chime in first.  How many are you looking at: hundreds or <50?

Link to comment
Share on other sites

1 hour ago, Chris Craddock said:

Rhonda,

 

I need to whitelist 6 subnets and about 120 single /32 IP addresses. 

 

Thank you. 

 

Its not pretty, but you can use explicit or clauses for the 6 subnets if unlikely to grow beyond that and the dataset for the /32 ip addresses:

http.req.url.path.contains_any("ps_urllists") &&

!((client.ip.src.in_subnet(x.x.x.x/aa) || client.ip.src.in-subnet(x.x.x.x/bb) || ....) || client.ip.src.typecast_text_t.contain_any("ps_ipwhitelist"))

 

At this point, I'd rather make a callout do it...for long term management.

 

There are a couple of other expressions that could be written if all your subnets had the same subnet mask...but this isn't the easiest thing to compare using a dataset.

ds_of subnets minus masks only:   So if all your subnets were /24, create the ps_ or ds_ as the IPs after mask applied:  192.168.10.0/24 would be 192.168.10.0. We would use the subnet operator instead of a in_subnet operator

client.ip.src.subnet(xx).typecast_text_t.equals_any(ps_subnets)

 

 

 

  • Like 1
Link to comment
Share on other sites

Rhonda,

 

Is there a way to enter this expression ((client.ip.src.in_subnet(x.x.x.x/aa) || client.ip.src.in-subnet(x.x.x.x/bb) via the GUI? or is this possible only via CLI? Is there some option in the AppExpert that allows you to specify subnets that the policy can reference?

 

Thanks!

Link to comment
Share on other sites

This can easily be done in the GUI, just go to the expression field:

 ((client.ip.src.in_subnet(x.x.x.x/aa) || client.ip.src.in_subnet(x.x.x.x/bb)

 

Replace letters with IPs and subnet masks:   10.10.10.0/24 etc...

The only difference between GUI and CLI is phrases have to be enclosed in double quotes and internal quotes have to be escaped.

The only issue we have is we can't do in_subnet() across a dataset/patternset...

So you could have an expression of both individual ips via patternset (based on earlier example, in case I fat fingered this one) and the in_subnets and manually listed:

client.ip.src.typecast_text_t.equals_any("ps_ipsubnet") || client.ip.src.in_subnet("10.10.10.0/24") || client.ip.src.in_subent("10.10.20.0/24")

 

Since you still want to only drop client not in the allowed list it would be something like this to do (urls && !(allowed ips/subents))

(<whatever the url list is>) && !(client.ip.src.typecast_text_t.equals_any("ps_ipsubnet") || client.ip.src.in_subnet("10.10.10.0/24") || client.ip.src.in_subent("10.10.20.0/24"))

 

If you put one in in the cli, and you look in the GUI to edit you will see what I mean about the cli quotes vs. cli.

Or if you go to the gui to create one, you can see the inline editor or the expression editor help you build the expressions. Then you can look at the configured policy in the cli to see how it was created.

show ns runningconfig | grep <policyname> -i                   # omit the <> in the actual field, -i makes grep case-insensitive

 

  • Like 1
Link to comment
Share on other sites

  • 2 weeks later...

Rhonda,

 

So apparently my expression is only partially working. Here is the policy (omitted in some areas)

 

HTTP.REQ.URL.PATH.SET_TEXT_MODE(ignorecase).CONTAINS("/path")&&!CLIENT.IP.SRC.TYPECAST_TEXT_T.CONTAINS_ANY("ds_ipwhitelist")||CLIENT.IP.SRC.IN_SUBNET(2.2.2.128/26)||CLIENT.IP.SRC.IN_SUBNET(3.3.3.0/26)||CLIENT.IP.SRC.IN_SUBNET(4.4.4.64/26)

 

It seems when people connect from an IP in the "ds_ipwhitelist" Data Set, they are able to obtain access to "/path" but when they try to access it from any of the "client.ip.src.in_subnet" subnets, they are not able to. This expression was written in the GUI using the App Expert. Do you see any issues with it? It seems like the policy only adheres to what comes immediately after the "&&" argument and is not evaluating anything after that? Are there any restrictions to using the "CLIENT.IP.SRC.TYPECAST_TEXT_T.CONTAINS_ANY" and the "CLIENT.IP.SRC.IN_SUBNET" together in the same expression? We are on 13.0.47.24, any bugs that may cause Policies to not work correctly?

 

Thanks. 

Link to comment
Share on other sites

Let's see: its a missing parentheses issue:

You posted this:

HTTP.REQ.URL.PATH.SET_TEXT_MODE(ignorecase).CONTAINS("/path")&&!CLIENT.IP.SRC.TYPECAST_TEXT_T.CONTAINS_ANY("ds_ipwhitelist")||CLIENT.IP.SRC.IN_SUBNET(2.2.2.128/26)||CLIENT.IP.SRC.IN_SUBNET(3.3.3.0/26)||CLIENT.IP.SRC.IN_SUBNET(4.4.4.64/26)

 

Reminder: This is a responder policy which will REDIRECT when TRUE and we want it TRUE when you are NOT on the allowed whitelists/exceptions...

 

HTTP.REQ.URL.PATH.SET_TEXT_MODE(ignorecase).CONTAINS("/path") && !(CLIENT.IP.SRC.TYPECAST_TEXT_T.CONTAINS_ANY("ds_ipwhitelist")||CLIENT.IP.SRC.IN_SUBNET(2.2.2.128/26)||CLIENT.IP.SRC.IN_SUBNET(3.3.3.0/26)||CLIENT.IP.SRC.IN_SUBNET(4.4.4.64/26))

 

You need the policy to be TRUE when:

A && !B

Where A  is going to the the "/path"

AND ! (on whitelists)

 

Without the parenthesis your policy is acting as (A && !B) || C || D || E ...

which says don't block if path and whitelist, but do block on any of these subnets...

 

EDIT:

If I remember correctly:  you are excluding the whitelist AND the subnets (not blacklisting the subnets); clarify and I'll update again.

 

 

 

 

 

 

 

 

 

 

 

 

 

Edited by Rhonda Rowland
fixed; missed position of exclamation point
  • Like 2
Link to comment
Share on other sites

Rhonda,

 

You truly are a savior! That is exactly correct. I want to evaluate against the Whitelist AND the subnets. If the traffic is destined for the directory "/path" AND the IP is NOT in either the whitelist OR the subnets, then block!

 

Thanks so much for your help. Ill await your reply :11_blush:

Link to comment
Share on other sites

I fixed - it you are missing the !(<whitelists || subnets)  - it sounded like you didn't see the fix in the post; so reposting just in case:

You are missing the exclamation point and the parentheses above:

Reposting (since !) got split:

 

HTTP.REQ.URL.PATH.SET_TEXT_MODE(ignorecase).CONTAINS("/path") &&
!(CLIENT.IP.SRC.TYPECAST_TEXT_T.CONTAINS_ANY("ds_ipwhitelist")||
CLIENT.IP.SRC.IN_SUBNET(2.2.2.128/26)||CLIENT.IP.SRC.IN_SUBNET(3.3.3.0/26)||CLIENT.IP.SRC.IN_SUBNET(4.4.4.64/26))

 

in RED:

simplified view:   path && !(whitelist || subnetA || subentB || subentC)

  • Like 1
Link to comment
Share on other sites

Rhonda,

 

Thank you! The expression is now as follows and seems to be working as expected:

 

HTTP.REQ.URL.PATH.SET_TEXT_MODE(ignorecase).CONTAINS("/path")&&!(CLIENT.IP.SRC.TYPECAST_TEXT_T.CONTAINS_ANY("ds_ipwhitelist") || CLIENT.IP.SRC.IN_SUBNET(12.2.2.128/26) || CLIENT.IP.SRC.IN_SUBNET(3.3.3.0/26) || CLIENT.IP.SRC.IN_SUBNET(4.4.4.64/26))

 

 I am having the affected group test and will conclude this thread once they say its good but I believe this is it. Thank yo so much!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...