greenpau / caddy-security

πŸ” Authentication, Authorization, and Accounting (AAA) App and Plugin for Caddy v2. πŸ’Ž Implements Form-Based, Basic, Local, LDAP, OpenID Connect, OAuth 2.0 (Github, Google, Facebook, Okta, etc.), SAML Authentication. MFA/2FA with App Authenticators and Yubico. πŸ’Ž Authorization with JWT/PASETO tokens. πŸ”
https://authcrunch.com/
Apache License 2.0
1.49k stars 73 forks source link

breakfix: ACL path/method rules always seem to result in a miss #329

Closed alberthdev closed 7 months ago

alberthdev commented 7 months ago

Describe the issue

When creating an ACL rule in the Caddyfile that includes a variation of match method or match path, the ACL rule does not trigger at all and various misses occur in the debug log.

By way of example... this works and logs:

acl rule {
    match role some-role
    allow stop counter log info tag ALLOW_RULE
}

...but if I add either of match method get or match path /somepath, it fails and does not log any ACL hits at all:

acl rule {
    match role some-role
    match method get
    match path /somepath
    allow stop counter log info tag ALLOW_APP_SOME_PATH
}

Configuration

Paste full Caddyfile below:

{
    http_port 80
    https_port 443

    order authenticate before respond
    order authorize before basicauth

    debug

    security {
        local identity store localdb {
            realm local
            path /some/path/to/auth/users.json
        }

        credentials some.email@example.com {
            username some.email@example.com
            password REMOVED
        }

        messaging email provider SOME-EMAIL-PROVIDER {
            address some.email.provider.domain:12345
            protocol smtp
            credentials some.email@example.com
            sender some.email@example.com "Some Author"
            bcc some.email@example.com
        }

        user registration localdbRegistry {
            dropbox /some/path/to/auth/registrations.json
            title "Example Title"
            code "Example Code"
            require accept terms
            require domain mx
            admin email some.email@example.com
            email provider SOME-EMAIL-PROVIDER
            identity store localdb
        }

        authentication portal myportal {
            enable identity store localdb
            enable source ip tracking
            cookie domain *.example.com
            cookie samesite strict
            crypto default token lifetime 12345

            transform user {
                match realm local
                require mfa
                ui link "Portal Settings" /settings icon "las la-cog"
            }

            transform user {
                match realm local
                exact match email some.special.role@example.com
                action add role some-special-role
            }
        }

        authorization policy app-policy {
            # disable auth redirect
            set auth url https://auth.example.com/
            validate source address
            allow roles authp/admin authp/user
            allow roles anonymous guest admin
            allow roles authp/admin authp/guest
            allow roles admin editor viewer
            allow roles everyone Everyone

            with api key auth portal myportal realm local

            acl rule {
                comment For some-special-role only allow /somepath
                match role some-special-role
                #match method get
                #match path /somepath
                allow stop counter log info tag ALLOW_APP_SOME_PATH
            }

            acl rule {
                comment Deny some-special-role to all other endpoints
                match role some-special-role
                no match path /somepath
                deny stop counter log warn tag DENY_APP_OTHER_ENDPOINTS
            }
        }
    }

    crowdsec {
        api_url http://example.com:12345
        api_key REDACTED_KEY
        ticker_interval 15s
    }

    servers {
        listener_wrappers {
            proxy_protocol {
                allow 1.2.3.4/56
            }
            tls
        }
    }
}

example.com, *.example.com {
    tls {
        dns some_dns_provider {
            api_key REDACTED_KEY
            api_secret_key REDACTED_SECRET_KEY
        }
        resolvers 1.2.3.4
    }

    @auth-example-com host auth.example.com
    handle @auth-example-com {
        route {
            authenticate with myportal
        }
    }

    @app-example-com host app.example.com
    handle @app-example-com {
        authorize with app-policy
        reverse_proxy app:12345
    }

    # Fallback for otherwise unhandled domains
    handle {
        abort
    }
}

Version Information

Provide output of caddy list-modules -versions | grep git below:

No output when filtering for git. A shortened list of non-standard modules:

crowdsec v0.6.0
dns.providers.some_dns_provider v1.2.3
http.authentication.providers.authorizer v1.1.23
http.handlers.authenticator v1.1.23
security v1.1.23

Can post the full list if desired.

Expected behavior

When the requisite lines are uncommented, an authenticated user with some-special-role should be able to do GET /somepath only, while any other requests to @app-example-com are blocked.

Additional context

Some Caddy debug logs when trying to access /somepath when logged in with a user that has some-special-role, and match path /somepath is enabled:

