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

breakfix: keystore: failed to parse token with JWT shared secret #277

Closed
michaelsproul opened this issue Sep 20, 2023 · 4 comments
Closed
Assignees
Labels
bug Something isn't working jwt need triage

Comments

@michaelsproul
Copy link

Describe the issue

I'm having trouble getting JWT shared secret authorization to work. I'm repeatedly getting this error:

{"level":"error","ts":1695181938.4618888,"logger":"http.handlers.authentication","msg":"auth provider returned error","provider":"authorizer","error":"user authorization failed: src_ip=REDACTED, src_conn_ip=REDACTED, reason: keystore: failed to parse token"}

I get the same error regardless of whether I use a 32-byte secret or the default 16-byte secret from: https://authp.github.io/docs/authorize/token-verification. I would like to be using a 32-byte secret and the HS256 algorithm. I'm probably missing something very basic.

Configuration

{
        security {
                authorization policy eleel-jwt {
                        set token sources header query
                        validate bearer header
                        acl default allow
                        disable auth redirect

                        crypto default token lifetime 60

                        crypto key verify 383aca9a-1c39-4d7a-b4d8-67ba4718dd3f
                }
        }
}
# domain name redacted
site.com {
        log {
                output stderr
                format filter {
                        wrap console
                        fields {
                                request>headers>Authorization delete
                        }
                }
        }
        @eleel {
                path /eleel
                path /eleel/
        }
        route @eleel {
                authorize with eleel-jwt
                reverse_proxy @eleel localhost:8552 {
                        rewrite /
                }
        }
        route /auth {
                authorize with eleel-jwt
                respond "test"
        }
        handle_errors {
                respond "{\"error\": \"{http.error.status_code} {http.error.status_text}\"}"
        }

}

Version Information

admin.api.load v2.7.4
admin.api.metrics v2.7.4
admin.api.pki v2.7.4
admin.api.reverse_proxy v2.7.4
caddy.adapters.caddyfile v2.7.4
caddy.config_loaders.http v2.7.4
caddy.listeners.http_redirect v2.7.4
caddy.listeners.proxy_protocol v2.7.4
caddy.listeners.tls v2.7.4
caddy.logging.encoders.console v2.7.4
caddy.logging.encoders.filter v2.7.4
caddy.logging.encoders.filter.cookie v2.7.4
caddy.logging.encoders.filter.delete v2.7.4
caddy.logging.encoders.filter.hash v2.7.4
caddy.logging.encoders.filter.ip_mask v2.7.4
caddy.logging.encoders.filter.query v2.7.4
caddy.logging.encoders.filter.regexp v2.7.4
caddy.logging.encoders.filter.rename v2.7.4
caddy.logging.encoders.filter.replace v2.7.4
caddy.logging.encoders.json v2.7.4
caddy.logging.writers.discard v2.7.4
caddy.logging.writers.file v2.7.4
caddy.logging.writers.net v2.7.4
caddy.logging.writers.stderr v2.7.4
caddy.logging.writers.stdout v2.7.4
caddy.storage.file_system v2.7.4
events v2.7.4
http v2.7.4
http.authentication.hashes.bcrypt v2.7.4
http.authentication.hashes.scrypt v2.7.4
http.authentication.providers.http_basic v2.7.4
http.encoders.gzip v2.7.4
http.encoders.zstd v2.7.4
http.handlers.acme_server v2.7.4
http.handlers.authentication v2.7.4
http.handlers.copy_response v2.7.4
http.handlers.copy_response_headers v2.7.4
http.handlers.encode v2.7.4
http.handlers.error v2.7.4
http.handlers.file_server v2.7.4
http.handlers.headers v2.7.4
http.handlers.invoke v2.7.4
http.handlers.map v2.7.4
http.handlers.metrics v2.7.4
http.handlers.push v2.7.4
http.handlers.request_body v2.7.4
http.handlers.reverse_proxy v2.7.4
http.handlers.rewrite v2.7.4
http.handlers.static_response v2.7.4
http.handlers.subroute v2.7.4
http.handlers.templates v2.7.4
http.handlers.tracing v2.7.4
http.handlers.vars v2.7.4
http.ip_sources.static v2.7.4
http.matchers.client_ip v2.7.4
http.matchers.expression v2.7.4
http.matchers.file v2.7.4
http.matchers.header v2.7.4
http.matchers.header_regexp v2.7.4
http.matchers.host v2.7.4
http.matchers.method v2.7.4
http.matchers.not v2.7.4
http.matchers.path v2.7.4
http.matchers.path_regexp v2.7.4
http.matchers.protocol v2.7.4
http.matchers.query v2.7.4
http.matchers.remote_ip v2.7.4
http.matchers.vars v2.7.4
http.matchers.vars_regexp v2.7.4
http.precompressed.br v2.7.4
http.precompressed.gzip v2.7.4
http.precompressed.zstd v2.7.4
http.reverse_proxy.selection_policies.client_ip_hash v2.7.4
http.reverse_proxy.selection_policies.cookie v2.7.4
http.reverse_proxy.selection_policies.first v2.7.4
http.reverse_proxy.selection_policies.header v2.7.4
http.reverse_proxy.selection_policies.ip_hash v2.7.4
http.reverse_proxy.selection_policies.least_conn v2.7.4
http.reverse_proxy.selection_policies.query v2.7.4
http.reverse_proxy.selection_policies.random v2.7.4
http.reverse_proxy.selection_policies.random_choose v2.7.4
http.reverse_proxy.selection_policies.round_robin v2.7.4
http.reverse_proxy.selection_policies.uri_hash v2.7.4
http.reverse_proxy.selection_policies.weighted_round_robin v2.7.4
http.reverse_proxy.transport.fastcgi v2.7.4
http.reverse_proxy.transport.http v2.7.4
http.reverse_proxy.upstreams.a v2.7.4
http.reverse_proxy.upstreams.multi v2.7.4
http.reverse_proxy.upstreams.srv v2.7.4
pki v2.7.4
tls v2.7.4
tls.certificates.automate v2.7.4
tls.certificates.load_files v2.7.4
tls.certificates.load_folders v2.7.4
tls.certificates.load_pem v2.7.4
tls.certificates.load_storage v2.7.4
tls.client_auth.leaf v2.7.4
tls.get_certificate.http v2.7.4
tls.get_certificate.tailscale v2.7.4
tls.handshake_match.remote_ip v2.7.4
tls.handshake_match.sni v2.7.4
tls.issuance.acme v2.7.4
tls.issuance.internal v2.7.4
tls.issuance.zerossl v2.7.4
tls.stek.distributed v2.7.4
tls.stek.standard v2.7.4

  Standard modules: 106

