cfmlprojects / runwar

Other
11 stars 16 forks source link

Implement access control in Undertow #137

Open bdw429s opened 5 years ago

bdw429s commented 5 years ago

There are a lot of use cases for wanting to deny access to certain URL patterns and Undertow seems to have some decent support for this. Let's think of the best way to declare access control for certain paths that makes it easy to use but it still configurable. For instance, it may be easiest to simply have a list of paths that we want to deny, but it would be better if we could allow exceptions so, for instance, the CF admin would be accessible from localhost.

Here's some example use cases:

Undertow seems to have some capability to block based on user agent, but that seems worthless since anyone can spoof that. There is also the ability to block based on IP which which seems most useful.

Here's the docs I was able to dig up: http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#access-control-handler https://repository.jboss.org/nexus/content/unzip/unzip/io/undertow/undertow-core/1.0.14.Final/undertow-core-1.0.14.Final-javadoc.jar-unzip/io/undertow/server/handlers/AccessControlListHandler.html

http://undertow.io/undertow-docs/undertow-docs-2.0.0/index.html#ip-access-control-handler https://repository.jboss.org/nexus/content/unzip/unzip/io/undertow/undertow-core/1.0.14.Final/undertow-core-1.0.14.Final-javadoc.jar-unzip/io/undertow/server/handlers/IPAddressAccessControlHandler.html

bdw429s commented 5 years ago

https://ortussolutions.atlassian.net/browse/COMMANDBOX-831

denuno commented 5 years ago

We should probably set this up to jive with the basic auth stuff. I actually added some Undertow ACL code back then and played with it a little:

https://github.com/cfmlprojects/runwar/blob/master/src/main/java/runwar/security/SecurityManager.java#L77

IIRC we just need to figure out how to define the ACL...

This is another thing that looks a lot better in JSON than on the command line, and roles will be rough regardless because duplication/misspelling/etc..

See this issue for previous discussion: https://github.com/cfmlprojects/runwar/issues/64

I'm thinking maybe something like this to cover both:

{
    "web":{
        "security":{
            "type": "basicAuth",
            "users":{
                "brad":"wood",
                "guest":"password",
            },
            "roles" : { 
                 "admin" : [ "brad" ],
                  "public": [ "guest", "brad" ] 
            },
            "paths" : {
                "/foo" : {
                   "users":["guest","brad"],
                   "roles":["public"]
                },
                "/admin/bar" : {
                   "roles":["admin"]
                }
            },
            "enable":"true"
        }
    }
}

Perhaps the security element should have a sub-element named basicAuth with the info in it, versus the type attribute, but generally a structure along those lines.

denuno commented 5 years ago

Paths with no users/roles are forbidden? I dunno, seems like this and the existing auth stuff should go together though.

bdw429s commented 5 years ago

I think we should simplify it a bit of not necessarily offer everything Undertow does. For what it's worth, I'm not sure if many people ever even use the basic auth feature. To get some perspective, the two actual use case scenarios I really have at this point are:

I really don't want to go nearly as complicated with what's above when I could solve 99% of what my users are currently asking for with something really as simple as a selective whitelist:

"firewall" : {
  "/box.json" : "",
  "/server.json" : "",
  "/CFIDE/*" : "127.0.0.*,10.10.0.*"
}

So the ideal solution maybe is somewhere in between the two. I really don't want to get into any role nonsense-- that can be left up to the application. I'd be fine if we could have a simple map of path patterns that pointed to a whitelist of IP ranges (empty means no one can access) or a map of username/passwords. Maybe then we could tie in basic auth like that so securing your entire site would just be a pattern of /* then the users you wanted.

"firewall" : {
  "/box.json" : "",
  "/*" : {
       "brad" : "brads pass",
       "denny" : "dennys pass"
    }
}

I mainly just want to make sure we worry about keeping the simple use cases simple more than we worry about supporting some of the crazier edge cases. Another question to answer is whether the order of the rules matters. If so, I'd make it an array in the config.

bdw429s commented 5 years ago

I got another bit of info from Stuart on the Undertow-dev list today which is interesting. Undertow has a string syntax that allows you to specify (in some sort of config file) a parsable DSL that declares your predicates such as path matching patterns and what to do with them.

path-suffix(/box.json) -> response-code(404)

That string above would return a 404 for any box.json files. Here's some docs:

https://github.com/undertow-io/undertow-docs/blob/master/src/main/asciidoc/predicates-attributes-handlers.asciidoc

Undertow's so-called "predicate language" seems really powerful, although perhaps not 100% obvious for a first time reader. I can't help but think it would be super cool to allow users to pass predicates directly to Undertow. You could configure just about anything with the right combination it looks like.

So I think the million dollar question now is how can we keep some of those simple use cases above still stay simple and not require CommandBox/Runwar users to bother learning Undertow's predicate language but still leave it open for advanced users to tap into it and basically do unlimited stuff. According to Undertow, predicates/handlers are executed in the order defined, so either way I'd need to collect them as an array and we'd want to load them before the built in CFML handlers in Runwar so they can do things like security, rewrites, etc.

If we just straight up let people use Undertow's predicate language, the config could be as simple as this:

"rules" : [
  "path-suffix(/box.json) -> response-code(404)",
  "path-prefix(.) -> response-code(404)",
  "path-prefix(/admin/) -> ip-access-control(192.168.0.* allow)",
  "path-template(/sitemap.xml) -> rewrite(/sitemap.cfm)"
]

I'm really liking all the powerful things we could do here without needing to explicitly add support for all of them, just passing through the predicate and handler to Undertow. A note on this-- we need to figure out how to enable logging for this stuff or debugging it might be near impossible.

Another idea would be a mashup of the two, where we choose a default predicate and handler that we think would apply to 90% of all the use cases to reduce boilerplate and typos and then still allow the user to pass a full predicate/handler if they wish as an "advanced" feature.

// Default predicate as regex and default handler as response-code
"rules" : [
  { match=".*/box.json", response="404" },
  { match="\..*", response="404" },
  { predicate="path-prefix(/admin/) -> ip-access-control(192.168.0.* allow)" )
]

Yeah, I dunno. Now that I type up and read that last example, I'm not sure if it's really any more readable or simple than the previous example that just relies 100% on Undertow's predicate language.

The only thing we haven't accounted for here is basic auth (I'm not sure exactly how it ties in with the predicates or whether it's part of Undertow's predicate language). I'm not quite as worried about basic auth however since it's not used a lot and we already have an existing implementation that allows you to apply it to the entire site. The only thing you can't do right now which I think would be handy is to apply basic auth to a subdirectory, or a predicate of some sort. I do think that could be handy.

Thoughts?

bdw429s commented 5 years ago

@denuno What do you think about that Undertow predicate language and what would it take for you to tap into it? I think I'd like to experiment with something in CommandBox where I let the user give me an ordered list of Undertow predicates and handlers. The power of undertow's predicate DSL has sort of grown on me.

"rules" : [
  "path-suffix(/box.json) -> response-code(404)",
  "path-prefix(.) -> response-code(404)",
  "path-prefix(/admin/) -> ip-access-control(192.168.0.* allow)",
  "path-template(/sitemap.xml) -> rewrite(/sitemap.cfm)"
]

I think the only real question left is, "How do I pass this from CommandBox to Undertow?"