Skip to content

feat(martin-server): add support for basic CORS configuration #1815

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

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

NINNiT
Copy link

@NINNiT NINNiT commented May 1, 2025

As mentioned in #931, I've tried to come up with a basic implementation/spec which allows configuration of CORS..

Before:
Static CORS configuration in server.rs.

let cors_middleware = Cors::default()
         .allow_any_origin()
         .allowed_methods(vec!["GET"]);

This translates to the following logic:

  • If an incoming request has an Origin Header, the value of this Headers is used to set Access-Control-Allow-Origin (not *)
  • If an incoming request has no Origin Header, Access-Control-Allow-Origin is not set, however vary headers are still being sent

After:

  • Same default behavior as before (CORS enabled, all origins allowed, only GET methods allowed). There should be no breaking changes for existing deployments.
  • Allows configuration of the following properties:
# CORS Configuration
# Can also be disabled via `cors: false`
cors:
  # Sets the `Access-Control-Allow-Origin` header [default: *]
  # '*' will use the requests `ORIGIN` header
  origin:
    - https://example.org
  # Sets `Access-Control-Max-Age` Header. [default: null]
  # null means not setting the header for preflight requests
  max_age: 3600
  • Conditionally wraps the actix app in the CORS middleware, if the latter is enabled
   app.wrap(Condition::new(
            cors_middleware.is_some(),
            cors_middleware.unwrap_or_default(),
        ))
  • Adds tests for CORS

  • Config is nested under SrvConfig . I think this is fitting, as it's not a source.

Let me know if you have any suggestions, changes that should be applied or if you'd like to take this over. 🦀
@CommanderStorm im gonna ping you here, hope that's okay

@Copilot Copilot AI review requested due to automatic review settings May 1, 2025 11:55
@NINNiT NINNiT marked this pull request as draft May 1, 2025 11:55
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces configurable CORS support to the Martin server while preserving existing default behavior. Key changes include:

  • Adding a new CorsConfig and its make_cors_middleware method.
  • Updating server.rs to conditionally apply CORS middleware based on configuration.
  • Incorporating new tests and updating configuration documentation.

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
martin/tests/cors_test.rs Added tests to validate the new CORS configuration behavior.
martin/src/srv/server.rs Updated middleware setup to wrap the app conditionally with the CORS middleware.
martin/src/srv/mod.rs Imported the new CORS module.
martin/src/srv/cors.rs Introduced CorsConfig struct with middleware creation logic for CORS handling.
martin/src/srv/config.rs Integrated CorsConfig into SrvConfig and added a YAML parsing test for CORS settings.
docs/src/config-file.md Updated the configuration documentation to include CORS setup examples.
Comments suppressed due to low confidence (2)

docs/src/config-file.md:53

  • The configuration documentation uses 'allowed_origins' while the code uses 'origin'. Consider aligning these names for consistency.
allowed_origins:

martin/src/srv/cors.rs:8

  • Consider renaming the field 'origin' to 'allowed_origins' to match the configuration documentation and improve clarity.
pub origin: Vec<String>,

@NINNiT NINNiT requested a review from Copilot May 1, 2025 11:57
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

The PR adds configurable CORS support to the martin-server, ensuring backward compatibility while allowing deployment-specific CORS configuration via the server config and YAML file. It also includes updated tests and documentation for the new CORS feature.

  • Introduces a new CorsConfig with middleware generation in its own module.
  • Updates the server initialization to conditionally wrap the app with CORS middleware based on configuration.
  • Adds relevant tests and documentation for the configurable CORS settings.

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
martin/tests/cors_test.rs Adds tests to verify CORS behavior under various configurations.
martin/src/srv/server.rs Updates server creation to use the new configurable CORS middleware.
martin/src/srv/mod.rs Registers the new CORS module.
martin/src/srv/cors.rs Implements CorsConfig and middleware generation logic.
martin/src/srv/config.rs Updates SrvConfig to include CorsConfig and tests YAML parsing.
docs/src/config-file.md Enhances documentation with new CORS configuration options.
Comments suppressed due to low confidence (1)

martin/src/srv/server.rs:187

  • [nitpick] Consider caching the result of cors_middleware.unwrap_or_default() in a variable to avoid calling it twice, which can improve readability and reduce the chance of future errors if the unwrap logic evolves.
app.wrap(Condition::new(cors_middleware.is_some(), cors_middleware.unwrap_or_default(),))

@NINNiT NINNiT requested a review from Copilot May 1, 2025 12:10
@NINNiT NINNiT marked this pull request as ready for review May 1, 2025 12:10
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request adds configurable CORS support to the Martin server while preserving existing functionality. The changes include updating the server to conditionally apply CORS middleware based on configuration, introducing the new cors module for middleware generation, and adding tests and documentation for the new CORS settings.

  • Integrates CORS middleware creation in the server setup.
  • Adds new tests to verify different CORS configurations.
  • Updates the configuration parsing and docs to include CORS settings.

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
martin/tests/cors_test.rs Adds tests for verifying behavior when CORS is enabled/disabled or set with a specific origin.
martin/src/srv/server.rs Refactors server initialization to derive and conditionally apply CORS middleware.
martin/src/srv/mod.rs Registers the new cors module for managing CORS configuration.
martin/src/srv/cors.rs Implements the CorsConfig structure and middleware generation method.
martin/src/srv/config.rs Updates configuration parsing to include optional CORS settings.
docs/src/config-file.md Documents the new CORS configuration options.