{"level":"debug","ts":0.1979554,"logger":"security","msg":"acl rule hit","action":"allow","tag":"rule0","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"debug","ts":0.1979837,"logger":"security","msg":"acl rule miss","action":"continue","tag":"rule1","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"debug","ts":0.1979928,"logger":"security","msg":"acl rule miss","action":"continue","tag":"rule2","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"debug","ts":0.197999,"logger":"security","msg":"acl rule miss","action":"continue","tag":"rule3","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"debug","ts":0.198005,"logger":"security","msg":"acl rule miss","action":"continue","tag":"rule4","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"debug","ts":0.198028,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"app:12345","total_upstreams":1}
{"level":"debug","ts":0.23883,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"app:12345","duration":0.040765605,"request":{"remote_ip":"123.456.789.0","remote_port":"12345","client_ip":"123.456.789.0","proto":"HTTP/2.0","method":"GET","host":"app.example.com","uri":"/somepath","headers":{"Sec-Fetch-Mode":["navigate"],"Sec-Fetch-Dest":["document"],"Te":["trailers"],"Accept":["text/html"],"Sec-Fetch-Site":["none"],"Accept-Encoding":["gzip, deflate, br"],"Upgrade-Insecure-Requests":["1"],"X-Forwarded-Proto":["https"],"X-Forwarded-Host":["app.example.com"],"Sec-Gpc":["1"],"Sec-Fetch-User":["?1"],"Accept-Language":["en-US,en;q=0.5"],"Cookie":[],"User-Agent":["Some-User-Agent"],"Dnt":["1"],"X-Forwarded-For":["123.456.789.0"]},"tls":{"resumed":false,"version":12345,"cipher_suite":12345,"proto":"h2","server_name":"app.example.com"}},"headers":{},"status":404}

With that line removed (e.g. ACL only constraining on match role some-role), and caddy reload ran, the rule now hits:

{"level":"debug","ts":0.124578,"logger":"security","msg":"acl rule hit","action":"allow","tag":"rule0","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"debug","ts":0.1246111,"logger":"security","msg":"acl rule miss","action":"continue","tag":"rule1","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"debug","ts":0.1246188,"logger":"security","msg":"acl rule miss","action":"continue","tag":"rule2","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"debug","ts":0.1246257,"logger":"security","msg":"acl rule miss","action":"continue","tag":"rule3","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"debug","ts":0.124632,"logger":"security","msg":"acl rule miss","action":"continue","tag":"rule4","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"info","ts":0.1246397,"logger":"security","msg":"acl rule hit","action":"allow","counter":4,"tag":"ALLOW_APP_SOME_PATH","user":{"addr":"123.456.789.0","email":"some.special.role@example.com","iss":"hxxps://auth.example.com/login","jti":"REMOVED","origin":"local","roles":["authp/user","some-special-role"],"sub":"someuser"}}
{"level":"debug","ts":0.124664,"logger":"http.handlers.reverse_proxy","msg":"selected upstream","dial":"app:12345","total_upstreams":1}
{"level":"debug","ts":0.170096,"logger":"http.handlers.reverse_proxy","msg":"upstream roundtrip","upstream":"app:12345","duration":0.045390901,"request":{"remote_ip":"123.456.789.0","remote_port":"12345","client_ip":"123.456.789.0","proto":"HTTP/2.0","method":"GET","host":"app.example.com","uri":"/somepath","headers":{"Sec-Fetch-Dest":["document"],"Te":["trailers"],"Accept":["text/html"],"Sec-Fetch-User":["?1"],"X-Forwarded-Host":["app.example.com"],"Dnt":["1"],"Cookie":[],"Sec-Gpc":["1"],"Sec-Fetch-Site":["none"],"User-Agent":["Some-User-Agent"],"X-Forwarded-For":["123.456.789.0"],"X-Forwarded-Proto":["https"],"Accept-Encoding":["gzip, deflate, br"],"Accept-Language":["en-US,en;q=0.5"],"Sec-Fetch-Mode":["navigate"],"Upgrade-Insecure-Requests":["1"]},"tls":{"resumed":false,"version":12345,"cipher_suite":12345,"proto":"h2","server_name":"app.example.com"}},"headers":{},"status":404}
greenpau commented 7 months ago

@alberthdev , I think the match path and method applies to the https://docs.authcrunch.com/docs/authorize/path-acl

what you want to do is have a Caddy matcher (not acl match), that matches a specific method and path, and then create various policies for them.

greenpau commented 7 months ago

Caddy matchers https://caddyserver.com/docs/caddyfile/matchers#path-matchers