http.authentication.providers.authorizer v1.1.20
http.handlers.authenticator v1.1.20
security v1.1.20

  Non-standard modules: 3

  Unknown modules: 0

Expected behavior

I would expect that using this script, I would be able to get a 200 response:

#!/usr/bin/env bash

set -eo pipefail

if [ $# -eq 0 ]
then
    echo "no JWT secret provided"
    echo "usage: ./test.sh JWT [URL]"
    exit 1
fi

jwtsecret=$1
tmpfile=./.test_sh_jwt

server_url=$2
server_url=${server_url:="http://localhost:8551"}

cat $jwtsecret | xxd -r -p > $tmpfile
JWT=$(jwt encode --secret @$tmpfile)

echo $JWT
curl \
    --location \
    --header "Authorization: Bearer $JWT" \
    $server_url

rm -f $tmpfile

It depends on jwt-cli. With ~/example.jwt containing 383aca9a-1c39-4d7a-b4d8-67ba4718dd3f in plain text, I invoke it as ./test.sh ~/example.jwt https://site.com/auth:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2OTUxODI1NzV9.iIWqgwPtFSam3zx2EmPRE-Il1gtivGrmv09n2sXlHQA
{"error": "401 Unauthorized"}

I've checked that this example JWT verifies against the secret on jwt.io.

However, Caddy logs:

{"level":"error","ts":1695182575.5217702,"logger":"http.handlers.authentication","msg":"auth provider returned error","provider":"authorizer","error":"user authorization failed: src_ip=REDACTED, src_conn_ip=REDACTED, reason: keystore: failed to parse token"}
2023/09/20 04:02:55.521 ERROR http.log.access.log0 handled request {"request": {"remote_ip": "REDACTED", "remote_port": "60850", "client_ip": "REDACTED", "proto": "HTTP/2.0", "method": "GET", "host": "REDACTED", "uri": "/auth", "headers": {"User-Agent": ["curl/7.88.1"], "Accept": ["/"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": "REDACTED"}}, "bytes_read": 0, "user_id": "", "duration": 0.0001021, "size": 29, "status": 401, "resp_headers": {"Server": ["Caddy"], "Alt-Svc": ["h3=":443"; ma=2592000"], "Content-Type": ["application/json"]}}

Additional context

Things I've tried:

  • Every permutation of Authorization: (Bearer) $JWT and validate bearer header. When I mismatch the request header with the config I get reason: no token found in Caddy's logs, which suggests that token discovery is working when the header matches the config.
  • Passing the JWT in the query string as ?access_token=$JWT. It gets picked up but the error is the same.
  • Using different secrets.
  • Using GET and POST (/auth vs /eleel).
  • Removing the request>headers>Authorization delete directive which deletes the Auth header from the logs.
@greenpau
Copy link
Owner

greenpau commented Dec 2, 2023

@michaelsproul , is there a PR to fix it?

@michaelsproul
Copy link
Author

@greenpau No sorry, I'm clueless when it comes to writing Go. I ended up writing my own custom JWT authenticator in Rust.

@greenpau
Copy link
Owner

greenpau commented Dec 2, 2023

@michaelsproul , could you please remind me which part of code suffered from 32 vs 64?

@michaelsproul
Copy link
Author

I'm not sure what you mean? I haven't looked at the implementation at all, and I don't think the 32 vs 16 byte secret size is the main issue. The problem seems to be that having a separate section of the config for the JWT auth and then trying to reuse it for each of the routes, does not work.

@greenpau greenpau closed this as completed Dec 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working jwt need triage
Projects
None yet
Development

No branches or pull requests

2 participants