@NINNiT NINNiT marked this pull request as draft May 1, 2025 12:11
Comment on lines 47 to 56
# CORS configuration
cors:
# enable/disable CORS [default: true]
enable: true
# sets the Access-Control-Allow-Origin header [default: *]
# '*' will use the requests ORIGIN header
origin:
- https://example.org
# sets Access-Control-Max-Age Header. Remove to use default. [default: None]
max_age: 3600
Copy link
Collaborator

@CommanderStorm CommanderStorm May 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly, this is great work ❤️

I don't quite like the enable: true part of the config.
If CORS is entirely unconfigured, the rest of the options don't quite make sense.

What about below?

Suggested change
# CORS configuration
cors:
# enable/disable CORS [default: true]
enable: true
# sets the Access-Control-Allow-Origin header [default: *]
# '*' will use the requests ORIGIN header
origin:
- https://example.org
# sets Access-Control-Max-Age Header. Remove to use default. [default: None]
max_age: 3600
# CORS configuration
#
# Can also be disabled via `cors: false`
cors:
# Sets the Access-Control-Allow-Origin header [default: *]
# '*' will use the requests ORIGIN header
origin:
- https://example.org
# Sets Access-Control-Max-Age Header. [default: null]
# null means not seting the header
max_age: 3600

(And changing the CorsConfig to be an enum and with #[serde(transparent)] on the variant with configuration)

renovate-changelog-fetcher and others added 2 commits May 2, 2025 23:27
adds a few more tests (e.g for CORS preflight requests -> max-age)
.gitignore Outdated
@@ -23,3 +23,4 @@ tmp/
####
#### Above content must match .dockerignore ####
####
.aider*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move this above the comment, and also add it to the .dockerignore

@nyurik nyurik added config Relates to Martin configuration serving Related to web serving component breaking and removed breaking labels May 2, 2025
@@ -44,6 +44,17 @@ preferred_encoding: gzip
# Enable or disable Martin web UI. At the moment, only allows `enable-for-all` which enables the web UI for all connections. This may be undesirable in a production environment. [default: disable]
web_ui: disable

# CORS Configuration
# Can also be disabled via `cors: false`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to document what does true means, and what happens if not set at all (default). Note that default here and in sub-items is a bit confusing, i.e. does cors: {} equal to cors: ~ ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point - i'll expand on that and double check what happens. might even add some tests
never seen ~ or {} in the wild though.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now, both cors: {} and cors: ~ seem to enable CORS using our defaults.

Copy link
Author

@NINNiT NINNiT May 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As it stands right now, it'll always fall back to the defaults.

  • no cors option at all -> Enable using defaults
  • cors: true -> Enable using defaults
  • only configuring a subset and omitting certain properties-> use defaults for properties that are not explicitly set.

this...

cors:
  max_age: 3600

would translate to this:

cors:
  origin: ["*"]
  max_age: 3600

The only way to disable CORS right now, is to explicitly set cors: false.

I guess cors: ~ should also disable it? What would you prefer

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, null is the same as skipped, so it should be the default

# CORS Configuration
# Can also be disabled via `cors: false`
cors:
# Sets the `Access-Control-Allow-Origin` header [default: *]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to double check -- does the default of * mean exactly the same as the current default behavior - i.e. allow all? I.e. currently it "will use the requests ORIGIN header" ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, "will use the requests ORIGIN header", is the current default .

docs.rs
send_wildcard() is disabled by default in actix_cors.

When all origins are allowed and send_wildcard is set, * will be sent in the Access-Control-Allow-Origin response header. If send_wildcard is not set, the client’s Origin request header will be echoed back in the Access-Control-Allow-Origin response header.

Copy link
Author

@NINNiT NINNiT May 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To expand on this a bit, this is the current CORS initialization on main:

    let cors_middleware = Cors::default()
            .allow_any_origin()
            .allowed_methods(vec!["GET"]);
  • default(): sets restrictive defaults
    No allowed origins, methods, request headers or exposed headers. Credentials
    not supported. No max age (will use browser's default).
  • allow_any_origin(): allows any origin, either by returning * in the header or by just returning the requests ORIGIN (this is the default in actix_cors as send_wildcard() is NOT enabled). .
  • allowed_methods(vec!["GET"]) sets Access-Control-Allow-Methods, only used in OPTIONS/preflight requests.

This PR keeps the exact same defaults for now, but allows disabling CORS altogether and dynamically setting ACCESS_CONTROL_ALLOW_ORIGIN and ACCESS_CONTROL_MAX_AGE.

If the defaults were to change at some point (this would be a breaking change), we could think about making it a bit more restrictive by default.

Maybe enable CORS by default, but require users to set specific origins. This would also ensure that an informed decision is made when opting out of "security" by allowing any origin or disabling it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
config Relates to Martin configuration serving Related to web serving component
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants