Caddy returns "Basic realm=\"Radicale - Password Required\"" and http.status 401 #1798
-
|
Hello all, Today I installed Radicale in Docker, with LDAP for authentication and Caddy serving as a reverse proxy. On the face of it, everything seemed to work as expected and I was able to create a calendar and an address book and then sync their contents to my computer and phone. Radicale is accessible in the For all my external services I also add a Crowdsec bouncer in the Caddy config, which looks like this for Radicale: *.mydomain.com {
import dnsPorkbun
import bots
log {
output file /logs/mydomain.com.log {
roll_size 100MiB
roll_keep 5
roll_keep_for 100d
}
format json
level INFO
}
# Radicale
@radicale host dav.mydomain.com
handle @radicale {
reverse_proxy http://radicale:5232
}
handle * {
abort
}
}When I stepped out from home a little while ago, I got a notification that the IP address my phone was connecting from had been blocked. Here's the relevant portion of Caddy's log: {
"level": "info",
"ts": 1749310433.1873071,
"logger": "http.log.access.log0",
"msg": "handled request",
"request": {
"remote_ip": "212.39.89.45",
"remote_port": "50826",
"client_ip": "212.39.89.45",
"proto": "HTTP/2.0",
"method": "PROPFIND",
"host": "dav.mydomain.com",
"uri": "/zkvvoob/",
"headers": {
"Accept": [
"*/*"
],
"Accept-Encoding": [
"gzip, deflate, br"
],
"Prefer": [
"return=minimal"
],
"Depth": [
"0"
],
"Brief": [
"t"
],
"Content-Length": [
"267"
],
"User-Agent": [
"iOS/18.5 (22F76) dataaccessd/1.0"
],
"Accept-Language": [
"bg-BG,bg;q=0.9"
],
"Content-Type": [
"text/xml"
]
},
"tls": {
"resumed": false,
"version": 772,
"cipher_suite": 4865,
"proto": "h2",
"server_name": "dav.mydomain.com"
}
},
"bytes_read": 267,
"user_id": "",
"duration": 0.001797431,
"size": 61,
"status": 401,
"resp_headers": {
"Date": [
"Sat, 07 Jun 2025 15:33:53 GMT"
],
"Server": [
"WSGIServer/0.2 CPython/3.12.11"
],
"Content-Type": [
"text/plain; charset=utf-8"
],
"Www-Authenticate": [
"Basic realm=\"Radicale - Password Required\""
],
"Content-Encoding": [
"gzip"
],
"Via": [
"1.0 Caddy"
],
"Alt-Svc": [
"h3=\":443\"; ma=2592000"
],
"Content-Length": [
"61"
]
}
}And here are the Radicale Docker Composeservices:
radicale:
image: tomsquest/docker-radicale
container_name: radicale
ports:
- 5232:5232
init: true
read_only: true
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
cap_add:
- SETUID
- SETGID
- CHOWN
- KILL
deploy:
resources:
limits:
memory: 256M
pids: 50
healthcheck:
test: curl -f http://127.0.0.1:5232 || exit 1
interval: 30s
retries: 3
restart: unless-stopped
volumes:
- /mnt/apps/radicale/data:/data
- /mnt/apps/radicale/config:/config:ro
networks:
default:
name: proxy
external: trueRadicale config[server]
hosts = 0.0.0.0:5232
ssl = False
[encoding]
# Encoding for responding requests
request = utf-8
# Encoding for storing local collections
stock = utf-8
[auth]
# Authentication method
# Value: none | htpasswd | remote_user | http_x_remote_user | dovecot | ldap | oauth2 | pam | denyall
type = ldap
# Cache logins for until expiration time
cache_logins = false
# Expiration time for caching successful logins in seconds
cache_successful_logins_expiry = 15
## Expiration time of caching failed logins in seconds
cache_failed_logins_expiry = 90
# URI to the LDAP server
ldap_uri = ldap://lldap:3890
# The base DN where the user accounts have to be searched
ldap_base = dc=mydomain,dc=com
# The reader DN of the LDAP server
ldap_reader_dn = uid=radicale,ou=people,dc=mydomain,dc=com
# Password of the reader DN
ldap_secret = password
# Path of the file containing password of the reader DN
# dap_secret_file = /run/secrets/ldap_password
# the attribute to read the group memberships from in the user's LDAP entry (default: not set)
ldap_groups_attribute = memberOf
# The filter to find the DN of the user. This filter must contain a python-style placeholder for the login
ldap_filter = (&(|(uid={0})(mail={0}))(objectClass=person))
# the attribute holding the value to be used as username after authentication
ldap_user_attribute = uid
[rights]
# Rights backend
# Value: authenticated | owner_only | owner_write | from_file
type = owner_only
[storage]
# Folder for storing local collections, created if not present
filesystem_folder = /data/collections
# Create predefined user collections
predefined_collections = {
"addressbook": {
"D:displayname": "Contacts",
"tag": "VADDRESSBOOK"
},
"calendar": {
"C:supported-calendar-component-set": "VEVENT,VJOURNAL,VTODO",
"D:displayname": "Calendar",
"tag": "VCALENDAR"
}
}
[web]
type = none
[logging]
# Threshold for the logger
level = warning
# Don't include passwords in logs
mask_passwords = True
# Log bad PUT request content
bad_put_request_content = True
# Log backtrace on level=debug
backtrace_on_debug = True
# Log request header on level=debug
request_header_on_debug = True
# Log request content on level=debug
request_content_on_debug = True
# Log response content on level=debug
response_content_on_debug = True
# Log rights rule which doesn't match on level=debug
rights_rule_doesnt_match_on_debug = True
# Log storage cache actions on level=debug
storage_cache_actions_on_debug = True
Can someone help me understand if there's anything else I must add to either the reverse proxy config or to Radicale's in order for this type of errors to disappear? Thank you! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 1 reply
-
|
If server returns 401, client should re-request URL but with authentication header - any clue why this is not happen? |
Beta Was this translation helpful? Give feedback.
After some investigation, including testing the whole process on an Android phone, I think something has changed on the Apple side since the last update or two. Looking at the Caddy logs I saw this exact scenario that you describe happen: the client (iOS/macOS) attempts to connect without authorisation, receives 401 and follows up immediately with proper authentication.
Unfortunately, Crowdsec is merciless and institutes a ban immediately, regardless of the subsequent successfull authentication.
I've added a whitelist for cases such as this, because I can't afford to be locked out of my network when out.