Skip to content

Restrict the souin-api to only localhost:2019 in Caddy? #619

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

Open
jonbuda opened this issue Apr 21, 2025 · 4 comments
Open

Restrict the souin-api to only localhost:2019 in Caddy? #619

jonbuda opened this issue Apr 21, 2025 · 4 comments

Comments

@jonbuda
Copy link

jonbuda commented Apr 21, 2025

Unless I'm missing something, I can't seem to figure out how to prevent /souin-api/* from being accessed from public domain/subdomain endpoints in my Caddy config.

Here's a simplified Caddyfile:

# global Caddy config
{
  email [email protected]

  debug
  cache {
    allowed_http_verbs GET HEAD OPTIONS
    ttl 60s
    mode bypass
    nuts {
      path /path/to/nutsdb
    }
    api {
      souin
    }
  }

  # let Rails handle redirects to https
  auto_https disable_redirects

  on_demand_tls {
    ask http://:80/verify_domain
  }
}

# app Dashboard (not cached)
dashboard.mydomain.localhost:443 {
  reverse_proxy http://127.0.0.1:3000
}

# wildcard matcher
*.mydomain.localhost:443 {
  reverse_proxy http://127.0.0.1:3000
  cache {
    mode bypass
  }
}

# customer custom domains
:443 {
  reverse_proxy http://127.0.0.1:3000
  cache {
    mode bypass
  }
  tls {
    on_demand
  }
}

in this example, I can hit the Souin API at URLs like https://testing.mydomain.localhost/souin-api/souin/surrogate_keys or a customer's custom domain like https://mydomain.com/souin-api/souin/surrogate_keys

We're looking to only be able to hit the Souin API from within the webserver itself (http://127.0.01:2019/souin-api/souin/*), so that we can purge by surrogate key as needed.

Is this possible? Thanks! Overall it's working really well, we'd just like to figure this out!

@popcorn
Copy link

popcorn commented Apr 23, 2025

cc @jonbuda, I have only played a bit with Souin so far so take this advice with a grain of salt.

Potential fix would be to match on /souin-api* and check for an authorization header with the secret value only you know.

This way the endpoint can stay public but it's inaccessible unless you know the secret value.

Example Caddy JSON route:

{
  "match": [
    {
      "path": ["/souin-api*"],
      "not": [
        {
          "header": {
            "Authorization": ["123456"]
          }
        }
      ]
    }
  ],
  "handle": [
    {
      "handler": "static_response",
      "status_code": 403,
      "body": "Unauthorized"
    }
  ]
}

@jonbuda
Copy link
Author

jonbuda commented Apr 23, 2025

@popcorn love that idea...however it doesn't quite seem to work.

For instance, if I update my Caddyfile to this:

# Blocks access to /souin-api* unless Authorization header is "123456"
(souin_api) {
  @souin_api_match {
    path /souin-api*
    not header Authorization 123456
  }
  respond @souin_api_match "Unauthorized" 403 {
    close
  }
}

# wildcard matcher
*.mydomain.localhost:443 {
  import souin_api
  reverse_proxy http://127.0.0.1:3000
  cache {
    mode bypass
  }
}

something like https://test.mydomain.localhost/souin-api returns a 403, but https://test.mydomain.localhost/souin-api/souin still returns all my cache keys. seems like Souin is somehow ignoring that matcher for other routes? if I comment out the cache {} block, it works as intended. 🤔

@popcorn
Copy link

popcorn commented Apr 23, 2025

That's interesting, maybe there's some difference between how JSON configuration file and Caddyfile formats work.

When I put this route as the very first route, it works as you want it:

{
  "match": [
    {
      "path": ["/souin-api*"],
      "not": [
        {
          "header": {
            "Authorization": ["123456"]
          }
        }
      ]
    }
  ],
  "handle": [
    {
      "handler": "static_response",
      "status_code": 403,
      "body": "Unauthorized"
    }
  ]
}

Here's my entire caddy.json if you want to try:

{
  "apps": {
    "cache": {
      "api": {
        "basepath": "/souin-api",
        "souin": {
          "basepath": "/souin",
          "enable": true,
          "security": false
        }
      },
      "log_level": "debug",
      "ttl": "5m",
      "headers": [
        "Content-Type",
        "Authorization"
      ],
      "timeout":{
        "backend": "10s",
        "cache": "8s"
      },
      "cache_name": "saas_custom_domains_cache",
      "redis": {
        "found": true,
        "url": "127.0.0.1:6379"
      }
    },
    "http": {
      "servers": {
        "tls_terminator": {
          "listen": [
            ":443"
          ],
          "routes": [
            {
              "match": [
                {
                  "path": ["/souin-api*"],
                  "not": [
                    {
                      "header": {
                        "Authorization": ["123456"]
                      }
                    }
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "static_response",
                  "status_code": 403,
                  "body": "Unauthorized"
                }
              ]
            },
            {
              "match": [
                {
                  "host": [
                    "localhost"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "cache"
                },
                {
                  "handler": "static_response",
                  "body": "Hello, world!",
                  "status_code": 200
                }
              ]
            }
          ],
          "logs": {}
        }
      }
    },
    "tls": {
      "automation": {
        "on_demand": {
          "ask": "http://localhost:3000/caddy/ask"
        }
      },
      "cache": {
        "capacity": 100000
      }
    }
  },
  "admin": {
    "identity": {
      "issuers": [
        {
          "module": "acme",
          "email": "[email protected]"
        }
      ]
    }
  },
  "logging": {
    "logs": {
      "default": {
        "level": "DEBUG",
        "exclude": [
          "http.log.access"
        ],
        "writer": {
          "output": "file",
          "filename": "/var/log/caddy-dev/caddy.log",
          "roll": true,
          "roll_size_mb": 64,
          "roll_keep": 20
        },
        "encoder": {
          "format": "json",
          "time_format": "iso8601"
        }
      },
      "log0": {
        "level": "DEBUG",
        "writer": {
          "output": "file",
          "filename": "/var/log/caddy-dev/access.log",
          "roll": true,
          "roll_size_mb": 64,
          "roll_keep": 20
        },
        "encoder": {
          "format": "json",
          "time_format": "iso8601"
        },
        "include": [
          "http.log.access"
        ]
      }
    }
  },
  "storage": {
    "module": "file_system",
    "root": "~/caddy-dev/data"
  }

@jonbuda
Copy link
Author

jonbuda commented Apr 24, 2025

@popcorn oh wow, yeah when I convert my Caddyfile to JSON it is way different than yours. I'll have to investigate further!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants